@webpresso/agent-config 0.3.6 → 0.4.0

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
@@ -30,6 +30,10 @@ also re-exports the shared low-level primitives sourced from
30
30
  - `@webpresso/agent-config/stryker`
31
31
  - `@webpresso/agent-config/workers-test`
32
32
 
33
+ The Vitest surface includes stable group ordering, explicit staged-worker
34
+ selection, and root-aware Node project construction. Consumers keep ownership
35
+ of package-specific worker defaults, include patterns, aliases, and setup files.
36
+
33
37
  ## Relationship to other packages
34
38
 
35
39
  - `@webpresso/agent-config` — consumer-installed package surface
@@ -0,0 +1 @@
1
+ export * from "@webpresso/agent-core/automation-authoring";
@@ -0,0 +1,3 @@
1
+ // Consumer surface: re-export agent-core's sealed automation authoring contract
2
+ // so consumers depend on @webpresso/agent-config alone when preferred.
3
+ export * from "@webpresso/agent-core/automation-authoring";
@@ -0,0 +1,2 @@
1
+ export declare function resolveVitestGroupOrderFor(seed: string, offset?: number): number;
2
+ export declare function resolveVitestGroupOrder(offset?: number): number;
@@ -0,0 +1,17 @@
1
+ import { readConsumerPackageJson } from "./consumer-package.js";
2
+ function stableHash(input) {
3
+ let hash = 0;
4
+ for (const char of input) {
5
+ hash = (hash * 31 + char.charCodeAt(0)) % 1000;
6
+ }
7
+ return hash;
8
+ }
9
+ function consumerName() {
10
+ return readConsumerPackageJson()?.name ?? process.cwd();
11
+ }
12
+ export function resolveVitestGroupOrderFor(seed, offset = 0) {
13
+ return 1000 + stableHash(seed) + offset;
14
+ }
15
+ export function resolveVitestGroupOrder(offset = 0) {
16
+ return resolveVitestGroupOrderFor(consumerName(), offset);
17
+ }
@@ -16,7 +16,10 @@ export type ResolveAliasEntry = {
16
16
  find: string | RegExp;
17
17
  replacement: string;
18
18
  };
