@owlmeans/test 0.1.18-rc.34 → 0.1.18-rc.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Foundation helpers shared by the OwlMeans test packages and individual package test suites.
4
4
 
5
- - `loadEnv(opts?)` — locate `<monorepo-root>/.env`, parse it, merge into `process.env` (idempotent; no-op when Bun already loaded the file via `--env-file`).
6
- - `requireEnv(keys)` — returns `{ ok: true }` or `{ skip: true, reason }` so callers can `test.skip(...)` when an integration variable is missing.
7
- - `hasEnv(key)` — boolean shorthand for conditional service provisioning in `tests/context.ts`.
5
+ - `envHelper.loadEnv(opts?)` — locate `<monorepo-root>/.env`, parse it, merge into `process.env` (idempotent; no-op when Bun already loaded the file via `--env-file`).
6
+ - `envHelper.requireEnv(keys)` — returns `{ ok: true }` or `{ skip: true, reason }` so callers can `test.skip(...)` when an integration variable is missing.
7
+ - `envHelper.hasEnv(key)` — boolean shorthand for conditional service provisioning in `tests/context.ts`.
8
8
  - `makeGates(spec)` — builds a frozen record of `{ <name>: { skip, reason } }` so a per-package `tests/context.ts` exposes one source of truth and individual specs can self-skip.
9
9
  - `loadFixture<T>(relPath)` — JSON fixture loader rooted at the consuming package's `tests/fixtures/`.
10
10
 
@@ -18,7 +18,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
18
18
  your project's skill store (`.agents/skills/`):
19
19
 
20
20
  ```sh
21
- npx @owlmeans/agent-skills@^0.1.18-rc.48
21
+ npx @owlmeans/agent-skills@^0.1.18-rc.49
22
22
  ```
23
23
 
24
24
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/test",
4
- "version": "0.1.18-rc.34",
5
- "generatedAt": "2026-10-05T08:32:19.099Z",
4
+ "version": "0.1.18-rc.35",
5
+ "generatedAt": "2026-10-05T21:03:06.380Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -6,7 +6,7 @@ description: Category-A unit tests for OwlMeans Common packages — no mocks, re
6
6
 
7
7
  # Unit Tests — Category A (no mocks)
8
8
 
9
- **Install:** `"@owlmeans/test": "^0.1.18-rc.34"` in `devDependencies`
9
+ **Install:** `"@owlmeans/test": "^0.1.18-rc.35"` in `devDependencies`
10
10
 
11
11
  Apply this skill when adding tests to packages in category A (see `testing-overview`). The list includes core abstractions (`context`, `config`, `error`, `entrypoint`, `route`, `resource`, …) and platform-agnostic services (`api`, `state`, `flow`, `i18n`, `client-flow`, `client-socket`, `client-job`, `server-route`, `server-context`, `web-db`, …).
12
12
 
package/build/env.d.ts CHANGED
@@ -1,15 +1,4 @@
1
- export interface EnvOk {
2
- ok: true;
3
- }
4
- export interface EnvSkip {
5
- skip: true;
6
- reason: string;
7
- }
8
- export type EnvGate = EnvOk | EnvSkip;
9
- export declare const loadEnv: (opts?: {
10
- force?: boolean;
11
- file?: string;
12
- }) => void;
13
- export declare const hasEnv: (key: string) => boolean;
14
- export declare const requireEnv: (keys: string[]) => EnvGate;
1
+ import type { EnvHelper } from './types.js';
2
+ export declare const createEnvHelper: () => EnvHelper;
3
+ export declare const envHelper: EnvHelper;
15
4
  //# sourceMappingURL=env.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,IAAI,CAAA;CACT;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,IAAI,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;AAkCrC,eAAO,MAAM,OAAO,UAAW;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KAAG,IAanE,CAAA;AAED,eAAO,MAAM,MAAM,QAAS,MAAM,KAAG,OAIpC,CAAA;AAED,eAAO,MAAM,UAAU,SAAU,MAAM,EAAE,KAAG,OAK3C,CAAA"}
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAW,SAAS,EAAkB,MAAM,YAAY,CAAA;AAKpE,eAAO,MAAM,eAAe,QAAO,SA4DlC,CAAA;AAED,eAAO,MAAM,SAAS,WAAoB,CAAA"}
package/build/env.js CHANGED
@@ -1,63 +1,68 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { dirname, resolve } from 'node:path';
3
+ // Process-wide on purpose: the file is merged into `process.env`, which every helper instance shares.
3
4
  let loaded = false;
