Skip to content

Commit 3a16d45

Browse files
authored
Batch of fixes during epic-stack implementation (#203)
* make ENV readonly without making process.env readonly * use compact json in what gets passed in with varlock/load * adjust loading logic in browser-like test env this fixes an issue for vitest runnign jsdom tests and needing to execute backend logic first * allow if(condition) resoler to coerce to boolean * allow @required/@sensitive to accept undefined * add isEmpty() and not() resolvers
1 parent 6f4e998 commit 3a16d45

File tree

17 files changed

+311
-43
lines changed

17 files changed

+311
-43
lines changed

.changeset/all-shoes-fall.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"varlock": patch
3+
---
4+
5+
allow if() to take 1 arg to coerce to boolean

.changeset/famous-experts-beg.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"varlock": patch
3+
---
4+
5+
allow @required/@sensitive to accept undefined

.changeset/red-clocks-strive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"varlock": patch
3+
---
4+
5+
make ENV readonly without making process.env readonly

.changeset/slimy-cows-obey.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"varlock": patch
3+
---
4+
5+
adjust loading behavior for browser testing (vitest jsdom)

.changeset/smooth-bears-join.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"varlock": patch
3+
---
4+
5+
add not() and isEmpty() resolvers

packages/varlock-website/src/content/docs/reference/functions.mdx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,29 @@ API_URL=if(eq($GIT_BRANCH, "main"), api.example.com, staging-api.example.com)
165165
```
166166
</div>
167167

168+
<div>
169+
### `not()`
170+
171+
Negates a value and returns a boolean. Falsy values are - `false`, `""`, `0`, `undefined`, and will be negated to `true`. Otherwise will return `false`.
172+
173+
```env-spec "not"
174+
# Negate the result of another function
175+
SHOULD_DISABLE_FEATURE=not(forEnv(production))
176+
```
177+
</div>
178+
179+
<div>
180+
### `isEmpty()`
181+
182+
Returns `true` if the value is `undefined` or an empty string, `false` otherwise.
183+
184+
```env-spec "isEmpty"
185+
# Check if a value is empty
186+
HAS_API_KEY=not(isEmpty($API_KEY))
187+
188+
# Use with conditional logic
189+
API_URL=if(isEmpty($CUSTOM_API_URL), "https://api.default.com", $CUSTOM_API_URL)
190+
```
191+
</div>
192+
168193
</div>

packages/varlock/src/auto-load.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { patchGlobalResponse } from './runtime/patch-response';
1111
// this also isolates the varlock loading process from the end user process
1212

1313

14-
const execResult = execSyncVarlock('load --format json-full', {
14+
const execResult = execSyncVarlock('load --format json-full-compact', {
1515
exitOnError: true,
1616
showLogsOnError: true,
1717
});

packages/varlock/src/cli/commands/load.command.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const commandSpec = define({
1515
format: {
1616
type: 'enum',
1717
short: 'f',
18-
choices: ['pretty', 'json', 'env', 'json-full'],
18+
choices: ['pretty', 'json', 'env', 'json-full', 'json-full-compact'],
1919
description: 'Format of output',
2020
default: 'pretty',
2121
},
@@ -70,8 +70,8 @@ export const commandFn: TypedGunshiCommandFn<typeof commandSpec> = async (ctx) =
7070
}
7171
} else if (format === 'json') {
7272
console.log(JSON.stringify(envGraph.getResolvedEnvObject(), null, 2));
73-
} else if (format === 'json-full') {
74-
console.log(JSON.stringify(envGraph.getSerializedGraph(), null, 2));
73+
} else if (format === 'json-full' || format === 'json-full-compact') {
74+
console.log(JSON.stringify(envGraph.getSerializedGraph(), null, format === 'json-full-compact' ? 0 : 2));
7575
} else if (format === 'env') {
7676
const resolvedEnv = envGraph.getResolvedEnvObject();
7777
for (const key in resolvedEnv) {

packages/varlock/src/env-graph/lib/config-item.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -273,15 +273,25 @@ export class ConfigItem {
273273
const usingOptional = requiredDec.name === 'optional';
274274

275275
// need to track if required-ness is dynamic
276-
this._isRequiredDynamic = requiredDec.decValueResolver
277-
? requiredDec.decValueResolver.staticValue === undefined : false;
276+
// NOTE - if any other resolver ever has a static value, we'll need to change this
277+
if (requiredDec.decValueResolver?.fnName !== '\0static') {
278+
this._isRequiredDynamic = true;
279+
}
278280

279281
const requiredDecoratorVal = await requiredDec.resolve();
280-
if (!_.isBoolean(requiredDecoratorVal)) {
281-
throw new SchemaError('@required/@optional must resolve to a boolean');
282+
// if we got an error, we'll bail and the error will be checked later
283+
if (requiredDec.schemaErrors.length) {
284+
// but we mark as not required so we don't _also_ get required error
285+
this._isRequired = false;
286+
return;
287+
}
288+
if (![true, false, undefined].includes(requiredDecoratorVal)) {
289+
throw new SchemaError('@required/@optional must resolve to a boolean or undefined');
290+
}
291+
if (requiredDecoratorVal !== undefined) {
292+
this._isRequired = usingOptional ? !requiredDecoratorVal : requiredDecoratorVal;
293+
return;
282294
}
283-
this._isRequired = usingOptional ? !requiredDecoratorVal : requiredDecoratorVal;
284-
return;
285295
}
286296

287297
// Root-level @defaultRequired
@@ -329,6 +339,13 @@ export class ConfigItem {
329339
const sensitiveDec = def.itemDef.decorators?.find((d) => d.name === 'sensitive');
330340
if (sensitiveDec) {
331341
const sensitiveDecValue = await sensitiveDec.resolve();
342+
// can bail if the decorator value resolution failed
343+
if (sensitiveDec.schemaErrors.length) {
344+
return;
345+
}
346+
if (![true, false, undefined].includes(sensitiveDecValue)) {
347+
throw new SchemaError('@sensitive must resolve to a boolean or undefined');
348+
}
332349
if (sensitiveDecValue !== undefined) {
333350
this._isSensitive = sensitiveDecValue;
334351
return;

packages/varlock/src/env-graph/lib/decorators.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export abstract class DecoratorInstance {
103103
this.resolvedValue = await this.decValueResolver.resolve();
104104
} catch (err) {
105105
this._schemaErrors.push(err as any);
106+
return;
106107
}
107108

108109
this.isResolved = true;

0 commit comments

Comments
 (0)