@elench/testkit 0.1.50 → 0.1.52
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 +73 -7
- package/lib/config/index.mjs +18 -2
- package/lib/runner/runtime-preparation.mjs +14 -0
- package/lib/runner/services.mjs +11 -1
- package/lib/runner/template-step-module-runner.mjs +25 -0
- package/lib/runner/template-steps.mjs +54 -45
- package/lib/setup/index.d.ts +60 -13
- package/lib/setup/index.mjs +54 -0
- package/lib/setup/index.test.mjs +117 -1
- package/lib/toolchains/index.mjs +565 -0
- package/lib/toolchains/index.test.mjs +168 -0
- package/lib/toolchains/semver.mjs +222 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,11 +65,14 @@ import {
|
|
|
65
65
|
commandStep,
|
|
66
66
|
defineTestkitSetup,
|
|
67
67
|
localDatabase,
|
|
68
|
-
moduleStep,
|
|
69
68
|
nextService,
|
|
69
|
+
nodeToolchain,
|
|
70
|
+
schemaSql,
|
|
71
|
+
seedCommand,
|
|
72
|
+
seededDatabaseTemplate,
|
|
70
73
|
service,
|
|
71
|
-
sqlFileStep,
|
|
72
74
|
tsxService,
|
|
75
|
+
verifyModule,
|
|
73
76
|
} from "@elench/testkit/setup";
|
|
74
77
|
|
|
75
78
|
export default defineTestkitSetup({
|
|
@@ -85,6 +88,13 @@ export default defineTestkitSetup({
|
|
|
85
88
|
cacheTtlSeconds: 900,
|
|
86
89
|
},
|
|
87
90
|
},
|
|
91
|
+
toolchains: {
|
|
92
|
+
frontendNode: nodeToolchain({
|
|
93
|
+
cwd: "frontend",
|
|
94
|
+
detect: "auto",
|
|
95
|
+
install: "download",
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
88
98
|
services: {
|
|
89
99
|
api: service({
|
|
90
100
|
...tsxService({
|
|
@@ -95,12 +105,12 @@ export default defineTestkitSetup({
|
|
|
95
105
|
}),
|
|
96
106
|
envFiles: [".env.testkit"],
|
|
97
107
|
database: localDatabase({
|
|
98
|
-
template: {
|
|
108
|
+
template: seededDatabaseTemplate({
|
|
99
109
|
inputs: ["db/schema.sql", "scripts/seed.ts"],
|
|
100
|
-
|
|
101
|
-
seed:
|
|
102
|
-
verify:
|
|
103
|
-
},
|
|
110
|
+
schema: schemaSql("db/schema.sql"),
|
|
111
|
+
seed: seedCommand("npm run db:seed"),
|
|
112
|
+
verify: verifyModule("src/testkit/verify-seed.ts#verifySeed"),
|
|
113
|
+
}),
|
|
104
114
|
}),
|
|
105
115
|
runtime: {
|
|
106
116
|
instances: 1,
|
|
@@ -130,6 +140,7 @@ export default defineTestkitSetup({
|
|
|
130
140
|
runtime: {
|
|
131
141
|
instances: 1,
|
|
132
142
|
maxConcurrentTasks: 2,
|
|
143
|
+
toolchain: "frontendNode",
|
|
133
144
|
prepare: {
|
|
134
145
|
inputs: ["frontend/src", "frontend/public", "frontend/package.json"],
|
|
135
146
|
steps: [commandStep("npm run build", { cwd: "frontend" })],
|
|
@@ -164,6 +175,7 @@ for:
|
|
|
164
175
|
- multi-service graphs
|
|
165
176
|
- local runtime instance counts
|
|
166
177
|
- per-runtime concurrent task caps
|
|
178
|
+
- repo-managed Node toolchains for prepare/start commands
|
|
167
179
|
- one-time runtime preparation steps for stable shared servers
|
|
168
180
|
- local DB binding configuration
|
|
169
181
|
- template database migrate / seed / verify stages
|
|
@@ -181,6 +193,60 @@ inputs, and writes cache state under the service runtime directory. This is the
|
|
|
181
193
|
right way to move expensive browser targets from `next dev` / watch mode to
|
|
182
194
|
stable build-and-start flows.
|
|
183
195
|
|
|
196
|
+
`database.template` is the database-side equivalent for reusable template DB
|
|
197
|
+
state. It always executes in three explicit phases:
|
|
198
|
+
|
|
199
|
+
- `migrate`
|
|
200
|
+
- `seed`
|
|
201
|
+
- `verify`
|
|
202
|
+
|
|
203
|
+
For most repos, prefer the intent-focused helpers:
|
|
204
|
+
|
|
205
|
+
- `schemaSql(...)`
|
|
206
|
+
- `seedCommand(...)`
|
|
207
|
+
- `seedModule(...)`
|
|
208
|
+
- `verifyCommand(...)`
|
|
209
|
+
- `verifyModule(...)`
|
|
210
|
+
- `seededDatabaseTemplate(...)`
|
|
211
|
+
|
|
212
|
+
Use raw `commandStep(...)`, `sqlFileStep(...)`, and `moduleStep(...)` arrays when
|
|
213
|
+
you need lower-level control over the exact stage layout.
|
|
214
|
+
|
|
215
|
+
`runtime.toolchain` is the first-class way to make those prepare/start commands
|
|
216
|
+
run under the correct Node toolchain instead of whatever `node`/`npm` happened
|
|
217
|
+
to launch `testkit`. Node toolchains support:
|
|
218
|
+
|
|
219
|
+
- host verification mode: `install: "require-host"`
|
|
220
|
+
- cached repo-local provisioning mode: `install: "download"`
|
|
221
|
+
- auto-detection from:
|
|
222
|
+
- `package.json#volta.node`
|
|
223
|
+
- `.nvmrc`
|
|
224
|
+
- `.node-version`
|
|
225
|
+
- `.tool-versions` (`nodejs`)
|
|
226
|
+
- `package.json#engines.node`
|
|
227
|
+
- `package.json#volta.npm`
|
|
228
|
+
- `package.json#packageManager`
|
|
229
|
+
- `package.json#engines.npm`
|
|
230
|
+
|
|
231
|
+
Example:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
toolchains: {
|
|
235
|
+
frontendNode: nodeToolchain({
|
|
236
|
+
cwd: "frontend",
|
|
237
|
+
detect: "auto",
|
|
238
|
+
install: "download",
|
|
239
|
+
}),
|
|
240
|
+
},
|
|
241
|
+
services: {
|
|
242
|
+
frontend: service({
|
|
243
|
+
runtime: {
|
|
244
|
+
toolchain: "frontendNode",
|
|
245
|
+
},
|
|
246
|
+
}),
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
184
250
|
If `reporting.knownFailuresFile` is configured, `testkit` enriches
|
|
185
251
|
`.testkit/results/latest.json` and `testkit.status.json` with:
|
|
186
252
|
|
package/lib/config/index.mjs
CHANGED
|
@@ -16,6 +16,10 @@ import {
|
|
|
16
16
|
normalizeRuntimeInstances,
|
|
17
17
|
} from "../runner/execution-config.mjs";
|
|
18
18
|
import { normalizeKnownFailureIssueValidationConfig } from "../known-failures/github.mjs";
|
|
19
|
+
import {
|
|
20
|
+
normalizeRuntimeToolchain,
|
|
21
|
+
normalizeToolchainRegistry,
|
|
22
|
+
} from "../toolchains/index.mjs";
|
|
19
23
|
|
|
20
24
|
const TESTKIT_K6_BIN = "TESTKIT_K6_BIN";
|
|
21
25
|
const DEFAULT_LOCAL_IMAGE = "pgvector/pgvector:pg16";
|
|
@@ -32,6 +36,7 @@ export async function loadConfigs(opts = {}) {
|
|
|
32
36
|
const { setup, setupFile } = await loadTestkitSetup(productDir);
|
|
33
37
|
const execution = normalizeRepoExecution(setup.execution);
|
|
34
38
|
const reporting = normalizeReportingConfig(setup.reporting);
|
|
39
|
+
const toolchains = normalizeToolchainRegistry(setup.toolchains);
|
|
35
40
|
const explicitServices = setup.services || {};
|
|
36
41
|
const discovery = discoverProject(productDir, explicitServices);
|
|
37
42
|
const serviceNames = new Set([
|
|
@@ -49,6 +54,7 @@ export async function loadConfigs(opts = {}) {
|
|
|
49
54
|
setupFile,
|
|
50
55
|
execution,
|
|
51
56
|
reporting,
|
|
57
|
+
toolchains,
|
|
52
58
|
explicitService: explicitServices[name] || {},
|
|
53
59
|
discoveredService: discovery.services[name] || null,
|
|
54
60
|
suites: discovery.suitesByService[name] || {},
|
|
@@ -109,6 +115,7 @@ function normalizeServiceConfig({
|
|
|
109
115
|
setupFile,
|
|
110
116
|
execution,
|
|
111
117
|
reporting,
|
|
118
|
+
toolchains,
|
|
112
119
|
explicitService,
|
|
113
120
|
discoveredService,
|
|
114
121
|
suites,
|
|
@@ -125,7 +132,7 @@ function normalizeServiceConfig({
|
|
|
125
132
|
);
|
|
126
133
|
}
|
|
127
134
|
const database = normalizeDatabaseConfig(explicitService, name);
|
|
128
|
-
const runtime = normalizeRuntimeConfig(explicitService.runtime, name);
|
|
135
|
+
const runtime = normalizeRuntimeConfig(explicitService.runtime, name, toolchains);
|
|
129
136
|
const skip = normalizeSkipConfig(explicitService.skip, {
|
|
130
137
|
name,
|
|
131
138
|
productDir,
|
|
@@ -274,7 +281,7 @@ function normalizeDatabaseConfig(explicitService, serviceName) {
|
|
|
274
281
|
};
|
|
275
282
|
}
|
|
276
283
|
|
|
277
|
-
function normalizeRuntimeConfig(value, serviceName) {
|
|
284
|
+
function normalizeRuntimeConfig(value, serviceName, toolchains) {
|
|
278
285
|
if (!value) {
|
|
279
286
|
return {
|
|
280
287
|
instances: 1,
|
|
@@ -283,6 +290,7 @@ function normalizeRuntimeConfig(value, serviceName) {
|
|
|
283
290
|
inputs: [],
|
|
284
291
|
steps: [],
|
|
285
292
|
},
|
|
293
|
+
toolchain: null,
|
|
286
294
|
};
|
|
287
295
|
}
|
|
288
296
|
|
|
@@ -296,6 +304,11 @@ function normalizeRuntimeConfig(value, serviceName) {
|
|
|
296
304
|
`Service "${serviceName}" runtime.maxConcurrentTasks`
|
|
297
305
|
),
|
|
298
306
|
prepare: normalizeRuntimePrepareConfig(value.prepare, serviceName),
|
|
307
|
+
toolchain: normalizeRuntimeToolchain(
|
|
308
|
+
value.toolchain,
|
|
309
|
+
`Service "${serviceName}" runtime.toolchain`,
|
|
310
|
+
toolchains || {}
|
|
311
|
+
),
|
|
299
312
|
};
|
|
300
313
|
}
|
|
301
314
|
|
|
@@ -739,6 +752,9 @@ function validateServiceConfig({
|
|
|
739
752
|
if (local?.cwd) {
|
|
740
753
|
ensureExistingPath(productDir, local.cwd, `Service "${name}" local.cwd`);
|
|
741
754
|
}
|
|
755
|
+
if (runtime.toolchain?.cwd) {
|
|
756
|
+
ensureExistingPath(productDir, runtime.toolchain.cwd, `Service "${name}" runtime.toolchain.cwd`);
|
|
757
|
+
}
|
|
742
758
|
for (const [stageName, steps] of Object.entries(database?.template || {})) {
|
|
743
759
|
if (stageName === "inputs") continue;
|
|
744
760
|
for (const step of steps || []) {
|
|
@@ -3,6 +3,7 @@ import fs from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { resolveServiceCwd } from "../config/index.mjs";
|
|
5
5
|
import { appendFileToHash, appendInputToHash } from "../database/fingerprint.mjs";
|
|
6
|
+
import { announceResolvedToolchain, resolveConfiguredToolchain } from "../toolchains/index.mjs";
|
|
6
7
|
import { readDatabaseUrl } from "./state-io.mjs";
|
|
7
8
|
import { buildExecutionEnv } from "./template.mjs";
|
|
8
9
|
import {
|
|
@@ -42,6 +43,7 @@ export async function prepareRuntimeService(config) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
try {
|
|
46
|
+
await announceResolvedToolchain(config, await resolveConfiguredToolchain(config));
|
|
45
47
|
await runConfiguredSteps({
|
|
46
48
|
config,
|
|
47
49
|
steps: prepare.steps,
|
|
@@ -61,10 +63,22 @@ export async function prepareRuntimeService(config) {
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
export async function computeRuntimePrepareFingerprint(config) {
|
|
66
|
+
const resolvedToolchain = await resolveConfiguredToolchain(config);
|
|
64
67
|
const hash = crypto.createHash("sha256");
|
|
65
68
|
hash.update(
|
|
66
69
|
JSON.stringify({
|
|
67
70
|
prepare: config.testkit.runtime.prepare || null,
|
|
71
|
+
toolchain: resolvedToolchain
|
|
72
|
+
? {
|
|
73
|
+
kind: resolvedToolchain.kind,
|
|
74
|
+
install: resolvedToolchain.install,
|
|
75
|
+
nodeVersion: resolvedToolchain.nodeVersion,
|
|
76
|
+
npmVersion: resolvedToolchain.npmVersion,
|
|
77
|
+
nodeSource: resolvedToolchain.nodeSource,
|
|
78
|
+
npmSource: resolvedToolchain.npmSource,
|
|
79
|
+
fingerprint: resolvedToolchain.fingerprint,
|
|
80
|
+
}
|
|
81
|
+
: null,
|
|
68
82
|
serviceEnv: config.testkit.serviceEnv || {},
|
|
69
83
|
local: config.testkit.local
|
|
70
84
|
? {
|
package/lib/runner/services.mjs
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { resolveServiceCwd } from "../config/index.mjs";
|
|
2
|
+
import {
|
|
3
|
+
announceResolvedToolchain,
|
|
4
|
+
applyToolchainEnv,
|
|
5
|
+
resolveConfiguredToolchain,
|
|
6
|
+
} from "../toolchains/index.mjs";
|
|
2
7
|
import { buildExecutionEnv, numericPortFromUrl } from "./template.mjs";
|
|
3
8
|
import { DEFAULT_READY_TIMEOUT_MS, assertLocalServicePortsAvailable, isPortInUse, waitForReady } from "./readiness.mjs";
|
|
4
9
|
import { killChildProcess, pipeOutput, startDetachedCommand, stopChildProcess, sleep } from "./processes.mjs";
|
|
@@ -23,7 +28,12 @@ export async function startLocalServices(runtimeConfigs, lifecycle) {
|
|
|
23
28
|
|
|
24
29
|
export async function startLocalService(config, lifecycle) {
|
|
25
30
|
const cwd = resolveServiceCwd(config.productDir, config.testkit.local.cwd);
|
|
26
|
-
const
|
|
31
|
+
const resolvedToolchain = await resolveConfiguredToolchain(config);
|
|
32
|
+
await announceResolvedToolchain(config, resolvedToolchain);
|
|
33
|
+
const env = applyToolchainEnv(
|
|
34
|
+
buildExecutionEnv(config, config.testkit.local.env, process.env),
|
|
35
|
+
resolvedToolchain
|
|
36
|
+
);
|
|
27
37
|
const port = config.testkit.local.port || numericPortFromUrl(config.testkit.local.baseUrl);
|
|
28
38
|
if (port) {
|
|
29
39
|
env.PORT = String(port);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { pathToFileURL } from "url";
|
|
3
|
+
|
|
4
|
+
const [, , moduleFile, exportName, contextFile] = process.argv;
|
|
5
|
+
|
|
6
|
+
if (!moduleFile || !exportName || !contextFile) {
|
|
7
|
+
console.error("Usage: node template-step-module-runner.mjs <module-file> <export-name> <context-file>");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const context = JSON.parse(fs.readFileSync(contextFile, "utf8"));
|
|
13
|
+
const moduleRef = await import(pathToFileURL(moduleFile).href);
|
|
14
|
+
const fn = moduleRef[exportName];
|
|
15
|
+
if (typeof fn !== "function") {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Template module step "${moduleFile}#${exportName}" did not export a function named "${exportName}"`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await fn(context);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error(error?.stack || error?.message || String(error));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
@@ -3,22 +3,35 @@ import fs from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { build } from "esbuild";
|
|
5
5
|
import { execa, execaCommand } from "execa";
|
|
6
|
-
import { fileURLToPath
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
7
|
import { resolveServiceCwd } from "../config/index.mjs";
|
|
8
|
+
import {
|
|
9
|
+
announceResolvedToolchain,
|
|
10
|
+
applyToolchainEnv,
|
|
11
|
+
resolveConfiguredToolchain,
|
|
12
|
+
} from "../toolchains/index.mjs";
|
|
8
13
|
|
|
9
14
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
10
15
|
const ROOT_ENTRY = path.join(PACKAGE_ROOT, "lib", "index.mjs");
|
|
11
16
|
const SETUP_ENTRY = path.join(PACKAGE_ROOT, "lib", "setup", "index.mjs");
|
|
12
17
|
const RUNTIME_ENTRY = path.join(PACKAGE_ROOT, "lib", "runtime", "index.mjs");
|
|
13
18
|
const KNOWN_FAILURES_ENTRY = path.join(PACKAGE_ROOT, "lib", "known-failures", "index.mjs");
|
|
19
|
+
const MODULE_RUNNER_ENTRY = path.join(
|
|
20
|
+
PACKAGE_ROOT,
|
|
21
|
+
"lib",
|
|
22
|
+
"runner",
|
|
23
|
+
"template-step-module-runner.mjs"
|
|
24
|
+
);
|
|
14
25
|
|
|
15
26
|
export async function runConfiguredSteps({ config, steps = [], env, labelPrefix }) {
|
|
16
27
|
if (steps.length === 0) return;
|
|
28
|
+
const resolvedToolchain = await resolveConfiguredToolchain(config);
|
|
29
|
+
await announceResolvedToolchain(config, resolvedToolchain);
|
|
17
30
|
|
|
18
31
|
for (const [index, step] of steps.entries()) {
|
|
19
32
|
const label = `${labelPrefix}:${config.name}:${index + 1}`;
|
|
20
33
|
console.log(`\n── ${label} ──`);
|
|
21
|
-
await runConfiguredStep(config, step, env);
|
|
34
|
+
await runConfiguredStep(config, step, env, resolvedToolchain);
|
|
22
35
|
}
|
|
23
36
|
}
|
|
24
37
|
|
|
@@ -51,11 +64,14 @@ export function resolveConfiguredPath(productDir, stepCwd, targetPath) {
|
|
|
51
64
|
return path.resolve(resolveConfiguredCwd(productDir, stepCwd), targetPath);
|
|
52
65
|
}
|
|
53
66
|
|
|
54
|
-
async function runConfiguredStep(config, step, env) {
|
|
67
|
+
async function runConfiguredStep(config, step, env, resolvedToolchain) {
|
|
68
|
+
const runtimeEnv = applyToolchainEnv(env, resolvedToolchain);
|
|
69
|
+
const cwd = resolveConfiguredCwd(config.productDir, step.cwd);
|
|
70
|
+
|
|
55
71
|
if (step.kind === "command") {
|
|
56
72
|
await execaCommand(step.cmd, {
|
|
57
|
-
cwd
|
|
58
|
-
env,
|
|
73
|
+
cwd,
|
|
74
|
+
env: runtimeEnv,
|
|
59
75
|
stdio: "inherit",
|
|
60
76
|
shell: true,
|
|
61
77
|
});
|
|
@@ -66,7 +82,7 @@ async function runConfiguredStep(config, step, env) {
|
|
|
66
82
|
await execa(
|
|
67
83
|
"psql",
|
|
68
84
|
[
|
|
69
|
-
|
|
85
|
+
runtimeEnv.DATABASE_URL,
|
|
70
86
|
"-v",
|
|
71
87
|
"ON_ERROR_STOP=1",
|
|
72
88
|
"-X",
|
|
@@ -74,8 +90,8 @@ async function runConfiguredStep(config, step, env) {
|
|
|
74
90
|
resolveConfiguredPath(config.productDir, step.cwd, step.path),
|
|
75
91
|
],
|
|
76
92
|
{
|
|
77
|
-
cwd
|
|
78
|
-
env,
|
|
93
|
+
cwd,
|
|
94
|
+
env: runtimeEnv,
|
|
79
95
|
stdio: "inherit",
|
|
80
96
|
}
|
|
81
97
|
);
|
|
@@ -83,34 +99,41 @@ async function runConfiguredStep(config, step, env) {
|
|
|
83
99
|
}
|
|
84
100
|
|
|
85
101
|
if (step.kind === "module") {
|
|
86
|
-
const
|
|
102
|
+
const bundledModule = await bundleConfiguredModule(config.productDir, step);
|
|
87
103
|
const { exportName } = parseModuleSpecifier(step.specifier);
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
const context = {
|
|
105
|
+
databaseUrl: runtimeEnv.DATABASE_URL || null,
|
|
106
|
+
productDir: config.productDir,
|
|
107
|
+
cwd,
|
|
108
|
+
serviceName: config.name,
|
|
109
|
+
env: { ...runtimeEnv },
|
|
110
|
+
runtimeId: config.runtimeId || null,
|
|
111
|
+
stateDir: config.stateDir,
|
|
112
|
+
prepareDir: config.testkit.prepareDir || null,
|
|
113
|
+
};
|
|
114
|
+
const contextPath = `${bundledModule.outputFile}.context.json`;
|
|
115
|
+
fs.writeFileSync(contextPath, JSON.stringify(context));
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await execa(
|
|
119
|
+
resolvedToolchain?.nodeExecutable || process.execPath,
|
|
120
|
+
[MODULE_RUNNER_ENTRY, bundledModule.outputFile, exportName, contextPath],
|
|
121
|
+
{
|
|
122
|
+
cwd,
|
|
123
|
+
env: runtimeEnv,
|
|
124
|
+
stdio: "inherit",
|
|
125
|
+
}
|
|
92
126
|
);
|
|
127
|
+
} finally {
|
|
128
|
+
fs.rmSync(contextPath, { force: true });
|
|
93
129
|
}
|
|
94
|
-
|
|
95
|
-
await withProcessContext(resolveConfiguredCwd(config.productDir, step.cwd), env, async () => {
|
|
96
|
-
await fn({
|
|
97
|
-
databaseUrl: env.DATABASE_URL || null,
|
|
98
|
-
productDir: config.productDir,
|
|
99
|
-
cwd: resolveConfiguredCwd(config.productDir, step.cwd),
|
|
100
|
-
serviceName: config.name,
|
|
101
|
-
env: { ...env },
|
|
102
|
-
runtimeId: config.runtimeId || null,
|
|
103
|
-
stateDir: config.stateDir,
|
|
104
|
-
prepareDir: config.testkit.prepareDir || null,
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
130
|
return;
|
|
108
131
|
}
|
|
109
132
|
|
|
110
133
|
throw new Error(`Unsupported template step kind "${step.kind}"`);
|
|
111
134
|
}
|
|
112
135
|
|
|
113
|
-
async function
|
|
136
|
+
async function bundleConfiguredModule(productDir, step) {
|
|
114
137
|
const { modulePath } = parseModuleSpecifier(step.specifier);
|
|
115
138
|
const absoluteModulePath = resolveConfiguredPath(productDir, step.cwd, modulePath);
|
|
116
139
|
const bundleDir = path.join(productDir, ".testkit", "_template-steps");
|
|
@@ -135,7 +158,10 @@ async function loadConfiguredModule(productDir, step) {
|
|
|
135
158
|
plugins: [testkitAliasPlugin()],
|
|
136
159
|
});
|
|
137
160
|
|
|
138
|
-
return
|
|
161
|
+
return {
|
|
162
|
+
outputFile,
|
|
163
|
+
cacheKey,
|
|
164
|
+
};
|
|
139
165
|
}
|
|
140
166
|
|
|
141
167
|
function buildModuleCacheKey(modulePath) {
|
|
@@ -172,20 +198,3 @@ function parseModuleSpecifier(specifier) {
|
|
|
172
198
|
exportName: exportName || "default",
|
|
173
199
|
};
|
|
174
200
|
}
|
|
175
|
-
|
|
176
|
-
async function withProcessContext(cwd, env, fn) {
|
|
177
|
-
const previousCwd = process.cwd();
|
|
178
|
-
const previousEnv = process.env;
|
|
179
|
-
process.chdir(cwd);
|
|
180
|
-
process.env = {
|
|
181
|
-
...previousEnv,
|
|
182
|
-
...env,
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
return await fn();
|
|
187
|
-
} finally {
|
|
188
|
-
process.chdir(previousCwd);
|
|
189
|
-
process.env = previousEnv;
|
|
190
|
-
}
|
|
191
|
-
}
|
package/lib/setup/index.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { AuthAdapter, HeaderBuilder, HttpSuiteConfig } from "../index";
|
|
2
2
|
|
|
3
|
-
export interface
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
export interface DatabaseTemplateConfig {
|
|
4
|
+
inputs?: string[];
|
|
5
|
+
migrate?: TemplateLifecycleStepConfig[];
|
|
6
|
+
seed?: TemplateLifecycleStepConfig[];
|
|
7
|
+
verify?: TemplateLifecycleStepConfig[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SeededDatabaseTemplateOptions {
|
|
11
|
+
inputs?: string[];
|
|
12
|
+
schema?: string | TemplateSqlFileStepConfig;
|
|
13
|
+
migrate?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[];
|
|
14
|
+
seed?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[];
|
|
15
|
+
verify?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export interface TemplateStepBaseConfig {
|
|
@@ -40,6 +40,16 @@ export type TemplateLifecycleStepConfig =
|
|
|
40
40
|
| TemplateSqlFileStepConfig
|
|
41
41
|
| TemplateModuleStepConfig;
|
|
42
42
|
|
|
43
|
+
export interface LocalDatabaseConfig {
|
|
44
|
+
provider: "local";
|
|
45
|
+
binding?: "shared" | "per-runtime";
|
|
46
|
+
image?: string;
|
|
47
|
+
password?: string;
|
|
48
|
+
reset?: boolean;
|
|
49
|
+
template?: DatabaseTemplateConfig;
|
|
50
|
+
user?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
export interface SkipFileRule {
|
|
44
54
|
path: string;
|
|
45
55
|
reason: string;
|
|
@@ -62,8 +72,20 @@ export interface RuntimeConfig {
|
|
|
62
72
|
inputs?: string[];
|
|
63
73
|
steps?: TemplateLifecycleStepConfig[];
|
|
64
74
|
};
|
|
75
|
+
toolchain?: string | NodeToolchainConfig;
|
|
65
76
|
}
|
|
66
77
|
|
|
78
|
+
export interface NodeToolchainConfig {
|
|
79
|
+
kind?: "node";
|
|
80
|
+
cwd?: string;
|
|
81
|
+
detect?: "auto" | "off";
|
|
82
|
+
install?: "require-host" | "download";
|
|
83
|
+
node?: string;
|
|
84
|
+
npm?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type ToolchainConfig = NodeToolchainConfig;
|
|
88
|
+
|
|
67
89
|
export interface SuiteRequirementRule {
|
|
68
90
|
selector: string;
|
|
69
91
|
locks?: string[];
|
|
@@ -124,6 +146,7 @@ export interface TestkitSetup {
|
|
|
124
146
|
issueValidation?: KnownFailureIssueValidationConfig;
|
|
125
147
|
};
|
|
126
148
|
services?: Record<string, ServiceConfig>;
|
|
149
|
+
toolchains?: Record<string, ToolchainConfig>;
|
|
127
150
|
telemetry?: {
|
|
128
151
|
enabled?: boolean;
|
|
129
152
|
endpoint?: string;
|
|
@@ -148,6 +171,30 @@ export declare function moduleStep(
|
|
|
148
171
|
specifier: string,
|
|
149
172
|
options?: Omit<TemplateModuleStepConfig, "kind" | "specifier">
|
|
150
173
|
): TemplateModuleStepConfig;
|
|
174
|
+
export declare function schemaSql(
|
|
175
|
+
filePath: string,
|
|
176
|
+
options?: Omit<TemplateSqlFileStepConfig, "kind" | "path">
|
|
177
|
+
): TemplateSqlFileStepConfig;
|
|
178
|
+
export declare function seedCommand(
|
|
179
|
+
cmd: string,
|
|
180
|
+
options?: Omit<TemplateCommandStepConfig, "kind" | "cmd">
|
|
181
|
+
): TemplateCommandStepConfig;
|
|
182
|
+
export declare function seedModule(
|
|
183
|
+
specifier: string,
|
|
184
|
+
options?: Omit<TemplateModuleStepConfig, "kind" | "specifier">
|
|
185
|
+
): TemplateModuleStepConfig;
|
|
186
|
+
export declare function verifyCommand(
|
|
187
|
+
cmd: string,
|
|
188
|
+
options?: Omit<TemplateCommandStepConfig, "kind" | "cmd">
|
|
189
|
+
): TemplateCommandStepConfig;
|
|
190
|
+
export declare function verifyModule(
|
|
191
|
+
specifier: string,
|
|
192
|
+
options?: Omit<TemplateModuleStepConfig, "kind" | "specifier">
|
|
193
|
+
): TemplateModuleStepConfig;
|
|
194
|
+
export declare function seededDatabaseTemplate(
|
|
195
|
+
options?: SeededDatabaseTemplateOptions
|
|
196
|
+
): DatabaseTemplateConfig;
|
|
197
|
+
export declare function nodeToolchain(options?: NodeToolchainConfig): NodeToolchainConfig;
|
|
151
198
|
export declare function goService(options: ServiceConfig["local"] & {
|
|
152
199
|
command?: string;
|
|
153
200
|
entrypoint?: string;
|
package/lib/setup/index.mjs
CHANGED
|
@@ -56,6 +56,47 @@ export function moduleStep(specifier, options = {}) {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
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
|
+
export function seededDatabaseTemplate(options = {}) {
|
|
80
|
+
const migrate = normalizeTemplateStepList(options.migrate);
|
|
81
|
+
const seed = normalizeTemplateStepList(options.seed);
|
|
82
|
+
const verify = normalizeTemplateStepList(options.verify);
|
|
83
|
+
const schema = normalizeSchemaStep(options.schema);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
inputs: Array.isArray(options.inputs) ? [...options.inputs] : undefined,
|
|
87
|
+
migrate: schema ? [schema, ...migrate] : migrate,
|
|
88
|
+
seed,
|
|
89
|
+
verify,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function nodeToolchain(options = {}) {
|
|
94
|
+
return {
|
|
95
|
+
kind: "node",
|
|
96
|
+
...options,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
59
100
|
export function goService(options = {}) {
|
|
60
101
|
const cwd = options.cwd || ".";
|
|
61
102
|
const port = requiredNumber(options.port, "goService port");
|
|
@@ -211,6 +252,19 @@ function requiredNumber(value, label) {
|
|
|
211
252
|
return value;
|
|
212
253
|
}
|
|
213
254
|
|
|
255
|
+
function normalizeTemplateStepList(value) {
|
|
256
|
+
if (value == null) return [];
|
|
257
|
+
return Array.isArray(value) ? [...value] : [value];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function normalizeSchemaStep(value) {
|
|
261
|
+
if (value == null) return null;
|
|
262
|
+
if (typeof value === "string") {
|
|
263
|
+
return sqlFileStep(value);
|
|
264
|
+
}
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
267
|
+
|
|
214
268
|
function envValue(name) {
|
|
215
269
|
const env = getRuntimeEnv();
|
|
216
270
|
const value = env?.rawEnv?.[name] || env?.[name];
|