diff --git a/.changeset/add-public-decorator.md b/.changeset/add-public-decorator.md
new file mode 100644
index 00000000..f5c16b30
--- /dev/null
+++ b/.changeset/add-public-decorator.md
@@ -0,0 +1,5 @@
+---
+"varlock": patch
+---
+
+Add `@public` item decorator as the counterpart to `@sensitive`, matching the pattern of `@required`/`@optional` decorator pairs
diff --git a/packages/varlock-website/src/content/docs/reference/item-decorators.mdx b/packages/varlock-website/src/content/docs/reference/item-decorators.mdx
index f27c236e..f2a525de 100644
--- a/packages/varlock-website/src/content/docs/reference/item-decorators.mdx
+++ b/packages/varlock-website/src/content/docs/reference/item-decorators.mdx
@@ -76,6 +76,20 @@ SERVICE_X_CLIENT_ID=
```
+
+### `@public`
+**Value type:** `boolean`
+
+Opposite of [`@sensitive`](#sensitive). Equivalent to writing `@sensitive=false`.
+
+```env-spec
+# @defaultSensitive=true
+# ---
+# @public
+PUBLIC_API_URL=https://api.example.com
+```
+
+
### `@type`
**Value type:** [`data type`](/reference/data-types) (name only or function call)
diff --git a/packages/varlock/src/env-graph/lib/config-item.ts b/packages/varlock/src/env-graph/lib/config-item.ts
index f80e85f8..d937a40d 100644
--- a/packages/varlock/src/env-graph/lib/config-item.ts
+++ b/packages/varlock/src/env-graph/lib/config-item.ts
@@ -336,21 +336,26 @@ export class ConfigItem {
private async processSensitive() {
const sensitiveFromDataType = this.dataType?.isSensitive;
for (const def of this.defs) {
- const sensitiveDec = def.itemDef.decorators?.find((d) => d.name === 'sensitive');
+ const sensitiveDecs = def.itemDef.decorators?.filter((d) => d.name === 'sensitive' || d.name === 'public') || [];
+ // NOTE - checks for duplicates and using sensitive+public together are already handled more generally
+ const sensitiveDec = sensitiveDecs[0];
+
+ // Explicit per-item decorators
if (sensitiveDec) {
+ const usingPublic = sensitiveDec.name === 'public';
+
const sensitiveDecValue = await sensitiveDec.resolve();
// can bail if the decorator value resolution failed
if (sensitiveDec.schemaErrors.length) {
return;
}
if (![true, false, undefined].includes(sensitiveDecValue)) {
- throw new SchemaError('@sensitive must resolve to a boolean or undefined');
+ throw new SchemaError('@sensitive/@public must resolve to a boolean or undefined');
}
if (sensitiveDecValue !== undefined) {
- this._isSensitive = sensitiveDecValue;
+ this._isSensitive = usingPublic ? !sensitiveDecValue : sensitiveDecValue;
return;
}
- // TODO: do we want an opposite decorator similar to @required/@optional -- maybe @public?
}
// we skip `defaultSensitive` behaviour if the data type specifies sensitivity
diff --git a/packages/varlock/src/env-graph/lib/decorators.ts b/packages/varlock/src/env-graph/lib/decorators.ts
index 6714fb3d..711607f1 100644
--- a/packages/varlock/src/env-graph/lib/decorators.ts
+++ b/packages/varlock/src/env-graph/lib/decorators.ts
@@ -235,6 +235,10 @@ export const builtInItemDecorators: Array> = [
{
name: 'sensitive',
},
+ {
+ name: 'public',
+ incompatibleWith: ['sensitive'],
+ },
{
name: 'type',
useFnArgsResolver: true,
diff --git a/packages/varlock/src/env-graph/test/sensitive-decorator.test.ts b/packages/varlock/src/env-graph/test/sensitive-decorator.test.ts
index 41570eb1..254fad36 100644
--- a/packages/varlock/src/env-graph/test/sensitive-decorator.test.ts
+++ b/packages/varlock/src/env-graph/test/sensitive-decorator.test.ts
@@ -43,6 +43,45 @@ describe('@sensitive and @defaultSensitive tests', () => {
},
}));
+ test('@public and @sensitive mark items properly', envFilesTest({
+ envFile: outdent`
+ SENSITIVE= # @sensitive
+ SENSITIVE_TRUE= # @sensitive=true
+ SENSITIVE_FALSE= # @sensitive=false
+ SENSITIVE_UNDEF= # @sensitive=undefined
+ PUBLIC= # @public
+ PUBLIC_TRUE= # @public=true
+ PUBLIC_FALSE= # @public=false
+ PUBLIC_UNDEF= # @public=undefined
+ `,
+ expectSensitive: {
+ SENSITIVE: true,
+ SENSITIVE_TRUE: true,
+ SENSITIVE_FALSE: false,
+ SENSITIVE_UNDEF: true, // stays as default
+ PUBLIC: false,
+ PUBLIC_TRUE: false,
+ PUBLIC_FALSE: true,
+ PUBLIC_UNDEF: true, // stays as default
+ },
+ }));
+
+ test('@public and @sensitive can be overridden', envFilesTest({
+ files: {
+ '.env.schema': outdent`
+ WAS_SENSITIVE= # @sensitive
+ WAS_PUBLIC= # @public
+ `,
+ '.env': outdent`
+ WAS_SENSITIVE= # @public
+ WAS_PUBLIC= # @sensitive
+ `,
+ },
+ expectSensitive: {
+ WAS_SENSITIVE: false, WAS_PUBLIC: true,
+ },
+ }));
+
describe('dynamic @sensitive', () => {
test('dynamic @sensitive works', envFilesTest({
envFile: outdent`
@@ -54,6 +93,17 @@ describe('@sensitive and @defaultSensitive tests', () => {
TRUE: true, FALSE: false, UNDEF: true,
},
}));
+
+ test('dynamic @public works', envFilesTest({
+ envFile: outdent`
+ TRUE= # @public=if(yes)
+ FALSE= # @public=if(0)
+ UNDEF= # @public=if(true, undefined) # uses default
+ `,
+ expectSensitive: {
+ TRUE: false, FALSE: true, UNDEF: true,
+ },
+ }));
});
describe('inferFromPrefix() - use key prefix to infer sensitivity', () => {