4
- const findRoot = (start) => {
5
- let dir = start;
6
- for (let i = 0; i < 10; i++) {
7
- if (existsSync(resolve(dir, 'bun.lock')) || existsSync(resolve(dir, 'package.json')) && existsSync(resolve(dir, 'packages'))) {
8
- return dir;
5
+ export const createEnvHelper = () => {
6
+ const findRoot = (start) => {
7
+ let dir = start;
8
+ for (let i = 0; i < 10; i++) {
9
+ if (existsSync(resolve(dir, 'bun.lock')) || existsSync(resolve(dir, 'package.json')) && existsSync(resolve(dir, 'packages'))) {
10
+ return dir;
11
+ }
12
+ const parent = dirname(dir);
13
+ if (parent === dir)
14
+ return null;
15
+ dir = parent;
9
16
  }
10
- const parent = dirname(dir);
11
- if (parent === dir)
12
- return null;
13
- dir = parent;
14
- }
15
- return null;
16
- };
17
- const parseEnv = (text) => {
18
- const out = {};
19
- for (const raw of text.split(/\r?\n/)) {
20
- const line = raw.trim();
21
- if (line === '' || line.startsWith('#'))
22
- continue;
23
- const eq = line.indexOf('=');
24
- if (eq < 1)
25
- continue;
26
- const key = line.slice(0, eq).trim();
27
- let val = line.slice(eq + 1).trim();
28
- if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
29
- val = val.slice(1, -1);
17
+ return null;
18
+ };
19
+ const parseEnv = (text) => {
20
+ const out = {};
21
+ for (const raw of text.split(/\r?\n/)) {
22
+ const line = raw.trim();
23
+ if (line === '' || line.startsWith('#'))
24
+ continue;
25
+ const eq = line.indexOf('=');
26
+ if (eq < 1)
27
+ continue;
28
+ const key = line.slice(0, eq).trim();
29
+ let val = line.slice(eq + 1).trim();
30
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
31
+ val = val.slice(1, -1);
32
+ }
33
+ out[key] = val;
30
34
  }
31
- out[key] = val;
32
- }
33
- return out;
34
- };
35
- export const loadEnv = (opts) => {
36
- if (loaded && opts?.force !== true)
37
- return;
38
- loaded = true;
39
- const root = findRoot(process.cwd());
40
- const envPath = opts?.file ?? (root != null ? resolve(root, '.env') : null);
41
- if (envPath == null || !existsSync(envPath))
42
- return;
43
- const text = readFileSync(envPath, 'utf8');
44
- const parsed = parseEnv(text);
45
- for (const [k, v] of Object.entries(parsed)) {
46
- if (process.env[k] == null || process.env[k] === '') {
47
- process.env[k] = v;
35
+ return out;
36
+ };
37
+ const loadEnv = (opts) => {
38
+ if (loaded && opts?.force !== true)
39
+ return;
40
+ loaded = true;
41
+ const root = findRoot(process.cwd());
42
+ const envPath = opts?.file ?? (root != null ? resolve(root, '.env') : null);
43
+ if (envPath == null || !existsSync(envPath))
44
+ return;
45
+ const text = readFileSync(envPath, 'utf8');
46
+ const parsed = parseEnv(text);
47
+ for (const [k, v] of Object.entries(parsed)) {
48
+ if (process.env[k] == null || process.env[k] === '') {
49
+ process.env[k] = v;
50
+ }
48
51
  }
49
- }
50
- };
51
- export const hasEnv = (key) => {
52
- loadEnv();
53
- const v = process.env[key];
54
- return v != null && v !== '';
55
- };
56
- export const requireEnv = (keys) => {
57
- loadEnv();
58
- const missing = keys.filter(k => !hasEnv(k));
59
- if (missing.length === 0)
60
- return { ok: true };
61
- return { skip: true, reason: `Missing env: ${missing.join(', ')}` };
52
+ };
53
+ const hasEnv = (key) => {
54
+ loadEnv();
55
+ const v = process.env[key];
56
+ return v != null && v !== '';
57
+ };
58
+ const requireEnv = (keys) => {
59
+ loadEnv();
60
+ const missing = keys.filter(k => !hasEnv(k));
61
+ if (missing.length === 0)
62
+ return { ok: true };
63
+ return { skip: true, reason: `Missing env: ${missing.join(', ')}` };
64
+ };
65
+ return { loadEnv, hasEnv, requireEnv };
62
66
  };
67
+ export const envHelper = createEnvHelper();
63
68
  //# sourceMappingURL=env.js.map
package/build/env.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAG5C,sGAAsG;AACtG,IAAI,MAAM,GAAG,KAAK,CAAA;AAElB,MAAM,CAAC,MAAM,eAAe,GAAG,GAAc,EAAE;IAC7C,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAiB,EAAE;QAChD,IAAI,GAAG,GAAG,KAAK,CAAA;QACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,IAAI,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;gBAC7H,OAAO,GAAG,CAAA;YACZ,CAAC;YACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;YAC3B,IAAI,MAAM,KAAK,GAAG;gBAAE,OAAO,IAAI,CAAA;YAC/B,GAAG,GAAG,MAAM,CAAA;QACd,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,MAAM,QAAQ,GAAG,CAAC,IAAY,EAA0B,EAAE;QACxD,MAAM,GAAG,GAA2B,EAAE,CAAA;QACtC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;YACvB,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAQ;YACjD,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAC5B,IAAI,EAAE,GAAG,CAAC;gBAAE,SAAQ;YACpB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;YACpC,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;YACnC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC7F,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YACxB,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;QAChB,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,CAAC,IAAqB,EAAQ,EAAE;QAC9C,IAAI,MAAM,IAAI,IAAI,EAAE,KAAK,KAAK,IAAI;YAAE,OAAM;QAC1C,MAAM,GAAG,IAAI,CAAA;QACb,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACpC,MAAM,OAAO,GAAG,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAC3E,IAAI,OAAO,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAM;QACnD,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC1C,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC7B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5C,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;gBACpD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;YACpB,CAAC;QACH,CAAC;IACH,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,CAAC,GAAW,EAAW,EAAE;QACtC,OAAO,EAAE,CAAA;QACT,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC1B,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAA;IAC9B,CAAC,CAAA;IAED,MAAM,UAAU,GAAG,CAAC,IAAc,EAAW,EAAE;QAC7C,OAAO,EAAE,CAAA;QACT,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;QAC5C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QAC7C,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAA;IACrE,CAAC,CAAA;IAED,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAA;AACxC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,eAAe,EAAE,CAAA"}
package/build/gates.d.ts CHANGED
@@ -1,8 +1,4 @@
1
- import type { EnvGate } from './env.js';
2
- export type GateSpec = Record<string, string[]>;
3
- export type Gates<S extends GateSpec> = {
4
- readonly [K in keyof S]: EnvGate;
5
- };
1
+ import type { EnvGate, GateSpec, Gates } from './types.js';
6
2
  /**
7
3
  * Build a frozen record of gates from a spec of `{ name: [envKey, ...] }`.
8
4
  * Use in `tests/context.ts` so a single source of truth describes which
@@ -1 +1 @@
1
- {"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../src/gates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAEvC,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAE/C,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,QAAQ,IAAI;IAAE,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO;CAAE,CAAA;AAE5E;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,QAAQ,QAAQ,CAAC,KAAG,KAAK,CAAC,CAAC,CAM9D,CAAA;AAED,eAAO,MAAM,MAAM,SAAU,OAAO,KAAG,IAAI,IAAI;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAClC,CAAA"}
1
+ {"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../src/gates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAE1D;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,QAAQ,QAAQ,CAAC,KAAG,KAAK,CAAC,CAAC,CAM9D,CAAA;AAED,eAAO,MAAM,MAAM,SAAU,OAAO,KAAG,IAAI,IAAI;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAClC,CAAA"}
package/build/gates.js CHANGED
@@ -1,4 +1,4 @@
1
- import { requireEnv } from './env.js';
1
+ import { envHelper } from './env.js';
2
2
  /**
3
3
  * Build a frozen record of gates from a spec of `{ name: [envKey, ...] }`.
4
4
  * Use in `tests/context.ts` so a single source of truth describes which
@@ -8,7 +8,7 @@ import { requireEnv } from './env.js';
8
8
  export const makeGates = (spec) => {
9
9
  const out = {};
10
10
  for (const [name, keys] of Object.entries(spec)) {
11
- out[name] = requireEnv(keys);
11
+ out[name] = envHelper.requireEnv(keys);
12
12
  }
13
13
  return Object.freeze(out);
14
14
  };
@@ -1 +1 @@
1
- {"version":3,"file":"gates.js","sourceRoot":"","sources":["../src/gates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAOrC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAqB,IAAO,EAAY,EAAE;IACjE,MAAM,GAAG,GAAG,EAA6B,CAAA;IACzC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,GAAG,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAC9B,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAa,CAAA;AACvC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,IAAa,EAA0C,EAAE,CAC7E,IAAwB,CAAC,IAAI,KAAK,IAAI,CAAA"}
1
+ {"version":3,"file":"gates.js","sourceRoot":"","sources":["../src/gates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAGpC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAqB,IAAO,EAAY,EAAE;IACjE,MAAM,GAAG,GAAG,EAA6B,CAAA;IACzC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;IACxC,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAa,CAAA;AACvC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,IAAa,EAA0C,EAAE,CAC7E,IAAwB,CAAC,IAAI,KAAK,IAAI,CAAA"}
package/build/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './env.js';
2
2
  export * from './gates.js';
3
3
  export * from './fixtures.js';
4
+ export type * from './types.js';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,mBAAmB,YAAY,CAAA"}
@@ -0,0 +1,31 @@
1
+ export interface EnvOk {
2
+ ok: true;
3
+ }
4
+ export interface EnvSkip {
5
+ skip: true;
6
+ reason: string;
7
+ }
8
+ export type EnvGate = EnvOk | EnvSkip;
9
+ export type GateSpec = Record<string, string[]>;
10
+ export type Gates<S extends GateSpec> = {
11
+ readonly [K in keyof S]: EnvGate;
12
+ };
13
+ export interface LoadEnvOptions {
14
+ /** Re-read the file even when it was already loaded once in this process. */
15
+ force?: boolean;
16
+ /** The file to read instead of `<monorepo-root>/.env`. */
17
+ file?: string;
18
+ }
19
+ /** The test environment: `<monorepo-root>/.env` merged into `process.env`, and gates over it. */
20
+ export interface EnvHelper {
21
+ /**
22
+ * Locate `<monorepo-root>/.env`, parse it and merge it into `process.env` — a variable that is
23
+ * already set and non-empty wins. Idempotent per process unless `force` is passed.
24
+ */
25
+ loadEnv: (opts?: LoadEnvOptions) => void;
26
+ /** Whether a variable is set to a non-empty value (after {@link EnvHelper.loadEnv}). */
27
+ hasEnv: (key: string) => boolean;
28
+ /** `{ ok: true }` when every key is set, otherwise `{ skip: true, reason }` naming the missing ones. */
29
+ requireEnv: (keys: string[]) => EnvGate;
30
+ }
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,IAAI,CAAA;CACT;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,IAAI,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;CACf;AAGD,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;AAGrC,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAG/C,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,QAAQ,IAAI;IAAE,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO;CAAE,CAAA;AAE5E,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,iGAAiG;AACjG,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,cAAc,KAAK,IAAI,CAAA;IACxC,wFAAwF;IACxF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;IAChC,wGAAwG;IACxG,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAA;CACxC"}
package/build/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/test",
3
- "version": "0.1.18-rc.34",
3
+ "version": "0.1.18-rc.35",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,7 +21,7 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@owlmeans/context": "^0.1.18-rc.33"
24
+ "@owlmeans/context": "^0.1.18-rc.34"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@owlmeans/dep-config": "^0.1.18-rc.6",
package/src/env.ts CHANGED
@@ -1,73 +1,70 @@
1
1
  import { existsSync, readFileSync } from 'node:fs'
2
2
  import { dirname, resolve } from 'node:path'
3
+ import type { EnvGate, EnvHelper, LoadEnvOptions } from './types.js'
3
4
 
4
- export interface EnvOk {
5
- ok: true
6
- }
7
-
8
- export interface EnvSkip {
9
- skip: true
10
- reason: string
11
- }
12
-
13
- export type EnvGate = EnvOk | EnvSkip
14
-
5
+ // Process-wide on purpose: the file is merged into `process.env`, which every helper instance shares.
15
6
  let loaded = false
16
7
 
17
- const findRoot = (start: string): string | null => {
18
- let dir = start
19
- for (let i = 0; i < 10; i++) {
20
- if (existsSync(resolve(dir, 'bun.lock')) || existsSync(resolve(dir, 'package.json')) && existsSync(resolve(dir, 'packages'))) {
21
- return dir
8
+ export const createEnvHelper = (): EnvHelper => {
9
+ const findRoot = (start: string): string | null => {
10
+ let dir = start
11
+ for (let i = 0; i < 10; i++) {
12
+ if (existsSync(resolve(dir, 'bun.lock')) || existsSync(resolve(dir, 'package.json')) && existsSync(resolve(dir, 'packages'))) {
13
+ return dir
14
+ }
15
+ const parent = dirname(dir)
16
+ if (parent === dir) return null
17
+ dir = parent
22
18
  }
23
- const parent = dirname(dir)
24
- if (parent === dir) return null
25
- dir = parent
19
+ return null
26
20
  }
27
- return null
28
- }
29
21
 
30
- const parseEnv = (text: string): Record<string, string> => {
31
- const out: Record<string, string> = {}
32
- for (const raw of text.split(/\r?\n/)) {
33
- const line = raw.trim()
34
- if (line === '' || line.startsWith('#')) continue
35
- const eq = line.indexOf('=')
36
- if (eq < 1) continue
37
- const key = line.slice(0, eq).trim()
38
- let val = line.slice(eq + 1).trim()
39
- if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
40
- val = val.slice(1, -1)
22
+ const parseEnv = (text: string): Record<string, string> => {
23
+ const out: Record<string, string> = {}
24
+ for (const raw of text.split(/\r?\n/)) {
25
+ const line = raw.trim()
26
+ if (line === '' || line.startsWith('#')) continue
27
+ const eq = line.indexOf('=')
28
+ if (eq < 1) continue
29
+ const key = line.slice(0, eq).trim()
30
+ let val = line.slice(eq + 1).trim()
31
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
32
+ val = val.slice(1, -1)
33
+ }
34
+ out[key] = val
41
35
  }
42
- out[key] = val
36
+ return out
43
37
  }
44
- return out
45
- }
46
38
 
47
- export const loadEnv = (opts?: { force?: boolean, file?: string }): void => {
48
- if (loaded && opts?.force !== true) return
49
- loaded = true
50
- const root = findRoot(process.cwd())
51
- const envPath = opts?.file ?? (root != null ? resolve(root, '.env') : null)
52
- if (envPath == null || !existsSync(envPath)) return
53
- const text = readFileSync(envPath, 'utf8')
54
- const parsed = parseEnv(text)
55
- for (const [k, v] of Object.entries(parsed)) {
56
- if (process.env[k] == null || process.env[k] === '') {
57
- process.env[k] = v
39
+ const loadEnv = (opts?: LoadEnvOptions): void => {
40
+ if (loaded && opts?.force !== true) return
41
+ loaded = true
42
+ const root = findRoot(process.cwd())
43
+ const envPath = opts?.file ?? (root != null ? resolve(root, '.env') : null)
44
+ if (envPath == null || !existsSync(envPath)) return
45
+ const text = readFileSync(envPath, 'utf8')
46
+ const parsed = parseEnv(text)
47
+ for (const [k, v] of Object.entries(parsed)) {
48
+ if (process.env[k] == null || process.env[k] === '') {
49
+ process.env[k] = v
50
+ }
58
51
  }
59
52
  }
60
- }
61
53
 
62
- export const hasEnv = (key: string): boolean => {
63
- loadEnv()
64
- const v = process.env[key]
65
- return v != null && v !== ''
66
- }
54
+ const hasEnv = (key: string): boolean => {
55
+ loadEnv()
56
+ const v = process.env[key]
57
+ return v != null && v !== ''
58
+ }
67
59
 
68
- export const requireEnv = (keys: string[]): EnvGate => {
69
- loadEnv()
70
- const missing = keys.filter(k => !hasEnv(k))
71
- if (missing.length === 0) return { ok: true }
72
- return { skip: true, reason: `Missing env: ${missing.join(', ')}` }
60
+ const requireEnv = (keys: string[]): EnvGate => {
61
+ loadEnv()
62
+ const missing = keys.filter(k => !hasEnv(k))
63
+ if (missing.length === 0) return { ok: true }
64
+ return { skip: true, reason: `Missing env: ${missing.join(', ')}` }
65
+ }
66
+
67
+ return { loadEnv, hasEnv, requireEnv }
73
68
  }
69
+
70
+ export const envHelper = createEnvHelper()
package/src/gates.ts CHANGED
@@ -1,9 +1,5 @@
1
- import { requireEnv } from './env.js'
2
- import type { EnvGate } from './env.js'
3
-
4
- export type GateSpec = Record<string, string[]>
5
-
6
- export type Gates<S extends GateSpec> = { readonly [K in keyof S]: EnvGate }
1
+ import { envHelper } from './env.js'
2
+ import type { EnvGate, GateSpec, Gates } from './types.js'
7
3
 
8
4
  /**
9
5
  * Build a frozen record of gates from a spec of `{ name: [envKey, ...] }`.
@@ -14,7 +10,7 @@ export type Gates<S extends GateSpec> = { readonly [K in keyof S]: EnvGate }
14
10
  export const makeGates = <S extends GateSpec>(spec: S): Gates<S> => {
15
11
  const out = {} as Record<string, EnvGate>
16
12
  for (const [name, keys] of Object.entries(spec)) {
17
- out[name] = requireEnv(keys)
13
+ out[name] = envHelper.requireEnv(keys)
18
14
  }
19
15
  return Object.freeze(out) as Gates<S>
20
16
  }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './env.js'
2
2
  export * from './gates.js'
3
3
  export * from './fixtures.js'
4
+ export type * from './types.js'
package/src/types.ts ADDED
@@ -0,0 +1,37 @@
1
+ export interface EnvOk {
2
+ ok: true
3
+ }
4
+
5
+ export interface EnvSkip {
6
+ skip: true
7
+ reason: string
8
+ }
9
+
10
+ // Kept as a type: a union of the two gate outcomes.
11
+ export type EnvGate = EnvOk | EnvSkip
12
+
13
+ // Kept as a type: a record over arbitrary gate names (an implicit index signature).
14
+ export type GateSpec = Record<string, string[]>
15
+
16
+ // Kept as a type: mapped over the spec's gate names.
17
+ export type Gates<S extends GateSpec> = { readonly [K in keyof S]: EnvGate }
18
+
19
+ export interface LoadEnvOptions {
20
+ /** Re-read the file even when it was already loaded once in this process. */
21
+ force?: boolean
22
+ /** The file to read instead of `<monorepo-root>/.env`. */
23
+ file?: string
24
+ }
25
+
26
+ /** The test environment: `<monorepo-root>/.env` merged into `process.env`, and gates over it. */
27
+ export interface EnvHelper {
28
+ /**
29
+ * Locate `<monorepo-root>/.env`, parse it and merge it into `process.env` — a variable that is
30
+ * already set and non-empty wins. Idempotent per process unless `force` is passed.
31
+ */
32
+ loadEnv: (opts?: LoadEnvOptions) => void
33
+ /** Whether a variable is set to a non-empty value (after {@link EnvHelper.loadEnv}). */
34
+ hasEnv: (key: string) => boolean
35
+ /** `{ ok: true }` when every key is set, otherwise `{ skip: true, reason }` naming the missing ones. */
36
+ requireEnv: (keys: string[]) => EnvGate
37
+ }