Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/actions/coverage/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Test Coverage
description: Send coverage to Coveralls

runs:
using: "composite"
steps:
- name: Send coverage
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: cover.out
15 changes: 15 additions & 0 deletions .github/actions/go-setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Go Setup
description: Setup Go and install dependencies

runs:
using: "composite"
steps:
- uses: actions/setup-go@v6
with:
go-version: "1.25.7"

- name: Setup dependencies
shell: bash
run: |
go install github.com/magefile/mage
mage -v bootstrap
15 changes: 15 additions & 0 deletions .github/actions/test/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Test
description: Run Go tests

runs:
using: "composite"
steps:
- name: Run Tests
shell: bash
run: mage -v test

- name: Run Build
shell: bash
run: |
mage -v build:release
ls -la bin
21 changes: 21 additions & 0 deletions .github/workflows/test-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Test Dev

on:
push:
branches: [dev]
pull_request:
branches: [dev]
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
name: Run Tests

steps:
- uses: actions/checkout@v6
- name: Go Setup
uses: ./.github/actions/go-setup

- name: Run Tests
uses: ./.github/actions/test
24 changes: 24 additions & 0 deletions .github/workflows/test-master.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Test

on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
name: Run Tests

steps:
- uses: actions/checkout@v6
- name: Go Setup
uses: ./.github/actions/go-setup

- name: Run Tests
uses: ./.github/actions/test

