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
5 changes: 5 additions & 0 deletions .changeset/fix-standard-vars-warning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"varlock": patch
---

Fix false warning 'found in environment but not connected to plugin' when standard vars are already wired via init decorator (e.g. `@initOp(token=$OP_SERVICE_ACCOUNT_TOKEN)`)
11 changes: 6 additions & 5 deletions packages/varlock/src/env-graph/lib/env-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,11 +337,6 @@ export class EnvGraph {
if (plugin.loadingError) return;
}

// check declared standardVars against the environment
for (const plugin of this.plugins) {
plugin._checkStandardVars(this);
}

// Attach builtin defs to any user-defined VARLOCK_* items
// (they may have been defined directly without a $VARLOCK_* reference)
for (const key in this.configSchema) {
Expand All @@ -358,6 +353,12 @@ export class EnvGraph {
}
}

// check declared standardVars against the environment
// (runs after root decorator processing so decValueResolver.deps is available)
for (const plugin of this.plugins) {
plugin._checkStandardVars(this);
}

// process config items
// checks decorators, sets data type, checks resolver args, adds deps
for (const itemKey in this.configSchema) {
Expand Down
24 changes: 22 additions & 2 deletions packages/varlock/src/env-graph/lib/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,15 @@ export class VarlockPlugin {
};

/** called by the loading infrastructure — checks declared standardVars against the graph */
_checkStandardVars(graph: { overrideValues: Record<string, string | undefined>, configSchema: Record<string, any> }) {
_checkStandardVars(graph: {
overrideValues: Record<string, string | undefined>,
configSchema: Record<string, any>,
sortedDataSources: Iterable<{
rootDecorators: Array<{
name: string, isFunctionCall: boolean, decValueResolver?: { deps: Array<string> },
}>,
}>,
}) {
if (!this.standardVars) return;
const { initDecorator, params } = this.standardVars;

Expand All @@ -260,7 +268,19 @@ export class VarlockPlugin {
};
});

const detected = resolved.filter((v) => v.matchedKey);
// collect config item keys already wired via init decorator instances
const initDecName = initDecorator.replace(/^@/, '');
const wiredVarNames = new Set<string>();
for (const source of graph.sortedDataSources) {
for (const rootDec of source.rootDecorators) {
if (rootDec.name === initDecName && rootDec.isFunctionCall && rootDec.decValueResolver) {
for (const dep of rootDec.decValueResolver.deps) wiredVarNames.add(dep);
}
}
}

// filter: only warn about vars detected in environment but NOT wired to the init decorator
const detected = resolved.filter((v) => v.matchedKey && !wiredVarNames.has(v.matchedKey));
if (detected.length === 0) return;

const detectedKeys = detected.map((v) => v.matchedKey!);
Expand Down
66 changes: 65 additions & 1 deletion packages/varlock/src/env-graph/test/plugins.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { describe, test } from 'vitest';
import {
describe, test, expect, vi,
} from 'vitest';
import path from 'node:path';
import outdent from 'outdent';
import { envFilesTest } from './helpers/generic-test';
import { EnvGraph, DotEnvFileDataSource } from '../index';

describe('plugins ', () => {
test('validate simple plugin works', envFilesTest({
Expand Down Expand Up @@ -94,4 +98,64 @@ describe('plugins ', () => {
`,
expectValues: { WARNED_ITEM: 'some_value', OTHER_ITEM: 'bar' },
}));

describe('standardVars warnings', () => {
async function loadGraphWithPlugin(envFile: string, overrideValues: Record<string, string>) {
const currentDir = path.dirname(expect.getState().testPath!);
vi.spyOn(process, 'cwd').mockReturnValue(currentDir);
const g = new EnvGraph();
g.overrideValues = overrideValues;
const source = new DotEnvFileDataSource('.env.schema', { overrideContents: envFile });
await g.setRootDataSource(source);
await g.finishLoad();
return g;
}

test('warns when standard var is in environment but not wired to init decorator', async () => {
const g = await loadGraphWithPlugin(
outdent`
# @plugin(./plugins/test-plugin-with-standard-vars/)
# @initTestStdVars()
# ---
MY_PLUGIN_TOKEN=
`,
{ MY_PLUGIN_TOKEN: 'some-token-value' },
);
const plugin = g.plugins.find((p) => p.name === '@varlock/test-plugin-with-standard-vars');
expect(plugin).toBeDefined();
expect(plugin!.warnings.length).toBe(1);
expect(plugin!.warnings[0].message).toContain('MY_PLUGIN_TOKEN');
expect(plugin!.warnings[0].message).toContain('not connected to plugin');
});

test('no warning when standard var is wired via init decorator', async () => {
const g = await loadGraphWithPlugin(
outdent`
# @plugin(./plugins/test-plugin-with-standard-vars/)
# @initTestStdVars(token=$MY_PLUGIN_TOKEN)
# ---
MY_PLUGIN_TOKEN=
`,
{ MY_PLUGIN_TOKEN: 'some-token-value' },
);
const plugin = g.plugins.find((p) => p.name === '@varlock/test-plugin-with-standard-vars');
expect(plugin).toBeDefined();
expect(plugin!.warnings.length).toBe(0);
});

test('no warning when standard var is not in environment', async () => {
const g = await loadGraphWithPlugin(
outdent`
# @plugin(./plugins/test-plugin-with-standard-vars/)
# @initTestStdVars()
# ---
MY_PLUGIN_TOKEN=
`,
{},
);
const plugin = g.plugins.find((p) => p.name === '@varlock/test-plugin-with-standard-vars');
expect(plugin).toBeDefined();
expect(plugin!.warnings.length).toBe(0);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@varlock/test-plugin-with-standard-vars",
"private": true,
"description": "plugin used in automated tests - has standardVars and init decorator",
"version": "1.0.0",
"type": "module",
"exports": {
"./plugin": "./plugin.js"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { plugin, SchemaError } = require('varlock/plugin-lib');

const instances = {};

plugin.name = 'test-plugin-with-standard-vars';

plugin.standardVars = {
initDecorator: '@initTestStdVars',
params: {
token: { key: 'MY_PLUGIN_TOKEN', dataType: 'myPluginToken' },
},
};

plugin.registerRootDecorator({
name: 'initTestStdVars',
isFunction: true,
useFnArgsResolver: true,
process() {
const id = '_default';
if (instances[id]) {
throw new SchemaError(`Instance with id "${id}" already initialized`);
}
instances[id] = true;
return { id };
},
execute() {},
});
Loading