19
+ export declare function resolveStagedMaxWorkers(defaultMaxWorkers: number): number;
19
20
  export interface CreateNodeProjectsOptions {
21
+ /** Absolute package root. A verified-empty root omits the integration project. */
22
+ root?: string;
20
23
  unitInclude?: string[];
21
24
  unitExclude?: string[];
22
25
  integrationInclude?: string[];
@@ -24,6 +27,7 @@ export interface CreateNodeProjectsOptions {
24
27
  fileParallelism?: boolean;
25
28
  isolate?: boolean;
26
29
  testTimeout?: number;
30
+ hookTimeout?: number;
27
31
  /**
28
32
  * Extra resolve aliases appended (after the built-ins) to BOTH the unit and
29
33
  * integration projects. Lets a consumer inject repo-specific module rewrites
@@ -11,15 +11,19 @@
11
11
  * }))
12
12
  * ```
13
13
  */
14
- import { dirname, join } from "node:path";
14
+ import { globSync, readdirSync, statSync } from "node:fs";
15
+ import { dirname, isAbsolute, join } from "node:path";
15
16
  import { fileURLToPath } from "node:url";
16
17
  import { defineConfig } from "vite-plus/test/config";
17
18
  import { createFlakinessReporter } from "./flakiness-reporter.js";
18
19
  import { generatedRuntimeAliases } from "./generated-runtime-aliases.js";
19
- import { resolvedExecArgv, resolvedMaxWorkers, resolvedPool } from "./pool-defaults.js";
20
+ import { resolveVitestGroupOrder } from "./group-order.js";
21
+ import { parsePositiveInt, resolvedExecArgv, resolvedMaxWorkers, resolvedPool, } from "./pool-defaults.js";
20
22
  import { assertNonWorkersVitest4 } from "./version-guard.js";
21
23
  const __dirname = dirname(fileURLToPath(import.meta.url));
22
24
  const configDir = __dirname;
25
+ const groupOrder = resolveVitestGroupOrder();
26
+ const maxDiscoveryEntries = 10_000;
23
27
  assertNonWorkersVitest4({ caller: "nodeConfig" });
24
28
  // Route bun:sqlite → better-sqlite3 shim so Node-based vitest can load `@webpresso/agent-kit/blueprint`.
25
29
  const bunSqliteAlias = [
@@ -32,6 +36,46 @@ const bunSqliteAlias = [
32
36
  const webpressoInline = {
33
37
  deps: { inline: [/webpresso/] },
34
38
  };
39
+ export function resolveStagedMaxWorkers(defaultMaxWorkers) {
40
+ const forcedMaxWorkers = parsePositiveInt(process.env["VITEST_MAX_WORKERS"]);
41
+ if (forcedMaxWorkers !== undefined)
42
+ return forcedMaxWorkers;
43
+ return process.env["VP_RUN_CONCURRENCY_LIMIT"] ? 1 : defaultMaxWorkers;
44
+ }
45
+ function hasMatchingFiles(root, patterns) {
46
+ try {
47
+ if (!isAbsolute(root) || !statSync(root).isDirectory())
48
+ return true;
49
+ const matches = globSync(patterns, {
50
+ cwd: root,
51
+ exclude: ["**/.stryker-tmp/**", "**/node_modules/**"],
52
+ });
53
+ if (matches.length > 0)
54
+ return true;
55
+ // `globSync` can silently skip EACCES directories and report an empty
56
+ // result. Enumerate the same discoverable tree explicitly before treating
57
+ // emptiness as proof; Dirent checks avoid following symlink loops.
58
+ const pending = [root];
59
+ let discoveredEntries = 0;
60
+ while (pending.length > 0) {
61
+ const directory = pending.pop();
62
+ if (!directory)
63
+ continue;
64
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
65
+ discoveredEntries += 1;
66
+ if (discoveredEntries > maxDiscoveryEntries)
67
+ return true;
68
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".stryker-tmp") {
69
+ pending.push(join(directory, entry.name));
70
+ }
71
+ }
72
+ }
73
+ return false;
74
+ }
75
+ catch {
76
+ return true;
77
+ }
78
+ }
35
79
  /**
36
80
  * Create vitest projects for unit/integration test split.
37
81
  *
@@ -60,6 +104,7 @@ export function createNodeProjects(name, options = {}) {
60
104
  const projectFileParallelism = options.fileParallelism;
61
105
  const projectIsolate = options.isolate;
62
106
  const projectTestTimeout = options.testTimeout;
107
+ const projectHookTimeout = options.hookTimeout;
63
108
  const extraAlias = options.extraAlias ?? [];
64
109
  const extraSetupFiles = options.extraSetupFiles ?? [];
65
110
  const sharedResolve = {
@@ -69,13 +114,16 @@ export function createNodeProjects(name, options = {}) {
69
114
  const projectInline = {
70
115
  deps: { inline: [...webpressoInline.deps.inline, ...(options.extraInline ?? [])] },
71
116
  };
72
- return [
117
+ const projects = [
73
118
  {
74
119
  resolve: sharedResolve,
75
120
  server: projectInline,
76
121
  test: {
77
122
  server: projectInline,
78
123
  name: `${name}/unit`,
124
+ sequence: {
125
+ groupOrder,
126
+ },
79
127
  globals: true,
80
128
  restoreMocks: true,
81
129
  environment: "node",
@@ -84,6 +132,7 @@ export function createNodeProjects(name, options = {}) {
84
132
  fileParallelism: projectFileParallelism,
85
133
  isolate: projectIsolate,
86
134
  ...(projectTestTimeout !== undefined && { testTimeout: projectTestTimeout }),
135
+ ...(projectHookTimeout !== undefined && { hookTimeout: projectHookTimeout }),
87
136
  ...(extraSetupFiles.length > 0 && { setupFiles: [...extraSetupFiles] }),
88
137
  include: unitInclude,
89
138
  exclude: [
@@ -94,11 +143,16 @@ export function createNodeProjects(name, options = {}) {
94
143
  ],
95
144
  },
96
145
  },
97
- {
146
+ ];
147
+ if (!options.root || hasMatchingFiles(options.root, integrationInclude)) {
148
+ projects.push({
98
149
  resolve: sharedResolve,
99
150
  server: projectInline,
100
151
  test: {
101
152
  name: `${name}/integration`,
153
+ sequence: {
154
+ groupOrder: groupOrder + 1,
155
+ },
102
156
  globals: true,
103
157
  restoreMocks: true,
104
158
  environment: "node",
@@ -107,6 +161,7 @@ export function createNodeProjects(name, options = {}) {
107
161
  fileParallelism: projectFileParallelism,
108
162
  isolate: projectIsolate,
109
163
  ...(projectTestTimeout !== undefined && { testTimeout: projectTestTimeout }),
164
+ ...(projectHookTimeout !== undefined && { hookTimeout: projectHookTimeout }),
110
165
  execArgv: resolvedExecArgv,
111
166
  onConsoleLog: () => false,
112
167
  silent: process.env.VITEST_CONSOLE === "1" ? false : "passed-only",
@@ -116,8 +171,9 @@ export function createNodeProjects(name, options = {}) {
116
171
  reporters: ["default", createFlakinessReporter()],
117
172
  retry: process.env.CI ? 2 : 0,
118
173
  },
119
- },
120
- ];
174
+ });
175
+ }
176
+ return projects;
121
177
  }
122
178
  export const nodeConfig = defineConfig({
123
179
  resolve: {
@@ -126,6 +182,9 @@ export const nodeConfig = defineConfig({
126
182
  },
127
183
  server: webpressoInline,
128
184
  test: {
185
+ sequence: {
186
+ groupOrder,
187
+ },
129
188
  globals: true,
130
189
  restoreMocks: true,
131
190
  environment: "node",
@@ -4,6 +4,7 @@
4
4
  * Centralizes shared-runner detection, env var overrides, Stryker compatibility,
5
5
  * and execArgv logic used by node.ts, react.ts, and react-router.ts.
6
6
  */
7
+ export declare const parsePositiveInt: (value: string | undefined) => number | undefined;
7
8
  export declare const resolvedPool: string;
8
9
  export declare const resolvedMaxWorkers: number;
9
10
  export declare const resolvedMinWorkers: number;
@@ -13,11 +13,11 @@ const underSharedWorkspaceRunner = !!process.env.VP_RUN_CONCURRENCY_LIMIT;
13
13
  // inner fork workers causes frequent shutdown timeouts and EPIPE crashes on
14
14
  // large happy-dom / integration suites.
15
15
  const MAX_WORKERS = underSharedWorkspaceRunner ? 1 : Math.max(1, Math.floor(cpus().length * 0.75));
16
- const parsePositiveInt = (value) => {
17
- if (!value)
16
+ export const parsePositiveInt = (value) => {
17
+ if (!value || !/^\d+$/.test(value))
18
18
  return;
19
- const parsed = Number.parseInt(value, 10);
20
- return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
19
+ const parsed = Number(value);
20
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
21
21
  };
22
22
  const requestedPool = process.env.VITEST_POOL;
23
23
  const disableExecArgv = process.env.VITEST_DISABLE_EXEC_ARGV === "1";
@@ -20,9 +20,11 @@ import react from "@vitejs/plugin-react";
20
20
  import { defineConfig } from "vite-plus/test/config";
21
21
  import { createFlakinessReporter } from "./flakiness-reporter.js";
22
22
  import { generatedRuntimeAliases, generatedRuntimeDedupe } from "./generated-runtime-aliases.js";
23
+ import { resolveVitestGroupOrder } from "./group-order.js";
23
24
  import { resolvedExecArgv, resolvedMaxWorkers, resolvedMinWorkers, resolvedPool, } from "./pool-defaults.js";
24
25
  import { assertNonWorkersVitest4 } from "./version-guard.js";
25
26
  assertNonWorkersVitest4({ caller: "reactConfig" });
27
+ const groupOrder = resolveVitestGroupOrder();
26
28
  export const reactConfig = defineConfig({
27
29
  plugins: [react()],
28
30
  resolve: {
@@ -31,6 +33,9 @@ export const reactConfig = defineConfig({
31
33
  tsconfigPaths: true,
32
34
  },
33
35
  test: {
36
+ sequence: {
37
+ groupOrder,
38
+ },
34
39
  globals: true,
35
40
  restoreAllMocks: true,
36
41
  environment: "happy-dom",
@@ -6,6 +6,9 @@ export declare const workersConfig: {
6
6
  tsconfigPaths: boolean;
7
7
  };
8
8
  test: {
9
+ sequence: {
10
+ groupOrder: number;
11
+ };
9
12
  globals: boolean;
10
13
  restoreMocks: boolean;
11
14
  reporters: string[];
@@ -1,7 +1,9 @@
1
1
  import { defineConfig } from "vite-plus/test/config";
2
2
  import { createFlakinessReporter } from "./flakiness-reporter.js";
3
+ import { resolveVitestGroupOrder } from "./group-order.js";
3
4
  import { assertVitest4 } from "./version-guard.js";
4
5
  assertVitest4({ caller: "workersConfig" });
6
+ const groupOrder = resolveVitestGroupOrder();
5
7
  /**
6
8
  * Shared Vitest configuration for Cloudflare Workers
7
9
  */
@@ -10,6 +12,9 @@ export const workersConfig = defineConfig({
10
12
  tsconfigPaths: true,
11
13
  },
12
14
  test: {
15
+ sequence: {
16
+ groupOrder,
17
+ },
13
18
  globals: true,
14
19
  restoreMocks: true,
15
20
  reporters: ["default", createFlakinessReporter()],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpresso/agent-config",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "Shared vitest, tsconfig, stryker, and workers-test config for webpresso consumers. Binary-free — pure test/config tooling.",
6
6
  "homepage": "https://github.com/webpresso/agent-kit#readme",
@@ -22,6 +22,12 @@
22
22
  "type": "module",
23
23
  "sideEffects": false,
24
24
  "exports": {
25
+ "./automation-authoring": {
26
+ "import": {
27
+ "types": "./dist/esm/automation-authoring/index.d.ts",
28
+ "default": "./dist/esm/automation-authoring/index.js"
29
+ }
30
+ },
25
31
  "./deploy": {
26
32
  "import": {
27
33
  "types": "./dist/esm/deploy/index.d.ts",
@@ -100,6 +106,12 @@
100
106
  "default": "./dist/esm/vitest/flakiness-reporter.js"
101
107
  }
102
108
  },
109
+ "./vitest/group-order": {
110
+ "import": {
111
+ "types": "./dist/esm/vitest/group-order.d.ts",
112
+ "default": "./dist/esm/vitest/group-order.js"
113
+ }
114
+ },
103
115
  "./vitest/node": {
104
116
  "import": {
105
117
  "types": "./dist/esm/vitest/node.d.ts",
@@ -167,8 +179,8 @@
167
179
  },
168
180
  "dependencies": {
169
181
  "@vitejs/plugin-react": "^6.0.3",
170
- "@webpresso/agent-core": "0.1.3",
171
- "vite": "^8.1.4",
182
+ "@webpresso/agent-core": "0.1.4",
183
+ "vite": "^8.1.5",
172
184
  "vite-plus": "^0.2.4",
173
185
  "vitest": "^4.1.10"
174
186
  },
@@ -213,6 +225,7 @@
213
225
  "src/**/__fixtures__/**"
214
226
  ],
215
227
  "exports": {
228
+ "./automation-authoring": "./src/automation-authoring/index.ts",
216
229
  "./deploy": "./src/deploy/index.ts",
217
230
  "./dev": "./src/dev/index.ts",
218
231
  "./e2e": "./src/e2e/index.ts",
@@ -226,6 +239,7 @@
226
239
  "./tsconfig/react-library.json": "./src/tsconfig/react-library.json",
227
240
  "./tsconfig/react-router.json": "./src/tsconfig/react-router.json",
228
241
  "./vitest/flakiness-reporter": "./src/vitest/flakiness-reporter.ts",
242
+ "./vitest/group-order": "./src/vitest/group-order.ts",
229
243
  "./vitest/node": "./src/vitest/node.ts",
230
244
  "./vitest/node-source": "./src/vitest/node-source.ts",
231
245
  "./vitest/react": "./src/vitest/react.ts",