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
21 changes: 14 additions & 7 deletions stasis-core/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ export const DEFAULT_LOCK = 'add'
export const DEFAULT_BUNDLE = 'none'
export const DEFAULT_SCOPE = 'full'

const envBool = (value) => Boolean(value && value !== '0')
// Strict boolean env parsing: ''/'0'/'false' -> false, '1'/'true' -> true. Anything else
// ('no', 'off', ...) throws -- a truthiness parse would read those as *enabling* the
// toggle, and a misconfigured env var should fail loudly, not silently flip a feature on.
const envBool = (name, value) => {
if (value === '' || value === '0' || value === 'false') return false
if (value === '1' || value === 'true') return true
throw new RangeError(`${name} must be ''/'0'/'false' or '1'/'true' (got '${value}')`)
}

// EXODUS_STASIS_RESOURCES is a comma-separated extension list; split + trim + drop
// empties so '' and ' png , svg ' normalize predictably (parseResourcesOption then
Expand Down Expand Up @@ -121,10 +128,10 @@ export class Config {
assert.equal(this.#env.resourcesBundleFile, resourcesBundleFile)
}
if (this.#env.debug !== undefined && debug !== undefined) {
assert.equal(envBool(this.#env.debug), debug)
assert.equal(envBool('EXODUS_STASIS_DEBUG', this.#env.debug), debug)
}
if (this.#env.childProcess !== undefined && childProcess !== undefined) {
assert.equal(envBool(this.#env.childProcess), childProcess)
assert.equal(envBool('EXODUS_STASIS_CHILD_PROCESS', this.#env.childProcess), childProcess)
}
if (this.#env.fs !== undefined && fs !== undefined) assert.equal(this.#env.fs, fs)
// Compare parsed sets, not the raw strings: ['png','svg'] and 'svg,png' are the
Expand All @@ -146,8 +153,8 @@ export class Config {
this.#bundle = this.#env.bundle || bundle || DEFAULT_BUNDLE
this.#bundleFile = this.#env.bundleFile || bundleFile || undefined
this.#resourcesBundleFile = this.#env.resourcesBundleFile || resourcesBundleFile || undefined
this.#debug = this.#env.debug !== undefined ? envBool(this.#env.debug) : (debug ?? false)
this.#childProcess = this.#env.childProcess !== undefined ? envBool(this.#env.childProcess) : (childProcess ?? false)
this.#debug = this.#env.debug !== undefined ? envBool('EXODUS_STASIS_DEBUG', this.#env.debug) : (debug ?? false)
this.#childProcess = this.#env.childProcess !== undefined ? envBool('EXODUS_STASIS_CHILD_PROCESS', this.#env.childProcess) : (childProcess ?? false)
// env wins over the option, like scope/lock/bundle; undefined means "fs untouched" (off).
this.#fs = this.#env.fs || fs || undefined
// env wins over the option (it's the process-wide signal); parseResourcesOption
Expand Down Expand Up @@ -270,8 +277,8 @@ export class Config {
if (this.#env.bundle !== undefined) assert.equal(this.#bundle, this.#env.bundle)
if (this.#env.bundleFile !== undefined) assert.equal(this.#bundleFile, this.#env.bundleFile)
if (this.#env.resourcesBundleFile !== undefined) assert.equal(this.#resourcesBundleFile, this.#env.resourcesBundleFile)
if (this.#env.debug !== undefined) assert.equal(this.#debug, envBool(this.#env.debug))
if (this.#env.childProcess !== undefined) assert.equal(this.#childProcess, envBool(this.#env.childProcess))
if (this.#env.debug !== undefined) assert.equal(this.#debug, envBool('EXODUS_STASIS_DEBUG', this.#env.debug))
if (this.#env.childProcess !== undefined) assert.equal(this.#childProcess, envBool('EXODUS_STASIS_CHILD_PROCESS', this.#env.childProcess))
if (this.#env.fs !== undefined) assert.equal(this.#fs, this.#env.fs)
if (this.#env.resources !== undefined) {
assert.ok(extSetsEqual(this.#resources, parseResourcesOption('env', envList(this.#env.resources))),
Expand Down
47 changes: 47 additions & 0 deletions tests/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,30 @@ test('Config debug env "1" is true', withEnv(
(t) => { t.assert.equal(new Config().debug, true) }
))

test('Config debug env "false" is false (not truthy-parsed)', withEnv(
{ EXODUS_STASIS_DEBUG: 'false' },
(t) => { t.assert.equal(new Config().debug, false) }
))

test('Config debug env "true" is true', withEnv(
{ EXODUS_STASIS_DEBUG: 'true' },
(t) => { t.assert.equal(new Config().debug, true) }
))

test('Config debug env "" is unset and keeps the default', withEnv(
{ EXODUS_STASIS_DEBUG: '' },
(t) => { t.assert.equal(new Config().debug, false) }
))

// An unrecognized value ('no', 'off', ...) must throw, not silently enable: the old
// truthiness parse read EXODUS_STASIS_DEBUG=no as debug *on*.
test('Config debug env rejects unrecognized values', withEnv(
{ EXODUS_STASIS_DEBUG: 'no' },
(t) => {
t.assert.throws(() => new Config(), { name: 'RangeError', message: /EXODUS_STASIS_DEBUG must be/ })
}
))

test('Config debug=true option conflicts with env debug=0', withEnv(
{ EXODUS_STASIS_DEBUG: '0' },
(t) => {
Expand Down Expand Up @@ -662,6 +686,29 @@ test('Config childProcess env "1" is true', withEnv(
(t) => { t.assert.equal(new Config().childProcess, true) }
))

test('Config childProcess env "false" is false (not truthy-parsed)', withEnv(
{ EXODUS_STASIS_CHILD_PROCESS: 'false' },
(t) => { t.assert.equal(new Config().childProcess, false) }
))

test('Config childProcess env "true" is true', withEnv(
{ EXODUS_STASIS_CHILD_PROCESS: 'true' },
(t) => { t.assert.equal(new Config().childProcess, true) }
))

test('Config childProcess env "" is unset and keeps the default', withEnv(
{ EXODUS_STASIS_CHILD_PROCESS: '' },
(t) => { t.assert.equal(new Config().childProcess, false) }
))

// Mirrors the debug case: 'yes' would have silently enabled cross-process capture.
test('Config childProcess env rejects unrecognized values', withEnv(
{ EXODUS_STASIS_CHILD_PROCESS: 'yes' },
(t) => {
t.assert.throws(() => new Config(), { name: 'RangeError', message: /EXODUS_STASIS_CHILD_PROCESS must be/ })
}
))

test('Config childProcess=true option conflicts with env childProcess=0', withEnv(
{ EXODUS_STASIS_CHILD_PROCESS: '0' },
(t) => {
Expand Down