@elench/testkit 0.1.76 → 0.1.77
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 +25 -29
- package/lib/config/runtime.test.mjs +2 -2
- package/lib/config-api/index.d.ts +37 -62
- package/lib/config-api/index.mjs +124 -102
- package/lib/config-api/index.test.mjs +65 -151
- package/lib/coverage/index.test.mjs +2 -2
- package/lib/shared/build-config.test.mjs +1 -1
- package/lib/shared/configured-steps.mjs +9 -7
- package/lib/shared/configured-steps.test.mjs +3 -3
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -147,14 +147,11 @@ Create `testkit.config.ts` at repo root:
|
|
|
147
147
|
|
|
148
148
|
```ts
|
|
149
149
|
import {
|
|
150
|
+
app,
|
|
151
|
+
database,
|
|
150
152
|
defineConfig,
|
|
151
153
|
defineFile,
|
|
152
|
-
|
|
153
|
-
nodeToolchain,
|
|
154
|
-
nodeApp,
|
|
155
|
-
seedCommand,
|
|
156
|
-
templateDatabase,
|
|
157
|
-
verifyModule,
|
|
154
|
+
toolchain,
|
|
158
155
|
} from "@elench/testkit/config";
|
|
159
156
|
|
|
160
157
|
export default defineConfig({
|
|
@@ -171,38 +168,41 @@ export default defineConfig({
|
|
|
171
168
|
},
|
|
172
169
|
},
|
|
173
170
|
toolchains: {
|
|
174
|
-
frontendNode:
|
|
171
|
+
frontendNode: toolchain.node({
|
|
175
172
|
cwd: "frontend",
|
|
176
173
|
detect: "auto",
|
|
177
174
|
install: "download",
|
|
178
175
|
}),
|
|
179
176
|
},
|
|
180
177
|
services: {
|
|
181
|
-
api:
|
|
178
|
+
api: app.node({
|
|
182
179
|
cwd: ".",
|
|
183
180
|
entry: "src/index.ts",
|
|
184
181
|
port: 3004,
|
|
185
182
|
envFiles: [".env.testkit"],
|
|
186
|
-
database:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
183
|
+
database: database.postgres({
|
|
184
|
+
template: {
|
|
185
|
+
inputs: ["db/schema.sql", "scripts/seed.ts"],
|
|
186
|
+
schema: "db/schema.sql",
|
|
187
|
+
seed: [{ kind: "command", run: "npm run db:seed" }],
|
|
188
|
+
verify: [{ kind: "module", target: "src/testkit/verify-seed.ts#verifySeed" }],
|
|
189
|
+
},
|
|
191
190
|
}),
|
|
192
191
|
runtime: {
|
|
193
192
|
instances: 1,
|
|
194
193
|
maxConcurrentTasks: 4,
|
|
195
194
|
},
|
|
196
195
|
}),
|
|
197
|
-
frontend:
|
|
196
|
+
frontend: app.next({
|
|
198
197
|
cwd: "frontend",
|
|
199
198
|
mode: "start",
|
|
200
199
|
port: 3000,
|
|
201
200
|
dependsOn: ["api"],
|
|
202
201
|
envFiles: ["frontend/.env.testkit"],
|
|
203
202
|
env: {
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
values: {
|
|
204
|
+
NEXT_PUBLIC_API_URL: "{baseUrl:api}",
|
|
205
|
+
},
|
|
206
206
|
},
|
|
207
207
|
runtime: {
|
|
208
208
|
instances: 1,
|
|
@@ -258,17 +258,13 @@ state. It always executes in three explicit phases:
|
|
|
258
258
|
- `seed`
|
|
259
259
|
- `verify`
|
|
260
260
|
|
|
261
|
-
For most repos, prefer
|
|
261
|
+
For most repos, prefer declarative step objects directly inside
|
|
262
|
+
`database.postgres({ template: ... })` and `runtime.prepare.steps`.
|
|
263
|
+
The supported shapes are:
|
|
262
264
|
|
|
263
|
-
- `
|
|
264
|
-
- `
|
|
265
|
-
- `
|
|
266
|
-
- `verifyCommand(...)`
|
|
267
|
-
- `verifyModule(...)`
|
|
268
|
-
- `templateDatabase(...)`
|
|
269
|
-
|
|
270
|
-
Use raw `commandStep(...)`, `sqlFileStep(...)`, and `moduleStep(...)` arrays when
|
|
271
|
-
you need lower-level control over the exact stage layout.
|
|
265
|
+
- `{ kind: "command", run: "..." }`
|
|
266
|
+
- `{ kind: "sql-file", path: "..." }`
|
|
267
|
+
- `{ kind: "module", target: "file.ts#exportName" }`
|
|
272
268
|
|
|
273
269
|
`runtime.toolchain` is the first-class way to make those prepare/start commands
|
|
274
270
|
run under the correct Node toolchain instead of whatever `node`/`npm` happened
|
|
@@ -290,14 +286,14 @@ Example:
|
|
|
290
286
|
|
|
291
287
|
```ts
|
|
292
288
|
toolchains: {
|
|
293
|
-
frontendNode:
|
|
289
|
+
frontendNode: toolchain.node({
|
|
294
290
|
cwd: "frontend",
|
|
295
291
|
detect: "auto",
|
|
296
292
|
install: "download",
|
|
297
293
|
}),
|
|
298
294
|
},
|
|
299
295
|
services: {
|
|
300
|
-
frontend:
|
|
296
|
+
frontend: app.next({
|
|
301
297
|
cwd: "frontend",
|
|
302
298
|
port: 3000,
|
|
303
299
|
runtime: {
|
|
@@ -508,7 +504,7 @@ Git metadata.
|
|
|
508
504
|
## Local Databases
|
|
509
505
|
|
|
510
506
|
`@elench/testkit` provisions Docker-managed local Postgres automatically for
|
|
511
|
-
services that define `database:
|
|
507
|
+
services that define `database: database.postgres(...)`.
|
|
512
508
|
|
|
513
509
|
- template databases are cached
|
|
514
510
|
- runtime databases are cloned from templates when binding is `per-runtime`
|
|
@@ -19,7 +19,7 @@ describe("config runtime helpers", () => {
|
|
|
19
19
|
normalizeRuntimePrepareConfig(
|
|
20
20
|
{
|
|
21
21
|
inputs: ["fixtures/users.json"],
|
|
22
|
-
steps: [{ kind: "module",
|
|
22
|
+
steps: [{ kind: "module", target: "./scripts/setup.mjs#run", inputs: ["fixtures/users.json"] }],
|
|
23
23
|
},
|
|
24
24
|
"web"
|
|
25
25
|
)
|
|
@@ -73,7 +73,7 @@ describe("config runtime helpers", () => {
|
|
|
73
73
|
|
|
74
74
|
it("rejects malformed lifecycle steps and empty step inputs", () => {
|
|
75
75
|
expect(() => normalizeTemplateLifecycleStep({ kind: "module" }, "runtime.prepare.steps[0]")).toThrow(
|
|
76
|
-
/
|
|
76
|
+
/target must be a non-empty string/
|
|
77
77
|
);
|
|
78
78
|
expect(() => normalizeTemplateStepInputs([""], "runtime.prepare.steps[0]")).toThrow(
|
|
79
79
|
/must be a non-empty string/
|
|
@@ -22,7 +22,7 @@ export interface TemplateStepBaseConfig {
|
|
|
22
22
|
|
|
23
23
|
export interface TemplateCommandStepConfig extends TemplateStepBaseConfig {
|
|
24
24
|
kind: "command";
|
|
25
|
-
|
|
25
|
+
run: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export interface TemplateSqlFileStepConfig extends TemplateStepBaseConfig {
|
|
@@ -32,7 +32,7 @@ export interface TemplateSqlFileStepConfig extends TemplateStepBaseConfig {
|
|
|
32
32
|
|
|
33
33
|
export interface TemplateModuleStepConfig extends TemplateStepBaseConfig {
|
|
34
34
|
kind: "module";
|
|
35
|
-
|
|
35
|
+
target: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export type TemplateLifecycleStepConfig =
|
|
@@ -177,18 +177,28 @@ export interface ServiceConfig {
|
|
|
177
177
|
skip?: SkipConfig;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
export interface DatabaseBindingEnvConfig {
|
|
181
|
+
prefix: string;
|
|
182
|
+
service: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface PresetEnvConfig {
|
|
186
|
+
values?: Record<string, string>;
|
|
187
|
+
databases?: Record<string, DatabaseBindingEnvConfig>;
|
|
188
|
+
}
|
|
189
|
+
|
|
180
190
|
export interface TestkitFileMetadata {
|
|
181
191
|
locks?: string[];
|
|
182
192
|
skip?: string | { reason: string };
|
|
183
193
|
}
|
|
184
194
|
|
|
185
|
-
export interface NodeAppOptions extends Omit<ServiceConfig, "local" | "runtime"> {
|
|
195
|
+
export interface NodeAppOptions extends Omit<ServiceConfig, "local" | "runtime" | "env"> {
|
|
186
196
|
baseUrl?: string;
|
|
187
197
|
build?: BuildConfig | null;
|
|
188
198
|
buildInputs?: string[];
|
|
189
199
|
cwd?: string;
|
|
190
200
|
entry?: string;
|
|
191
|
-
env?:
|
|
201
|
+
env?: PresetEnvConfig;
|
|
192
202
|
outDir?: string;
|
|
193
203
|
port: number;
|
|
194
204
|
readyPath?: string;
|
|
@@ -200,12 +210,12 @@ export interface NodeAppOptions extends Omit<ServiceConfig, "local" | "runtime">
|
|
|
200
210
|
tsconfig?: string;
|
|
201
211
|
}
|
|
202
212
|
|
|
203
|
-
export interface NextAppOptions extends Omit<ServiceConfig, "local" | "runtime"> {
|
|
213
|
+
export interface NextAppOptions extends Omit<ServiceConfig, "local" | "runtime" | "env"> {
|
|
204
214
|
baseUrl?: string;
|
|
205
215
|
build?: BuildConfig | null;
|
|
206
216
|
buildInputs?: string[];
|
|
207
217
|
cwd?: string;
|
|
208
|
-
env?:
|
|
218
|
+
env?: PresetEnvConfig;
|
|
209
219
|
mode?: "dev" | "start";
|
|
210
220
|
port: number;
|
|
211
221
|
readyTimeoutMs?: number;
|
|
@@ -238,62 +248,27 @@ export interface TestkitConfig {
|
|
|
238
248
|
export declare function defineConfig<T extends TestkitConfig>(config: T): T;
|
|
239
249
|
export declare function defineHttpProfile<T extends HttpSuiteConfig>(profile: T): T;
|
|
240
250
|
export declare function defineFile<T extends TestkitFileMetadata>(metadata: T): T;
|
|
241
|
-
export declare
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
):
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
options?:
|
|
261
|
-
|
|
262
|
-
export declare function seedModule(
|
|
263
|
-
specifier: string,
|
|
264
|
-
options?: Omit<TemplateModuleStepConfig, "kind" | "specifier">
|
|
265
|
-
): TemplateModuleStepConfig;
|
|
266
|
-
export declare function verifyCommand(
|
|
267
|
-
cmd: string,
|
|
268
|
-
options?: Omit<TemplateCommandStepConfig, "kind" | "cmd">
|
|
269
|
-
): TemplateCommandStepConfig;
|
|
270
|
-
export declare function verifyModule(
|
|
271
|
-
specifier: string,
|
|
272
|
-
options?: Omit<TemplateModuleStepConfig, "kind" | "specifier">
|
|
273
|
-
): TemplateModuleStepConfig;
|
|
274
|
-
export declare function templateDatabase(
|
|
275
|
-
options?: DatabaseTemplateOptions & Omit<LocalDatabaseConfig, "provider" | "template">
|
|
276
|
-
): LocalDatabaseConfig;
|
|
277
|
-
export declare function postgresFixture(
|
|
278
|
-
options?: Omit<LocalDatabaseConfig, "provider"> & {
|
|
279
|
-
discovery?: DiscoveryConfig;
|
|
280
|
-
envFiles?: string[];
|
|
281
|
-
}
|
|
282
|
-
): ServiceConfig;
|
|
283
|
-
export declare function databaseServiceEnv(
|
|
284
|
-
prefix: string,
|
|
285
|
-
serviceName: string
|
|
286
|
-
): Record<string, string>;
|
|
287
|
-
export declare function nodeToolchain(options?: NodeToolchainConfig): NodeToolchainConfig;
|
|
288
|
-
export declare function tscBuild(options?: Omit<TscBuildConfig, "kind">): TscBuildConfig;
|
|
289
|
-
export declare function scriptBuild(
|
|
290
|
-
script: string,
|
|
291
|
-
options?: Omit<ScriptBuildConfig, "kind" | "script">
|
|
292
|
-
): ScriptBuildConfig;
|
|
293
|
-
export declare function stepsBuild(options?: Omit<StepsBuildConfig, "kind">): StepsBuildConfig;
|
|
294
|
-
export declare function nextBuild(options?: Omit<NextBuildConfig, "kind">): NextBuildConfig;
|
|
295
|
-
export declare function nodeApp(options: NodeAppOptions): ServiceConfig;
|
|
296
|
-
export declare function nextApp(options: NextAppOptions): ServiceConfig;
|
|
251
|
+
export declare const app: {
|
|
252
|
+
node(options: NodeAppOptions): ServiceConfig;
|
|
253
|
+
next(options: NextAppOptions): ServiceConfig;
|
|
254
|
+
};
|
|
255
|
+
export declare const database: {
|
|
256
|
+
postgres(
|
|
257
|
+
options?: Omit<LocalDatabaseConfig, "provider" | "template"> & {
|
|
258
|
+
template?: DatabaseTemplateOptions;
|
|
259
|
+
}
|
|
260
|
+
): LocalDatabaseConfig;
|
|
261
|
+
fixture(
|
|
262
|
+
options?: Omit<LocalDatabaseConfig, "provider" | "template"> & {
|
|
263
|
+
template?: DatabaseTemplateOptions;
|
|
264
|
+
discovery?: DiscoveryConfig;
|
|
265
|
+
envFiles?: string[];
|
|
266
|
+
}
|
|
267
|
+
): ServiceConfig;
|
|
268
|
+
};
|
|
269
|
+
export declare const toolchain: {
|
|
270
|
+
node(options?: NodeToolchainConfig): NodeToolchainConfig;
|
|
271
|
+
};
|
|
297
272
|
export declare function clerkSessionProfile(options?: {
|
|
298
273
|
apiBase?: string;
|
|
299
274
|
needsAuth?: boolean;
|
package/lib/config-api/index.mjs
CHANGED
|
@@ -22,60 +22,13 @@ export function defineFile(metadata) {
|
|
|
22
22
|
return metadata || {};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
function postgresDatabase(options = {}) {
|
|
26
26
|
return {
|
|
27
27
|
provider: "local",
|
|
28
28
|
...options,
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export function commandStep(cmd, options = {}) {
|
|
33
|
-
return {
|
|
34
|
-
kind: "command",
|
|
35
|
-
cmd,
|
|
36
|
-
cwd: options.cwd,
|
|
37
|
-
inputs: Array.isArray(options.inputs) ? [...options.inputs] : undefined,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function sqlFileStep(filePath, options = {}) {
|
|
42
|
-
return {
|
|
43
|
-
kind: "sql-file",
|
|
44
|
-
path: filePath,
|
|
45
|
-
cwd: options.cwd,
|
|
46
|
-
inputs: Array.isArray(options.inputs) ? [...options.inputs] : undefined,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function moduleStep(specifier, options = {}) {
|
|
51
|
-
return {
|
|
52
|
-
kind: "module",
|
|
53
|
-
specifier,
|
|
54
|
-
cwd: options.cwd,
|
|
55
|
-
inputs: Array.isArray(options.inputs) ? [...options.inputs] : undefined,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function schemaSql(filePath, options = {}) {
|
|
60
|
-
return sqlFileStep(filePath, options);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function seedCommand(cmd, options = {}) {
|
|
64
|
-
return commandStep(cmd, options);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function seedModule(specifier, options = {}) {
|
|
68
|
-
return moduleStep(specifier, options);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function verifyCommand(cmd, options = {}) {
|
|
72
|
-
return commandStep(cmd, options);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function verifyModule(specifier, options = {}) {
|
|
76
|
-
return moduleStep(specifier, options);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
32
|
function buildDatabaseTemplateConfig(options = {}) {
|
|
80
33
|
const migrate = normalizeTemplateStepList(options.migrate);
|
|
81
34
|
const seed = normalizeTemplateStepList(options.seed);
|
|
@@ -90,60 +43,40 @@ function buildDatabaseTemplateConfig(options = {}) {
|
|
|
90
43
|
};
|
|
91
44
|
}
|
|
92
45
|
|
|
93
|
-
|
|
94
|
-
const {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
return postgresDatabase({
|
|
103
|
-
...databaseOptions,
|
|
104
|
-
template: buildDatabaseTemplateConfig(options.template || { inputs, schema, migrate, seed, verify }),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function postgresFixture(options = {}) {
|
|
109
|
-
const { discovery, envFiles, ...databaseOptions } = options;
|
|
46
|
+
function postgresFixture(options = {}) {
|
|
47
|
+
const { discovery, envFiles, template, ...databaseOptions } = options;
|
|
48
|
+
for (const legacyKey of ["inputs", "schema", "migrate", "seed", "verify"]) {
|
|
49
|
+
if (Object.prototype.hasOwnProperty.call(options, legacyKey)) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`database.fixture(...) no longer accepts top-level "${legacyKey}". Move lifecycle config under database.fixture({ template: { ... } }).`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
110
55
|
return {
|
|
111
56
|
discovery: discovery || {
|
|
112
57
|
roots: [".testkit-fixture"],
|
|
113
58
|
},
|
|
114
59
|
envFiles,
|
|
115
60
|
local: false,
|
|
116
|
-
database: postgresDatabase(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
if (!serviceName || !String(serviceName).trim()) {
|
|
126
|
-
throw new Error("databaseServiceEnv serviceName must be a non-empty string");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
[`${normalizedPrefix}_DATABASE_HOST`]: `{dbHost:${serviceName}}`,
|
|
131
|
-
[`${normalizedPrefix}_DATABASE_PORT`]: `{dbPort:${serviceName}}`,
|
|
132
|
-
[`${normalizedPrefix}_DATABASE_NAME`]: `{dbName:${serviceName}}`,
|
|
133
|
-
[`${normalizedPrefix}_DATABASE_USER`]: `{dbUser:${serviceName}}`,
|
|
134
|
-
[`${normalizedPrefix}_DATABASE_PASSWORD`]: `{dbPassword:${serviceName}}`,
|
|
135
|
-
[`${normalizedPrefix}_DATABASE_SSL`]: "0",
|
|
61
|
+
database: postgresDatabase(
|
|
62
|
+
template
|
|
63
|
+
? {
|
|
64
|
+
...databaseOptions,
|
|
65
|
+
template: buildDatabaseTemplateConfig(template),
|
|
66
|
+
}
|
|
67
|
+
: databaseOptions
|
|
68
|
+
),
|
|
136
69
|
};
|
|
137
70
|
}
|
|
138
71
|
|
|
139
|
-
|
|
72
|
+
function nodeToolchain(options = {}) {
|
|
140
73
|
return {
|
|
141
74
|
kind: "node",
|
|
142
75
|
...options,
|
|
143
76
|
};
|
|
144
77
|
}
|
|
145
78
|
|
|
146
|
-
|
|
79
|
+
function tscBuild(options = {}) {
|
|
147
80
|
return {
|
|
148
81
|
kind: "tsc",
|
|
149
82
|
cwd: options.cwd,
|
|
@@ -154,7 +87,7 @@ export function tscBuild(options = {}) {
|
|
|
154
87
|
};
|
|
155
88
|
}
|
|
156
89
|
|
|
157
|
-
|
|
90
|
+
function scriptBuild(script, options = {}) {
|
|
158
91
|
return {
|
|
159
92
|
kind: "script",
|
|
160
93
|
script,
|
|
@@ -163,7 +96,7 @@ export function scriptBuild(script, options = {}) {
|
|
|
163
96
|
};
|
|
164
97
|
}
|
|
165
98
|
|
|
166
|
-
|
|
99
|
+
function stepsBuild(options = {}) {
|
|
167
100
|
return {
|
|
168
101
|
kind: "steps",
|
|
169
102
|
inputs: Array.isArray(options.inputs) ? [...options.inputs] : undefined,
|
|
@@ -171,7 +104,7 @@ export function stepsBuild(options = {}) {
|
|
|
171
104
|
};
|
|
172
105
|
}
|
|
173
106
|
|
|
174
|
-
|
|
107
|
+
function nextBuild(options = {}) {
|
|
175
108
|
return {
|
|
176
109
|
kind: "next",
|
|
177
110
|
cwd: options.cwd,
|
|
@@ -181,14 +114,14 @@ export function nextBuild(options = {}) {
|
|
|
181
114
|
};
|
|
182
115
|
}
|
|
183
116
|
|
|
184
|
-
|
|
117
|
+
function nodeApp(options = {}) {
|
|
185
118
|
const {
|
|
186
119
|
baseUrl: explicitBaseUrl,
|
|
187
120
|
build: explicitBuild,
|
|
188
121
|
buildInputs,
|
|
189
122
|
cwd = ".",
|
|
190
123
|
entry = "src/index.ts",
|
|
191
|
-
env
|
|
124
|
+
env,
|
|
192
125
|
envFiles,
|
|
193
126
|
outDir = "dist",
|
|
194
127
|
port,
|
|
@@ -202,7 +135,8 @@ export function nodeApp(options = {}) {
|
|
|
202
135
|
...serviceConfig
|
|
203
136
|
} = options;
|
|
204
137
|
|
|
205
|
-
const normalizedPort = requiredNumber(port, "
|
|
138
|
+
const normalizedPort = requiredNumber(port, "app.node port");
|
|
139
|
+
const normalizedEnv = normalizePresetEnv(env);
|
|
206
140
|
const baseUrl = explicitBaseUrl || "http://127.0.0.1:{port}";
|
|
207
141
|
const build = explicitBuild === undefined ? tscBuild({
|
|
208
142
|
cwd,
|
|
@@ -228,12 +162,12 @@ export function nodeApp(options = {}) {
|
|
|
228
162
|
baseUrl,
|
|
229
163
|
readyUrl: explicitReadyUrl || `${baseUrl}${readyPath}`,
|
|
230
164
|
readyTimeoutMs,
|
|
231
|
-
env,
|
|
165
|
+
env: normalizedEnv,
|
|
232
166
|
},
|
|
233
167
|
};
|
|
234
168
|
}
|
|
235
169
|
|
|
236
|
-
|
|
170
|
+
function nextApp(options = {}) {
|
|
237
171
|
const {
|
|
238
172
|
baseUrl: explicitBaseUrl,
|
|
239
173
|
browser,
|
|
@@ -241,7 +175,7 @@ export function nextApp(options = {}) {
|
|
|
241
175
|
buildInputs,
|
|
242
176
|
cwd = ".",
|
|
243
177
|
dependsOn,
|
|
244
|
-
env
|
|
178
|
+
env,
|
|
245
179
|
envFiles,
|
|
246
180
|
mode = "dev",
|
|
247
181
|
port,
|
|
@@ -253,7 +187,8 @@ export function nextApp(options = {}) {
|
|
|
253
187
|
...serviceConfig
|
|
254
188
|
} = options;
|
|
255
189
|
|
|
256
|
-
const normalizedPort = requiredNumber(port, "
|
|
190
|
+
const normalizedPort = requiredNumber(port, "app.next port");
|
|
191
|
+
const normalizedEnv = normalizePresetEnv(env);
|
|
257
192
|
const baseUrl = explicitBaseUrl || "http://127.0.0.1:{port}";
|
|
258
193
|
const build =
|
|
259
194
|
explicitBuild === undefined
|
|
@@ -286,15 +221,47 @@ export function nextApp(options = {}) {
|
|
|
286
221
|
readyTimeoutMs,
|
|
287
222
|
env: mode === "start"
|
|
288
223
|
? {
|
|
289
|
-
NEXT_DIST_DIR:
|
|
290
|
-
NEXT_TSCONFIG_PATH:
|
|
291
|
-
|
|
224
|
+
NEXT_DIST_DIR: normalizedEnv.NEXT_DIST_DIR || ".next-testkit/{runtimeId}/dist",
|
|
225
|
+
NEXT_TSCONFIG_PATH:
|
|
226
|
+
normalizedEnv.NEXT_TSCONFIG_PATH || ".next-testkit/{runtimeId}/tsconfig.json",
|
|
227
|
+
...normalizedEnv,
|
|
292
228
|
}
|
|
293
|
-
:
|
|
229
|
+
: normalizedEnv,
|
|
294
230
|
},
|
|
295
231
|
};
|
|
296
232
|
}
|
|
297
233
|
|
|
234
|
+
export const app = {
|
|
235
|
+
node: nodeApp,
|
|
236
|
+
next: nextApp,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export const database = {
|
|
240
|
+
postgres(options = {}) {
|
|
241
|
+
const { template, ...databaseOptions } = options;
|
|
242
|
+
for (const legacyKey of ["inputs", "schema", "migrate", "seed", "verify"]) {
|
|
243
|
+
if (Object.prototype.hasOwnProperty.call(options, legacyKey)) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
`database.postgres(...) no longer accepts top-level "${legacyKey}". Move lifecycle config under database.postgres({ template: { ... } }).`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return postgresDatabase(
|
|
250
|
+
template
|
|
251
|
+
? {
|
|
252
|
+
...databaseOptions,
|
|
253
|
+
template: buildDatabaseTemplateConfig(template),
|
|
254
|
+
}
|
|
255
|
+
: databaseOptions
|
|
256
|
+
);
|
|
257
|
+
},
|
|
258
|
+
fixture: postgresFixture,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
export const toolchain = {
|
|
262
|
+
node: nodeToolchain,
|
|
263
|
+
};
|
|
264
|
+
|
|
298
265
|
export function clerkSessionProfile(options = {}) {
|
|
299
266
|
const apiBase = options.apiBase || "https://api.clerk.com/v1";
|
|
300
267
|
const secretKeyEnv = options.secretKeyEnv || "CLERK_SECRET_KEY";
|
|
@@ -381,6 +348,58 @@ function requiredNumber(value, label) {
|
|
|
381
348
|
return value;
|
|
382
349
|
}
|
|
383
350
|
|
|
351
|
+
function normalizePresetEnv(env) {
|
|
352
|
+
if (!env) return {};
|
|
353
|
+
if (typeof env !== "object" || Array.isArray(env)) {
|
|
354
|
+
throw new Error("Preset env must be an object");
|
|
355
|
+
}
|
|
356
|
+
const allowedKeys = new Set(["values", "databases"]);
|
|
357
|
+
const unexpectedKeys = Object.keys(env).filter((key) => !allowedKeys.has(key));
|
|
358
|
+
if (unexpectedKeys.length > 0) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
`Preset env only supports "values" and "databases". Received unexpected key(s): ${unexpectedKeys.join(", ")}`
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const values = env.values && typeof env.values === "object" && !Array.isArray(env.values) ? env.values : {};
|
|
365
|
+
const databases =
|
|
366
|
+
env.databases && typeof env.databases === "object" && !Array.isArray(env.databases) ? env.databases : {};
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
...expandDatabaseBindings(databases),
|
|
370
|
+
...values,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function expandDatabaseBindings(bindings) {
|
|
375
|
+
const env = {};
|
|
376
|
+
for (const [name, binding] of Object.entries(bindings || {})) {
|
|
377
|
+
if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
|
|
378
|
+
throw new Error(`env.databases.${name} must be an object`);
|
|
379
|
+
}
|
|
380
|
+
const prefix = normalizeDatabaseEnvToken(binding.prefix, `env.databases.${name}.prefix`);
|
|
381
|
+
const serviceName = normalizeDatabaseEnvToken(binding.service, `env.databases.${name}.service`, false);
|
|
382
|
+
env[`${prefix}_DATABASE_HOST`] = `{dbHost:${serviceName}}`;
|
|
383
|
+
env[`${prefix}_DATABASE_PORT`] = `{dbPort:${serviceName}}`;
|
|
384
|
+
env[`${prefix}_DATABASE_NAME`] = `{dbName:${serviceName}}`;
|
|
385
|
+
env[`${prefix}_DATABASE_USER`] = `{dbUser:${serviceName}}`;
|
|
386
|
+
env[`${prefix}_DATABASE_PASSWORD`] = `{dbPassword:${serviceName}}`;
|
|
387
|
+
env[`${prefix}_DATABASE_SSL`] = "0";
|
|
388
|
+
}
|
|
389
|
+
return env;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function normalizeDatabaseEnvToken(value, label, sanitize = true) {
|
|
393
|
+
const raw = String(value || "").trim();
|
|
394
|
+
const normalized = sanitize
|
|
395
|
+
? raw.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
|
|
396
|
+
: raw;
|
|
397
|
+
if (!normalized) {
|
|
398
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
399
|
+
}
|
|
400
|
+
return normalized;
|
|
401
|
+
}
|
|
402
|
+
|
|
384
403
|
function resolveNodeAppStart(build, entry) {
|
|
385
404
|
if (build?.kind === "tsc") {
|
|
386
405
|
const compiled = compiledEntryFromSource(entry || build.entry || "src/index.ts", build.outDir || "dist");
|
|
@@ -397,7 +416,10 @@ function normalizeTemplateStepList(value) {
|
|
|
397
416
|
function normalizeSchemaStep(value) {
|
|
398
417
|
if (value == null) return null;
|
|
399
418
|
if (typeof value === "string") {
|
|
400
|
-
return
|
|
419
|
+
return {
|
|
420
|
+
kind: "sql-file",
|
|
421
|
+
path: value,
|
|
422
|
+
};
|
|
401
423
|
}
|
|
402
424
|
return value;
|
|
403
425
|
}
|
|
@@ -1,26 +1,14 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
app,
|
|
4
|
+
database,
|
|
4
5
|
defineConfig,
|
|
5
6
|
defineFile,
|
|
6
7
|
defineHttpProfile,
|
|
7
|
-
|
|
8
|
-
nextBuild,
|
|
9
|
-
nodeToolchain,
|
|
10
|
-
nodeApp,
|
|
11
|
-
postgresDatabase,
|
|
12
|
-
postgresFixture,
|
|
13
|
-
schemaSql,
|
|
14
|
-
seedCommand,
|
|
15
|
-
seedModule,
|
|
16
|
-
stepsBuild,
|
|
17
|
-
templateDatabase,
|
|
18
|
-
tscBuild,
|
|
19
|
-
verifyCommand,
|
|
20
|
-
verifyModule,
|
|
8
|
+
toolchain,
|
|
21
9
|
} from "./index.mjs";
|
|
22
10
|
|
|
23
|
-
describe("config
|
|
11
|
+
describe("config api", () => {
|
|
24
12
|
it("defines repo config plainly", () => {
|
|
25
13
|
expect(defineConfig({ execution: { workers: 4 } })).toEqual({
|
|
26
14
|
execution: { workers: 4 },
|
|
@@ -40,13 +28,13 @@ describe("config helpers", () => {
|
|
|
40
28
|
});
|
|
41
29
|
|
|
42
30
|
it("builds a Next app preset for dev mode", () => {
|
|
43
|
-
const config =
|
|
31
|
+
const config = app.next({ port: 3000 });
|
|
44
32
|
|
|
45
33
|
expect(config.local.start).toBe("./node_modules/.bin/next dev -p {port}");
|
|
46
34
|
});
|
|
47
35
|
|
|
48
36
|
it("builds a Next app preset for start mode with managed runtime env defaults", () => {
|
|
49
|
-
const config =
|
|
37
|
+
const config = app.next({ cwd: "frontend", port: 3000, mode: "start" });
|
|
50
38
|
|
|
51
39
|
expect(config.local.start).toBe("./node_modules/.bin/next start --port {port}");
|
|
52
40
|
expect(config.local.env).toMatchObject({
|
|
@@ -63,7 +51,7 @@ describe("config helpers", () => {
|
|
|
63
51
|
});
|
|
64
52
|
|
|
65
53
|
it("allows Next start apps to disable managed builds explicitly", () => {
|
|
66
|
-
const config =
|
|
54
|
+
const config = app.next({ cwd: "frontend", port: 3000, mode: "start", build: null });
|
|
67
55
|
|
|
68
56
|
expect(config.runtime.build).toBeNull();
|
|
69
57
|
expect(config.local.env).toMatchObject({
|
|
@@ -73,7 +61,7 @@ describe("config helpers", () => {
|
|
|
73
61
|
});
|
|
74
62
|
|
|
75
63
|
it("builds a Node app preset with tsc build defaults", () => {
|
|
76
|
-
const config =
|
|
64
|
+
const config = app.node({ port: 3000, entry: "src/server.ts" });
|
|
77
65
|
|
|
78
66
|
expect(config.local.start).toBe("node {prepareDir}/dist/server.js");
|
|
79
67
|
expect(config.runtime.build).toEqual({
|
|
@@ -87,88 +75,22 @@ describe("config helpers", () => {
|
|
|
87
75
|
});
|
|
88
76
|
|
|
89
77
|
it("builds node toolchain profiles with a node kind", () => {
|
|
90
|
-
expect(
|
|
78
|
+
expect(toolchain.node({ node: "20.19.5", install: "download" })).toEqual({
|
|
91
79
|
kind: "node",
|
|
92
80
|
node: "20.19.5",
|
|
93
81
|
install: "download",
|
|
94
82
|
});
|
|
95
83
|
});
|
|
96
84
|
|
|
97
|
-
it("builds
|
|
98
|
-
expect(tscBuild({ entry: "src/server.ts", outDir: "build" })).toEqual({
|
|
99
|
-
kind: "tsc",
|
|
100
|
-
cwd: undefined,
|
|
101
|
-
entry: "src/server.ts",
|
|
102
|
-
tsconfig: "tsconfig.json",
|
|
103
|
-
outDir: "build",
|
|
104
|
-
inputs: undefined,
|
|
105
|
-
});
|
|
106
|
-
expect(nextBuild({ cwd: "frontend" })).toEqual({
|
|
107
|
-
kind: "next",
|
|
108
|
-
cwd: "frontend",
|
|
109
|
-
distDir: "dist",
|
|
110
|
-
tsconfig: "tsconfig.json",
|
|
111
|
-
inputs: undefined,
|
|
112
|
-
});
|
|
85
|
+
it("builds declarative postgres database templates from plain objects", () => {
|
|
113
86
|
expect(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
inputs: ["scripts/prepare.mjs"],
|
|
121
|
-
steps: [
|
|
122
|
-
{
|
|
123
|
-
kind: "command",
|
|
124
|
-
cmd: "node scripts/prepare.mjs",
|
|
125
|
-
cwd: undefined,
|
|
126
|
-
inputs: undefined,
|
|
87
|
+
database.postgres({
|
|
88
|
+
template: {
|
|
89
|
+
inputs: ["db/schema.sql", "scripts/seed.ts"],
|
|
90
|
+
schema: "db/schema.sql",
|
|
91
|
+
seed: { kind: "command", run: "npm run db:seed" },
|
|
92
|
+
verify: { kind: "module", target: "scripts/verify.ts#verifySeed" },
|
|
127
93
|
},
|
|
128
|
-
],
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("emits semantic database template steps using the underlying step shapes", () => {
|
|
133
|
-
expect(schemaSql("db/schema.sql")).toEqual({
|
|
134
|
-
kind: "sql-file",
|
|
135
|
-
path: "db/schema.sql",
|
|
136
|
-
cwd: undefined,
|
|
137
|
-
inputs: undefined,
|
|
138
|
-
});
|
|
139
|
-
expect(seedCommand("npm run db:seed")).toEqual({
|
|
140
|
-
kind: "command",
|
|
141
|
-
cmd: "npm run db:seed",
|
|
142
|
-
cwd: undefined,
|
|
143
|
-
inputs: undefined,
|
|
144
|
-
});
|
|
145
|
-
expect(seedModule("scripts/seed.ts#seed")).toEqual({
|
|
146
|
-
kind: "module",
|
|
147
|
-
specifier: "scripts/seed.ts#seed",
|
|
148
|
-
cwd: undefined,
|
|
149
|
-
inputs: undefined,
|
|
150
|
-
});
|
|
151
|
-
expect(verifyCommand("npm run db:verify")).toEqual({
|
|
152
|
-
kind: "command",
|
|
153
|
-
cmd: "npm run db:verify",
|
|
154
|
-
cwd: undefined,
|
|
155
|
-
inputs: undefined,
|
|
156
|
-
});
|
|
157
|
-
expect(verifyModule("scripts/verify.ts#verify")).toEqual({
|
|
158
|
-
kind: "module",
|
|
159
|
-
specifier: "scripts/verify.ts#verify",
|
|
160
|
-
cwd: undefined,
|
|
161
|
-
inputs: undefined,
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("builds declarative template databases from schema, seed, and verify intents", () => {
|
|
166
|
-
expect(
|
|
167
|
-
templateDatabase({
|
|
168
|
-
inputs: ["db/schema.sql", "scripts/seed.ts"],
|
|
169
|
-
schema: "db/schema.sql",
|
|
170
|
-
seed: seedCommand("npm run db:seed"),
|
|
171
|
-
verify: verifyModule("scripts/verify.ts#verifySeed"),
|
|
172
94
|
})
|
|
173
95
|
).toEqual({
|
|
174
96
|
provider: "local",
|
|
@@ -178,24 +100,18 @@ describe("config helpers", () => {
|
|
|
178
100
|
{
|
|
179
101
|
kind: "sql-file",
|
|
180
102
|
path: "db/schema.sql",
|
|
181
|
-
cwd: undefined,
|
|
182
|
-
inputs: undefined,
|
|
183
103
|
},
|
|
184
104
|
],
|
|
185
105
|
seed: [
|
|
186
106
|
{
|
|
187
107
|
kind: "command",
|
|
188
|
-
|
|
189
|
-
cwd: undefined,
|
|
190
|
-
inputs: undefined,
|
|
108
|
+
run: "npm run db:seed",
|
|
191
109
|
},
|
|
192
110
|
],
|
|
193
111
|
verify: [
|
|
194
112
|
{
|
|
195
113
|
kind: "module",
|
|
196
|
-
|
|
197
|
-
cwd: undefined,
|
|
198
|
-
inputs: undefined,
|
|
114
|
+
target: "scripts/verify.ts#verifySeed",
|
|
199
115
|
},
|
|
200
116
|
],
|
|
201
117
|
},
|
|
@@ -204,9 +120,11 @@ describe("config helpers", () => {
|
|
|
204
120
|
|
|
205
121
|
it("prepends schema before explicit migrate steps and normalizes singletons to arrays", () => {
|
|
206
122
|
expect(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
123
|
+
database.postgres({
|
|
124
|
+
template: {
|
|
125
|
+
schema: { kind: "sql-file", path: "db/schema.sql", cwd: "db" },
|
|
126
|
+
migrate: { kind: "command", run: "echo migrate" },
|
|
127
|
+
},
|
|
210
128
|
})
|
|
211
129
|
).toEqual({
|
|
212
130
|
provider: "local",
|
|
@@ -217,13 +135,10 @@ describe("config helpers", () => {
|
|
|
217
135
|
kind: "sql-file",
|
|
218
136
|
path: "db/schema.sql",
|
|
219
137
|
cwd: "db",
|
|
220
|
-
inputs: undefined,
|
|
221
138
|
},
|
|
222
139
|
{
|
|
223
140
|
kind: "command",
|
|
224
|
-
|
|
225
|
-
cwd: undefined,
|
|
226
|
-
inputs: undefined,
|
|
141
|
+
run: "echo migrate",
|
|
227
142
|
},
|
|
228
143
|
],
|
|
229
144
|
seed: [],
|
|
@@ -232,46 +147,9 @@ describe("config helpers", () => {
|
|
|
232
147
|
});
|
|
233
148
|
});
|
|
234
149
|
|
|
235
|
-
it("builds
|
|
236
|
-
expect(postgresDatabase({ reset: false })).toEqual({
|
|
237
|
-
provider: "local",
|
|
238
|
-
reset: false,
|
|
239
|
-
});
|
|
150
|
+
it("builds support database presets and expands database env bindings declaratively", () => {
|
|
240
151
|
expect(
|
|
241
|
-
|
|
242
|
-
reset: true,
|
|
243
|
-
schema: "db/schema.sql",
|
|
244
|
-
seed: seedCommand("npm run db:seed"),
|
|
245
|
-
})
|
|
246
|
-
).toEqual({
|
|
247
|
-
provider: "local",
|
|
248
|
-
reset: true,
|
|
249
|
-
template: {
|
|
250
|
-
inputs: undefined,
|
|
251
|
-
migrate: [
|
|
252
|
-
{
|
|
253
|
-
kind: "sql-file",
|
|
254
|
-
path: "db/schema.sql",
|
|
255
|
-
cwd: undefined,
|
|
256
|
-
inputs: undefined,
|
|
257
|
-
},
|
|
258
|
-
],
|
|
259
|
-
seed: [
|
|
260
|
-
{
|
|
261
|
-
kind: "command",
|
|
262
|
-
cmd: "npm run db:seed",
|
|
263
|
-
cwd: undefined,
|
|
264
|
-
inputs: undefined,
|
|
265
|
-
},
|
|
266
|
-
],
|
|
267
|
-
verify: [],
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
it("builds support database presets and env bindings", () => {
|
|
273
|
-
expect(
|
|
274
|
-
postgresFixture({
|
|
152
|
+
database.fixture({
|
|
275
153
|
reset: true,
|
|
276
154
|
})
|
|
277
155
|
).toEqual({
|
|
@@ -285,7 +163,19 @@ describe("config helpers", () => {
|
|
|
285
163
|
reset: true,
|
|
286
164
|
},
|
|
287
165
|
});
|
|
288
|
-
|
|
166
|
+
|
|
167
|
+
const config = app.node({
|
|
168
|
+
port: 3000,
|
|
169
|
+
env: {
|
|
170
|
+
values: { API_KEY: "test" },
|
|
171
|
+
databases: {
|
|
172
|
+
onix: { service: "catalog", prefix: "ONIX" },
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(config.local.env).toEqual({
|
|
178
|
+
API_KEY: "test",
|
|
289
179
|
ONIX_DATABASE_HOST: "{dbHost:catalog}",
|
|
290
180
|
ONIX_DATABASE_PORT: "{dbPort:catalog}",
|
|
291
181
|
ONIX_DATABASE_NAME: "{dbName:catalog}",
|
|
@@ -295,12 +185,25 @@ describe("config helpers", () => {
|
|
|
295
185
|
});
|
|
296
186
|
});
|
|
297
187
|
|
|
188
|
+
it("rejects top-level database template lifecycle fields", () => {
|
|
189
|
+
expect(() =>
|
|
190
|
+
database.postgres({
|
|
191
|
+
schema: "db/schema.sql",
|
|
192
|
+
})
|
|
193
|
+
).toThrow(/no longer accepts top-level "schema"/);
|
|
194
|
+
expect(() =>
|
|
195
|
+
database.fixture({
|
|
196
|
+
seed: { kind: "command", run: "npm run db:seed" },
|
|
197
|
+
})
|
|
198
|
+
).toThrow(/no longer accepts top-level "seed"/);
|
|
199
|
+
});
|
|
200
|
+
|
|
298
201
|
it("does not leak preset-only helper fields into node app configs", () => {
|
|
299
|
-
const config =
|
|
202
|
+
const config = app.node({
|
|
300
203
|
port: 3000,
|
|
301
204
|
entry: "src/server.ts",
|
|
302
205
|
buildInputs: ["src", "package.json"],
|
|
303
|
-
env: { API_KEY: "test" },
|
|
206
|
+
env: { values: { API_KEY: "test" } },
|
|
304
207
|
readyPath: "/live",
|
|
305
208
|
});
|
|
306
209
|
|
|
@@ -310,4 +213,15 @@ describe("config helpers", () => {
|
|
|
310
213
|
expect(config.local.env).toEqual({ API_KEY: "test" });
|
|
311
214
|
expect(config.local.readyUrl).toBe("http://127.0.0.1:{port}/live");
|
|
312
215
|
});
|
|
216
|
+
|
|
217
|
+
it("rejects flat preset env maps in favor of env.values and env.databases", () => {
|
|
218
|
+
expect(() =>
|
|
219
|
+
app.node({
|
|
220
|
+
port: 3000,
|
|
221
|
+
env: {
|
|
222
|
+
API_KEY: "test",
|
|
223
|
+
},
|
|
224
|
+
})
|
|
225
|
+
).toThrow(/Preset env only supports "values" and "databases"/);
|
|
226
|
+
});
|
|
313
227
|
});
|
|
@@ -19,11 +19,11 @@ describe("coverage graph builder", () => {
|
|
|
19
19
|
productDir,
|
|
20
20
|
"testkit.config.ts",
|
|
21
21
|
`
|
|
22
|
-
import {
|
|
22
|
+
import { app, defineConfig } from "@elench/testkit/config";
|
|
23
23
|
|
|
24
24
|
export default defineConfig({
|
|
25
25
|
services: {
|
|
26
|
-
web:
|
|
26
|
+
web: app.next({
|
|
27
27
|
cwd: ".",
|
|
28
28
|
start: "node server.js",
|
|
29
29
|
mode: "start",
|
|
@@ -77,7 +77,7 @@ describe("shared build config helpers", () => {
|
|
|
77
77
|
{
|
|
78
78
|
kind: "steps",
|
|
79
79
|
inputs: ["scripts/prepare.mjs"],
|
|
80
|
-
steps: [{ kind: "command",
|
|
80
|
+
steps: [{ kind: "command", run: "node scripts/prepare.mjs" }],
|
|
81
81
|
},
|
|
82
82
|
"runtime.build"
|
|
83
83
|
)
|
|
@@ -53,8 +53,8 @@ export function normalizeConfiguredStep(step, label) {
|
|
|
53
53
|
|
|
54
54
|
const kind = normalizeOptionalString(step.kind);
|
|
55
55
|
if (kind === "command") {
|
|
56
|
-
const cmd = normalizeOptionalString(step.
|
|
57
|
-
if (!cmd) throw new Error(`${label}.
|
|
56
|
+
const cmd = normalizeOptionalString(step.run);
|
|
57
|
+
if (!cmd) throw new Error(`${label}.run must be a non-empty string`);
|
|
58
58
|
return {
|
|
59
59
|
kind,
|
|
60
60
|
cmd,
|
|
@@ -73,8 +73,8 @@ export function normalizeConfiguredStep(step, label) {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
if (kind === "module") {
|
|
76
|
-
const specifier = normalizeOptionalString(step.
|
|
77
|
-
if (!specifier) throw new Error(`${label}.
|
|
76
|
+
const specifier = normalizeOptionalString(step.target);
|
|
77
|
+
if (!specifier) throw new Error(`${label}.target must be a non-empty string`);
|
|
78
78
|
return {
|
|
79
79
|
kind,
|
|
80
80
|
specifier,
|
|
@@ -118,7 +118,8 @@ export function collectConfiguredInputs(productDir, { inputs = [], steps = [] }
|
|
|
118
118
|
collected.add(resolveConfiguredPath(productDir, step.cwd, step.path));
|
|
119
119
|
}
|
|
120
120
|
if (step.kind === "module") {
|
|
121
|
-
const
|
|
121
|
+
const moduleRef = step.specifier || step.target;
|
|
122
|
+
const { modulePath } = parseModuleSpecifier(moduleRef);
|
|
122
123
|
const candidatePath = resolveConfiguredPath(productDir, step.cwd, modulePath);
|
|
123
124
|
if (modulePath.startsWith("@elench/testkit") || fs.existsSync(candidatePath)) {
|
|
124
125
|
if (!modulePath.startsWith("@elench/testkit")) {
|
|
@@ -138,7 +139,7 @@ export function collectConfiguredInputs(productDir, { inputs = [], steps = [] }
|
|
|
138
139
|
export function summarizeConfiguredStep(step) {
|
|
139
140
|
if (step.kind === "command") return `command: ${String(step.cmd).trim()}`;
|
|
140
141
|
if (step.kind === "sql-file") return `sql: ${step.path}`;
|
|
141
|
-
if (step.kind === "module") return `module: ${step.specifier}`;
|
|
142
|
+
if (step.kind === "module") return `module: ${step.specifier || step.target}`;
|
|
142
143
|
return step.kind;
|
|
143
144
|
}
|
|
144
145
|
|
|
@@ -174,7 +175,8 @@ export function validateConfiguredCollection({ productDir, label, inputs = [], s
|
|
|
174
175
|
ensureExistingPath(resolveConfiguredPath(productDir, step.cwd, step.path), `${label} sql file`);
|
|
175
176
|
}
|
|
176
177
|
if (step.kind === "module") {
|
|
177
|
-
const
|
|
178
|
+
const moduleRef = step.specifier || step.target;
|
|
179
|
+
const { modulePath } = parseModuleSpecifier(moduleRef);
|
|
178
180
|
const candidatePath = resolveConfiguredPath(productDir, step.cwd, modulePath);
|
|
179
181
|
if (modulePath.startsWith("@elench/testkit")) {
|
|
180
182
|
// Internal testkit module steps are resolved by the runtime alias map.
|
|
@@ -22,7 +22,7 @@ afterEach(() => {
|
|
|
22
22
|
describe("shared configured steps", () => {
|
|
23
23
|
it("normalizes and finalizes configured steps", () => {
|
|
24
24
|
const normalized = normalizeConfiguredStep(
|
|
25
|
-
{ kind: "module",
|
|
25
|
+
{ kind: "module", target: "./seed.mjs#run", cwd: "db", inputs: ["schema.sql"] },
|
|
26
26
|
'Service "api" database.template.seed[0]'
|
|
27
27
|
);
|
|
28
28
|
|
|
@@ -55,7 +55,7 @@ describe("shared configured steps", () => {
|
|
|
55
55
|
inputs: ["db/schema.sql"],
|
|
56
56
|
steps: [
|
|
57
57
|
{ kind: "sql-file", path: "schema.sql", cwd: "db", inputs: [] },
|
|
58
|
-
{ kind: "module",
|
|
58
|
+
{ kind: "module", target: "./seed.mjs#run", cwd: "db", inputs: [] },
|
|
59
59
|
],
|
|
60
60
|
};
|
|
61
61
|
|
|
@@ -82,7 +82,7 @@ describe("shared configured steps", () => {
|
|
|
82
82
|
steps: [
|
|
83
83
|
{
|
|
84
84
|
kind: "module",
|
|
85
|
-
|
|
85
|
+
target: "@elench/testkit/config/next-runtime-tsconfig#writeNextRuntimeTsconfig",
|
|
86
86
|
cwd: "frontend",
|
|
87
87
|
inputs: [],
|
|
88
88
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.77",
|
|
4
4
|
"description": "Browser bridge helpers for testkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@elench/testkit-protocol": "0.1.
|
|
25
|
+
"@elench/testkit-protocol": "0.1.77"
|
|
26
26
|
},
|
|
27
27
|
"private": false
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.77",
|
|
4
4
|
"description": "CLI for discovering and running local HTTP, DAL, and Playwright test suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -81,10 +81,10 @@
|
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"@babel/code-frame": "^7.29.0",
|
|
84
|
-
"@elench/next-analysis": "0.1.
|
|
85
|
-
"@elench/testkit-bridge": "0.1.
|
|
86
|
-
"@elench/testkit-protocol": "0.1.
|
|
87
|
-
"@elench/ts-analysis": "0.1.
|
|
84
|
+
"@elench/next-analysis": "0.1.77",
|
|
85
|
+
"@elench/testkit-bridge": "0.1.77",
|
|
86
|
+
"@elench/testkit-protocol": "0.1.77",
|
|
87
|
+
"@elench/ts-analysis": "0.1.77",
|
|
88
88
|
"@oclif/core": "^4.10.6",
|
|
89
89
|
"esbuild": "^0.25.11",
|
|
90
90
|
"execa": "^9.5.0",
|