- name: Send coverage
uses: ./.github/actions/coverage
10 changes: 8 additions & 2 deletions command/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func (c *RunCommand) Help() string {
jsInfo := c.UI.Colorize(".js", c.UI.InfoColor)
helpText := fmt.Sprintf(`
Usage: reactenv run [options] PATH

Inject environment variables into a built react app.

Example:
Expand Down Expand Up @@ -84,7 +84,13 @@ func (c *RunCommand) Run(args []string) int {
os.Exit(1)
}

renv.FindOccurrences()
err = renv.FindOccurrences()

if err != nil {
c.UI.Error(fmt.Sprintf("There was an error while searching for __reactenv variables in the %d '%s' files within '%s', therefore nothing was injected.\n", renv.FilesMatchTotal, fileMatchExpression, pathToAssets))
c.UI.Error(fmt.Sprintf("%v", err))
os.Exit(1)
}

if renv.OccurrencesTotal == 0 {
c.UI.Warn(ui.WrapAtLength(fmt.Sprintf("No reactenv environment variables were found in any of the %d '%s' files within '%s', therefore nothing was injected.\n", renv.FilesMatchTotal, fileMatchExpression, pathToAssets), 0))
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/mitchellh/gox v1.0.1
github.com/posener/complete v1.2.3
github.com/schollz/progressbar/v3 v3.19.0
github.com/stretchr/testify v1.11.1
gotest.tools/gotestsum v1.13.0
)

Expand All @@ -22,6 +23,7 @@ require (
github.com/armon/go-radix v1.0.0 // indirect
github.com/bgentry/speakeasy v0.2.0 // indirect
github.com/bitfield/gotestdox v0.2.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dnephin/pflag v1.0.7 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
Expand All @@ -36,6 +38,7 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/iochan v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
Expand All @@ -46,4 +49,5 @@ require (
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.42.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
5 changes: 3 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand All @@ -130,6 +130,7 @@ golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
12 changes: 10 additions & 2 deletions magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,23 @@ func Test() error {
log := NewLogger()
defer log.End()
return RunSync([][]string{
{"gotestsum", "--format", "pkgname", "--", "--cover", "./..."},
{"gotestsum", "--format", "pkgname", "--", "-coverprofile", "cover.out", "./..."},
})
}

func TestWatch() error {
log := NewLogger()
defer log.End()
return RunSync([][]string{
{"gotestsum", "--watch", "--format", "pkgname", "--", "./..."},
})
}

func Bench() error {
log := NewLogger()
defer log.End()
return RunSync([][]string{
{"gotestsum", "--format", "pkgname", "--", "--cover", "-bench", ".", "-benchmem", "./..."},
{"gotestsum", "--format", "pkgname", "--", "-bench", ".", "-benchmem", "./..."},
})
}

Expand Down
2 changes: 1 addition & 1 deletion npm/reactenv-darwin-arm64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reactenv/cli-darwin-arm64",
"version": "0.1.81",
"version": "0.1.88",
"description": "The macOS ARM 64-bit binary for reactenv, an experimental solution to inject env variables after a build.",
"license": "Apache-2.0",
"os": [
Expand Down
2 changes: 1 addition & 1 deletion npm/reactenv-darwin-x64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reactenv/cli-darwin-x64",
"version": "0.1.81",
"version": "0.1.88",
"description": "The macOS 64-bit binary for reactenv, an experimental solution to inject env variables after a build.",
"license": "Apache-2.0",
"os": [
Expand Down
2 changes: 1 addition & 1 deletion npm/reactenv-linux-arm64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reactenv/cli-linux-arm64",
"version": "0.1.81",
"version": "0.1.88",
"description": "The Linux ARM 64-bit binary for reactenv, an experimental solution to inject env variables after a build.",
"license": "Apache-2.0",
"os": [
Expand Down
2 changes: 1 addition & 1 deletion npm/reactenv-linux-x64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reactenv/cli-linux-x64",
"version": "0.1.81",
"version": "0.1.88",
"description": "The Linux 64-bit binary for reactenv, an experimental solution to inject env variables after a build.",
"license": "Apache-2.0",
"os": [
Expand Down
2 changes: 1 addition & 1 deletion npm/reactenv-win32-x64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reactenv/cli-win32-x64",
"version": "0.1.81",
"version": "0.1.88",
"description": "The Windows 64-bit binary for reactenv, an experimental solution to inject env variables after a build.",
"license": "Apache-2.0",
"os": [
Expand Down
2 changes: 1 addition & 1 deletion npm/reactenv/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reactenv/cli",
"version": "0.1.81",
"version": "0.1.88",
"description": "reactenv, an experimental solution to inject env variables after a build.",
"license": "Apache-2.0",
"bin": {
Expand Down
95 changes: 88 additions & 7 deletions reactenv/reactenv.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package reactenv

import (
"bytes"
"fmt"
"io/fs"
"os"
Expand All @@ -12,8 +13,7 @@ import (
)

const (
REACTENV_PREFIX = "__reactenv"
REACTENV_FIND_EXPRESSION = `(__reactenv\.[a-zA-Z_$][0-9a-zA-Z_$]*)`
REACTENV_PREFIX = "__reactenv"
)

type Reactenv struct {
Expand All @@ -22,7 +22,7 @@ type Reactenv struct {
// Path of directory to scan
Dir string

// Total file count (that match `REACTENV_FIND_EXPRESSION`, within `Dir`)
// Total file count - that have matches - within specified `Dir`
FilesMatchTotal int
// Files with occurrences (not every matched file will have an occurrence, so this may be less than `FilesMatchTotal`)
Files []*fs.DirEntry
Expand Down Expand Up @@ -62,6 +62,7 @@ func NewReactenv(ui *ui.Ui) *Reactenv {
// Populates `Reactenv.Files` with all files that match `fileMatchExpression`
func (r *Reactenv) FindFiles(dir string, fileMatchExpression string) error {
r.Dir = dir
r.Files = make([]*fs.DirEntry, 0)
files, err := os.ReadDir(r.Dir)

if err != nil {
Expand All @@ -76,7 +77,8 @@ func (r *Reactenv) FindFiles(dir string, fileMatchExpression string) error {

for _, file := range files {
if fileMatcher.MatchString(file.Name()) && !file.IsDir() {
r.Files = append(r.Files, &file)
fileEntry := file
r.Files = append(r.Files, &fileEntry)
}
}

Expand Down Expand Up @@ -120,8 +122,80 @@ func (r *Reactenv) FilesWalkContents(fileCb func(fileIndex int, file fs.DirEntry
return nil
}

// Strictly locates all instances of valid reactenv variables and returns their byte
// start and end indices.
//
// This implementation allows for side-by-side occurrences, which a regex pattern
// match would not (without an unsupported lookahead assertion).
func FindAllOccurrenceBytePositions(data []byte, prefix []byte) [][]int {
// Guard against infinite loop allocation caused by empty prefixes.
if len(prefix) == 0 {
return nil
}

var indices [][]int

// Establish absolute boundaries by finding all prefix locations
var prefixStarts []int
offset := 0
for {
idx := bytes.Index(data[offset:], prefix)
if idx == -1 {
break
}
absoluteIdx := offset + idx
prefixStarts = append(prefixStarts, absoluteIdx)
offset = absoluteIdx + len(prefix)
}

// Iterate through boundaries and enforce positional grammar
for i, start := range prefixStarts {
current := start + len(prefix)

limit := len(data)
if i+1 < len(prefixStarts) {
limit = prefixStarts[i+1]
}

// Validate the initial character (cannot be a numeral).
if current < limit {
firstByte := data[current]
isValidStart := (firstByte >= 'a' && firstByte <= 'z') ||
(firstByte >= 'A' && firstByte <= 'Z') ||
firstByte == '_' || firstByte == '$'

if !isValidStart {
// Abort: The character following the dot is invalid
continue
}
current++
} else {
// Abort: The string terminates immediately after the dot
continue
}

// Scan forward for all subsequent valid identifier bytes
for current < limit {
b := data[current]
isValid := (b >= 'a' && b <= 'z') ||
(b >= 'A' && b <= 'Z') ||
(b >= '0' && b <= '9') ||
b == '_' || b == '$'

if !isValid {
break
}
current++
}

indices = append(indices, []int{start, current})
}

return indices
}

// Walks every file and populates `Reactenv.Occurrences*` fields.
func (r *Reactenv) FindOccurrences() {
func (r *Reactenv) FindOccurrences() error {
// Reset occurrence fields
r.OccurrencesTotal = 0
r.OccurrencesByFile = make([]*FileOccurrences, 0)
Expand All @@ -133,8 +207,9 @@ func (r *Reactenv) FindOccurrences() {
newOccurrencesByFile := make([]*FileOccurrences, 0)
fileIndexesToRemove := make(map[int]int, 0)

r.FilesWalkContents(func(fileIndex int, file fs.DirEntry, filePath string, fileContents []byte) error {
fileOccurrences := regexp.MustCompile(REACTENV_FIND_EXPRESSION).FindAllIndex(fileContents, -1)
err := r.FilesWalkContents(func(fileIndex int, file fs.DirEntry, filePath string, fileContents []byte) error {
prefix := fmt.Appendf([]byte(""), "%s.", REACTENV_PREFIX)
fileOccurrences := FindAllOccurrenceBytePositions(fileContents, prefix)

fileOccurrencesToStore := make([]Occurrence, 0, len(fileOccurrences))
r.OccurrencesTotal += len(fileOccurrences)
Expand Down Expand Up @@ -166,6 +241,10 @@ func (r *Reactenv) FindOccurrences() {
return nil
})

if err != nil {
return err
}

// Remove files with no occurrences
if len(fileIndexesToRemove) > 0 {
for fileIndex, file := range r.Files {
Expand All @@ -178,6 +257,8 @@ func (r *Reactenv) FindOccurrences() {
r.Files = newFiles
r.OccurrencesByFile = newOccurrencesByFile
}

return nil
}

func (r *Reactenv) ReplaceOccurrences() {
Expand Down
Loading