@elench/testkit 0.1.67 → 0.1.69
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/lib/runner/template-steps.mjs +28 -1
- package/lib/setup/index.d.ts +11 -2
- package/lib/setup/index.mjs +20 -3
- package/lib/setup/index.test.mjs +30 -2
- package/lib/setup/next-runtime-tsconfig.mjs +43 -0
- package/lib/setup/next-runtime-tsconfig.test.mjs +57 -0
- package/lib/shared/build-config.mjs +77 -13
- package/lib/shared/build-config.test.mjs +52 -1
- package/lib/shared/configured-steps.mjs +24 -5
- package/lib/shared/configured-steps.test.mjs +29 -0
- 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
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import { createRequire } from "module";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import { build } from "esbuild";
|
|
5
6
|
import { execa, execaCommand } from "execa";
|
|
6
7
|
import { fileURLToPath } from "url";
|
|
7
8
|
import {
|
|
8
9
|
collectConfiguredInputs,
|
|
10
|
+
isBareModuleSpecifier,
|
|
9
11
|
parseModuleSpecifier,
|
|
10
12
|
resolveConfiguredCwd,
|
|
11
13
|
resolveConfiguredPath,
|
|
@@ -21,6 +23,12 @@ import {
|
|
|
21
23
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
22
24
|
const ROOT_ENTRY = path.join(PACKAGE_ROOT, "lib", "index.mjs");
|
|
23
25
|
const SETUP_ENTRY = path.join(PACKAGE_ROOT, "lib", "setup", "index.mjs");
|
|
26
|
+
const SETUP_NEXT_TSCONFIG_ENTRY = path.join(
|
|
27
|
+
PACKAGE_ROOT,
|
|
28
|
+
"lib",
|
|
29
|
+
"setup",
|
|
30
|
+
"next-runtime-tsconfig.mjs"
|
|
31
|
+
);
|
|
24
32
|
const RUNTIME_ENTRY = path.join(PACKAGE_ROOT, "lib", "runtime", "index.mjs");
|
|
25
33
|
const KNOWN_FAILURES_ENTRY = path.join(PACKAGE_ROOT, "lib", "known-failures", "index.mjs");
|
|
26
34
|
const MODULE_RUNNER_ENTRY = path.join(
|
|
@@ -175,7 +183,7 @@ async function runConfiguredStep(config, step, env, resolvedToolchain, options =
|
|
|
175
183
|
|
|
176
184
|
async function bundleConfiguredModule(productDir, step) {
|
|
177
185
|
const { modulePath } = parseModuleSpecifier(step.specifier);
|
|
178
|
-
const absoluteModulePath =
|
|
186
|
+
const absoluteModulePath = resolveConfiguredModuleEntry(productDir, step);
|
|
179
187
|
const bundleDir = path.join(productDir, ".testkit", "_template-steps");
|
|
180
188
|
fs.mkdirSync(bundleDir, { recursive: true });
|
|
181
189
|
|
|
@@ -204,6 +212,24 @@ async function bundleConfiguredModule(productDir, step) {
|
|
|
204
212
|
};
|
|
205
213
|
}
|
|
206
214
|
|
|
215
|
+
function resolveConfiguredModuleEntry(productDir, step) {
|
|
216
|
+
const { modulePath } = parseModuleSpecifier(step.specifier);
|
|
217
|
+
if (modulePath.startsWith("@elench/testkit")) {
|
|
218
|
+
return resolvePackageSubpath(modulePath);
|
|
219
|
+
}
|
|
220
|
+
const candidatePath = resolveConfiguredPath(productDir, step.cwd, modulePath);
|
|
221
|
+
if (fs.existsSync(candidatePath)) {
|
|
222
|
+
return candidatePath;
|
|
223
|
+
}
|
|
224
|
+
if (!isBareModuleSpecifier(modulePath)) {
|
|
225
|
+
return candidatePath;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const cwd = resolveConfiguredCwd(productDir, step.cwd);
|
|
229
|
+
const requireFromCwd = createRequire(path.join(cwd, "__testkit__.cjs"));
|
|
230
|
+
return requireFromCwd.resolve(modulePath);
|
|
231
|
+
}
|
|
232
|
+
|
|
207
233
|
function buildModuleCacheKey(modulePath) {
|
|
208
234
|
const content = fs.readFileSync(modulePath, "utf8");
|
|
209
235
|
return crypto.createHash("sha256").update(modulePath).update("\0").update(content).digest("hex");
|
|
@@ -225,6 +251,7 @@ function resolvePackageSubpath(specifier) {
|
|
|
225
251
|
const subpath = specifier.slice("@elench/testkit".length);
|
|
226
252
|
if (!subpath) return ROOT_ENTRY;
|
|
227
253
|
if (subpath === "/setup") return SETUP_ENTRY;
|
|
254
|
+
if (subpath === "/setup/next-runtime-tsconfig") return SETUP_NEXT_TSCONFIG_ENTRY;
|
|
228
255
|
if (subpath === "/runtime") return RUNTIME_ENTRY;
|
|
229
256
|
if (subpath === "/known-failures") return KNOWN_FAILURES_ENTRY;
|
|
230
257
|
|
package/lib/setup/index.d.ts
CHANGED
|
@@ -56,13 +56,21 @@ export interface ScriptBuildConfig {
|
|
|
56
56
|
script: string;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
export interface NextBuildConfig {
|
|
60
|
+
kind: "next";
|
|
61
|
+
cwd?: string;
|
|
62
|
+
distDir?: string;
|
|
63
|
+
inputs?: string[];
|
|
64
|
+
tsconfig?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
59
67
|
export interface StepsBuildConfig {
|
|
60
68
|
kind: "steps";
|
|
61
69
|
inputs?: string[];
|
|
62
70
|
steps?: TemplateLifecycleStepConfig[];
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
export type BuildConfig = TscBuildConfig | ScriptBuildConfig | StepsBuildConfig;
|
|
73
|
+
export type BuildConfig = TscBuildConfig | ScriptBuildConfig | NextBuildConfig | StepsBuildConfig;
|
|
66
74
|
|
|
67
75
|
export interface LocalDatabaseConfig {
|
|
68
76
|
provider: "local";
|
|
@@ -151,6 +159,7 @@ export interface ServiceConfig {
|
|
|
151
159
|
databaseFrom?: string;
|
|
152
160
|
dependsOn?: string[];
|
|
153
161
|
discovery?: DiscoveryConfig;
|
|
162
|
+
env?: Record<string, string>;
|
|
154
163
|
envFile?: string;
|
|
155
164
|
envFiles?: string[];
|
|
156
165
|
browser?: BrowserServiceConfig;
|
|
@@ -282,7 +291,7 @@ export declare function scriptBuild(
|
|
|
282
291
|
options?: Omit<ScriptBuildConfig, "kind" | "script">
|
|
283
292
|
): ScriptBuildConfig;
|
|
284
293
|
export declare function stepsBuild(options?: Omit<StepsBuildConfig, "kind">): StepsBuildConfig;
|
|
285
|
-
export declare function nextBuild(options?: Omit<
|
|
294
|
+
export declare function nextBuild(options?: Omit<NextBuildConfig, "kind">): NextBuildConfig;
|
|
286
295
|
export declare function nodeApp(options: NodeAppOptions): ServiceConfig;
|
|
287
296
|
export declare function nextApp(options: NextAppOptions): ServiceConfig;
|
|
288
297
|
export declare function clerkSessionProfile(options?: {
|
package/lib/setup/index.mjs
CHANGED
|
@@ -172,7 +172,13 @@ export function stepsBuild(options = {}) {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
export function nextBuild(options = {}) {
|
|
175
|
-
return
|
|
175
|
+
return {
|
|
176
|
+
kind: "next",
|
|
177
|
+
cwd: options.cwd,
|
|
178
|
+
distDir: options.distDir || "dist",
|
|
179
|
+
tsconfig: options.tsconfig || "tsconfig.json",
|
|
180
|
+
inputs: Array.isArray(options.inputs) ? [...options.inputs] : undefined,
|
|
181
|
+
};
|
|
176
182
|
}
|
|
177
183
|
|
|
178
184
|
export function nodeApp(options = {}) {
|
|
@@ -249,7 +255,12 @@ export function nextApp(options = {}) {
|
|
|
249
255
|
|
|
250
256
|
const normalizedPort = requiredNumber(port, "nextApp port");
|
|
251
257
|
const baseUrl = explicitBaseUrl || "http://127.0.0.1:{port}";
|
|
252
|
-
const build =
|
|
258
|
+
const build =
|
|
259
|
+
explicitBuild === undefined
|
|
260
|
+
? mode === "start"
|
|
261
|
+
? nextBuild({ cwd, inputs: buildInputs })
|
|
262
|
+
: null
|
|
263
|
+
: explicitBuild;
|
|
253
264
|
const start =
|
|
254
265
|
explicitStart ||
|
|
255
266
|
(mode === "start"
|
|
@@ -273,7 +284,13 @@ export function nextApp(options = {}) {
|
|
|
273
284
|
baseUrl,
|
|
274
285
|
readyUrl: explicitReadyUrl || baseUrl,
|
|
275
286
|
readyTimeoutMs,
|
|
276
|
-
env
|
|
287
|
+
env: mode === "start"
|
|
288
|
+
? {
|
|
289
|
+
NEXT_DIST_DIR: env.NEXT_DIST_DIR || "{prepareDir}/dist",
|
|
290
|
+
NEXT_TSCONFIG_PATH: env.NEXT_TSCONFIG_PATH || "{prepareDir}/tsconfig.json",
|
|
291
|
+
...env,
|
|
292
|
+
}
|
|
293
|
+
: env,
|
|
277
294
|
},
|
|
278
295
|
};
|
|
279
296
|
}
|
package/lib/setup/index.test.mjs
CHANGED
|
@@ -32,6 +32,33 @@ describe("setup helpers", () => {
|
|
|
32
32
|
expect(config.local.start).toBe("./node_modules/.bin/next dev -p {port}");
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
it("builds a Next app preset for start mode with managed runtime env defaults", () => {
|
|
36
|
+
const config = nextApp({ cwd: "frontend", port: 3000, mode: "start" });
|
|
37
|
+
|
|
38
|
+
expect(config.local.start).toBe("./node_modules/.bin/next start --port {port}");
|
|
39
|
+
expect(config.local.env).toMatchObject({
|
|
40
|
+
NEXT_DIST_DIR: "{prepareDir}/dist",
|
|
41
|
+
NEXT_TSCONFIG_PATH: "{prepareDir}/tsconfig.json",
|
|
42
|
+
});
|
|
43
|
+
expect(config.runtime.build).toEqual({
|
|
44
|
+
kind: "next",
|
|
45
|
+
cwd: "frontend",
|
|
46
|
+
distDir: "dist",
|
|
47
|
+
tsconfig: "tsconfig.json",
|
|
48
|
+
inputs: undefined,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("allows Next start apps to disable managed builds explicitly", () => {
|
|
53
|
+
const config = nextApp({ cwd: "frontend", port: 3000, mode: "start", build: null });
|
|
54
|
+
|
|
55
|
+
expect(config.runtime.build).toBeNull();
|
|
56
|
+
expect(config.local.env).toMatchObject({
|
|
57
|
+
NEXT_DIST_DIR: "{prepareDir}/dist",
|
|
58
|
+
NEXT_TSCONFIG_PATH: "{prepareDir}/tsconfig.json",
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
35
62
|
it("builds a Node app preset with tsc build defaults", () => {
|
|
36
63
|
const config = nodeApp({ port: 3000, entry: "src/server.ts" });
|
|
37
64
|
|
|
@@ -64,9 +91,10 @@ describe("setup helpers", () => {
|
|
|
64
91
|
inputs: undefined,
|
|
65
92
|
});
|
|
66
93
|
expect(nextBuild({ cwd: "frontend" })).toEqual({
|
|
67
|
-
kind: "
|
|
68
|
-
script: "./node_modules/.bin/next build",
|
|
94
|
+
kind: "next",
|
|
69
95
|
cwd: "frontend",
|
|
96
|
+
distDir: "dist",
|
|
97
|
+
tsconfig: "tsconfig.json",
|
|
70
98
|
inputs: undefined,
|
|
71
99
|
});
|
|
72
100
|
expect(
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export async function writeNextRuntimeTsconfig(context = {}) {
|
|
5
|
+
const serviceDir = context.cwd || context.productDir;
|
|
6
|
+
const prepareDir = context.prepareDir;
|
|
7
|
+
if (!serviceDir || !prepareDir) {
|
|
8
|
+
throw new Error("writeNextRuntimeTsconfig requires cwd and prepareDir");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const outputPath = path.join(prepareDir, "tsconfig.json");
|
|
12
|
+
const outputDir = path.dirname(outputPath);
|
|
13
|
+
const relative = (target) => path.relative(outputDir, target).replaceAll(path.sep, "/");
|
|
14
|
+
const srcDir = path.join(serviceDir, "src");
|
|
15
|
+
const testsDir = path.join(serviceDir, "tests");
|
|
16
|
+
|
|
17
|
+
const config = {
|
|
18
|
+
extends: relative(path.join(serviceDir, "tsconfig.json")),
|
|
19
|
+
compilerOptions: {
|
|
20
|
+
paths: {
|
|
21
|
+
"@/*": [relative(path.join(srcDir, "*"))],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
include: [
|
|
25
|
+
relative(path.join(serviceDir, "next-env.d.ts")),
|
|
26
|
+
`${relative(srcDir)}/**/*.ts`,
|
|
27
|
+
`${relative(srcDir)}/**/*.tsx`,
|
|
28
|
+
`${relative(srcDir)}/**/*.mts`,
|
|
29
|
+
`${relative(path.join(prepareDir, "dist", "types"))}/**/*.ts`,
|
|
30
|
+
`${relative(path.join(prepareDir, "dist", "dev", "types"))}/**/*.ts`,
|
|
31
|
+
],
|
|
32
|
+
exclude: [
|
|
33
|
+
"node_modules",
|
|
34
|
+
`${relative(srcDir)}/**/__testkit__/**`,
|
|
35
|
+
`${relative(testsDir)}/**`,
|
|
36
|
+
".next/cache",
|
|
37
|
+
".next/dev",
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
42
|
+
fs.writeFileSync(outputPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
43
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { writeNextRuntimeTsconfig } from "./next-runtime-tsconfig.mjs";
|
|
6
|
+
|
|
7
|
+
const cleanups = [];
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
while (cleanups.length > 0) {
|
|
11
|
+
cleanups.pop()();
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("next runtime tsconfig helper", () => {
|
|
16
|
+
it("writes a runtime-specific tsconfig for next builds", async () => {
|
|
17
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-next-tsconfig-"));
|
|
18
|
+
cleanups.push(() => fs.rmSync(tempRoot, { recursive: true, force: true }));
|
|
19
|
+
|
|
20
|
+
const serviceDir = path.join(tempRoot, "frontend");
|
|
21
|
+
const prepareDir = path.join(tempRoot, ".testkit", "prepared");
|
|
22
|
+
fs.mkdirSync(path.join(serviceDir, "src"), { recursive: true });
|
|
23
|
+
fs.writeFileSync(path.join(serviceDir, "tsconfig.json"), "{}\n");
|
|
24
|
+
fs.writeFileSync(path.join(serviceDir, "next-env.d.ts"), "// next\n");
|
|
25
|
+
|
|
26
|
+
await writeNextRuntimeTsconfig({
|
|
27
|
+
cwd: serviceDir,
|
|
28
|
+
prepareDir,
|
|
29
|
+
productDir: tempRoot,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const generated = JSON.parse(fs.readFileSync(path.join(prepareDir, "tsconfig.json"), "utf8"));
|
|
33
|
+
expect(generated).toEqual({
|
|
34
|
+
extends: "../../frontend/tsconfig.json",
|
|
35
|
+
compilerOptions: {
|
|
36
|
+
paths: {
|
|
37
|
+
"@/*": ["../../frontend/src/*"],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
include: [
|
|
41
|
+
"../../frontend/next-env.d.ts",
|
|
42
|
+
"../../frontend/src/**/*.ts",
|
|
43
|
+
"../../frontend/src/**/*.tsx",
|
|
44
|
+
"../../frontend/src/**/*.mts",
|
|
45
|
+
"dist/types/**/*.ts",
|
|
46
|
+
"dist/dev/types/**/*.ts",
|
|
47
|
+
],
|
|
48
|
+
exclude: [
|
|
49
|
+
"node_modules",
|
|
50
|
+
"../../frontend/src/**/__testkit__/**",
|
|
51
|
+
"../../frontend/tests/**",
|
|
52
|
+
".next/cache",
|
|
53
|
+
".next/dev",
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -30,6 +30,16 @@ export function normalizeBuildConfig(value, label) {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
if (kind === "next") {
|
|
34
|
+
return {
|
|
35
|
+
kind,
|
|
36
|
+
cwd: normalizeOptionalString(value.cwd),
|
|
37
|
+
distDir: normalizeOptionalString(value.distDir) || "dist",
|
|
38
|
+
tsconfig: normalizeOptionalString(value.tsconfig) || "tsconfig.json",
|
|
39
|
+
inputs: normalizeConfiguredInputs(value.inputs, `${label}.inputs`),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
if (kind === "steps") {
|
|
34
44
|
return {
|
|
35
45
|
kind,
|
|
@@ -38,7 +48,7 @@ export function normalizeBuildConfig(value, label) {
|
|
|
38
48
|
};
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
throw new Error(`${label}.kind must be one of: tsc, script, steps`);
|
|
51
|
+
throw new Error(`${label}.kind must be one of: tsc, script, next, steps`);
|
|
42
52
|
}
|
|
43
53
|
|
|
44
54
|
export function finalizeBuildConfig(build, transform) {
|
|
@@ -61,6 +71,15 @@ export function finalizeBuildConfig(build, transform) {
|
|
|
61
71
|
inputs: build.inputs.map((input) => transform(input)),
|
|
62
72
|
};
|
|
63
73
|
}
|
|
74
|
+
if (build.kind === "next") {
|
|
75
|
+
return {
|
|
76
|
+
...build,
|
|
77
|
+
cwd: build.cwd ? transform(build.cwd) : build.cwd,
|
|
78
|
+
distDir: transform(build.distDir),
|
|
79
|
+
tsconfig: transform(build.tsconfig),
|
|
80
|
+
inputs: build.inputs.map((input) => transform(input)),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
64
83
|
return {
|
|
65
84
|
...build,
|
|
66
85
|
inputs: build.inputs.map((input) => transform(input)),
|
|
@@ -98,7 +117,28 @@ export function buildConfigToPrepare(build) {
|
|
|
98
117
|
kind: "command",
|
|
99
118
|
cmd: build.script,
|
|
100
119
|
cwd: build.cwd || undefined,
|
|
101
|
-
inputs: [
|
|
120
|
+
inputs: [],
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (build.kind === "next") {
|
|
127
|
+
const inputs = build.inputs.length > 0 ? [...build.inputs] : defaultNextInputs(build);
|
|
128
|
+
return {
|
|
129
|
+
inputs,
|
|
130
|
+
steps: [
|
|
131
|
+
{
|
|
132
|
+
kind: "module",
|
|
133
|
+
specifier: "@elench/testkit/setup/next-runtime-tsconfig#writeNextRuntimeTsconfig",
|
|
134
|
+
cwd: build.cwd || undefined,
|
|
135
|
+
inputs: [],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
kind: "command",
|
|
139
|
+
cmd: "./node_modules/.bin/next build",
|
|
140
|
+
cwd: build.cwd || undefined,
|
|
141
|
+
inputs: [],
|
|
102
142
|
},
|
|
103
143
|
],
|
|
104
144
|
};
|
|
@@ -109,15 +149,15 @@ export function buildConfigToPrepare(build) {
|
|
|
109
149
|
return {
|
|
110
150
|
inputs,
|
|
111
151
|
steps: [
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
152
|
+
{
|
|
153
|
+
kind: "command",
|
|
154
|
+
cmd,
|
|
155
|
+
cwd: build.cwd || undefined,
|
|
156
|
+
inputs: [],
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
121
161
|
|
|
122
162
|
export function compiledEntryFromBuild(build) {
|
|
123
163
|
if (!build || build.kind !== "tsc") return null;
|
|
@@ -134,11 +174,35 @@ function sourceEntryToOutputPath(entry) {
|
|
|
134
174
|
}
|
|
135
175
|
|
|
136
176
|
function defaultTscInputs(build) {
|
|
137
|
-
const inputs = new Set([
|
|
138
|
-
|
|
177
|
+
const inputs = new Set([
|
|
178
|
+
prefixConfiguredPath(build.cwd, build.tsconfig),
|
|
179
|
+
prefixConfiguredPath(build.cwd, "package.json"),
|
|
180
|
+
]);
|
|
181
|
+
const normalizedEntry = prefixConfiguredPath(build.cwd, String(build.entry).split(path.sep).join("/"));
|
|
139
182
|
const topLevelDir = normalizedEntry.includes("/") ? normalizedEntry.slice(0, normalizedEntry.indexOf("/")) : normalizedEntry;
|
|
140
183
|
if (topLevelDir) {
|
|
141
184
|
inputs.add(topLevelDir);
|
|
142
185
|
}
|
|
143
186
|
return [...inputs];
|
|
144
187
|
}
|
|
188
|
+
|
|
189
|
+
function defaultNextInputs(build) {
|
|
190
|
+
const inputs = new Set([
|
|
191
|
+
prefixConfiguredPath(build.cwd, build.tsconfig),
|
|
192
|
+
prefixConfiguredPath(build.cwd, "package.json"),
|
|
193
|
+
prefixConfiguredPath(build.cwd, "src"),
|
|
194
|
+
prefixConfiguredPath(build.cwd, "app"),
|
|
195
|
+
prefixConfiguredPath(build.cwd, "public"),
|
|
196
|
+
prefixConfiguredPath(build.cwd, "next.config.js"),
|
|
197
|
+
prefixConfiguredPath(build.cwd, "next.config.mjs"),
|
|
198
|
+
prefixConfiguredPath(build.cwd, "next.config.ts"),
|
|
199
|
+
]);
|
|
200
|
+
return [...inputs];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function prefixConfiguredPath(cwd, value) {
|
|
204
|
+
const normalizedCwd = String(cwd || "").split(path.sep).join("/").replace(/^\.\/+/, "").replace(/\/+$/g, "");
|
|
205
|
+
const normalizedValue = String(value).split(path.sep).join("/").replace(/^\.\/+/, "");
|
|
206
|
+
if (!normalizedCwd || normalizedCwd === ".") return normalizedValue;
|
|
207
|
+
return `${normalizedCwd}/${normalizedValue}`.replace(/\/+/g, "/");
|
|
208
|
+
}
|
|
@@ -32,7 +32,7 @@ describe("shared build config helpers", () => {
|
|
|
32
32
|
kind: "command",
|
|
33
33
|
cmd: './node_modules/.bin/tsc -p tsconfig.json --outDir "{prepareDir}/dist"',
|
|
34
34
|
cwd: undefined,
|
|
35
|
-
inputs: [
|
|
35
|
+
inputs: [],
|
|
36
36
|
},
|
|
37
37
|
],
|
|
38
38
|
});
|
|
@@ -56,6 +56,22 @@ describe("shared build config helpers", () => {
|
|
|
56
56
|
inputs: ["frontend/package.json"],
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
expect(
|
|
60
|
+
normalizeBuildConfig(
|
|
61
|
+
{
|
|
62
|
+
kind: "next",
|
|
63
|
+
cwd: "frontend",
|
|
64
|
+
},
|
|
65
|
+
"runtime.build"
|
|
66
|
+
)
|
|
67
|
+
).toEqual({
|
|
68
|
+
kind: "next",
|
|
69
|
+
cwd: "frontend",
|
|
70
|
+
distDir: "dist",
|
|
71
|
+
tsconfig: "tsconfig.json",
|
|
72
|
+
inputs: [],
|
|
73
|
+
});
|
|
74
|
+
|
|
59
75
|
expect(
|
|
60
76
|
normalizeBuildConfig(
|
|
61
77
|
{
|
|
@@ -77,5 +93,40 @@ describe("shared build config helpers", () => {
|
|
|
77
93
|
},
|
|
78
94
|
],
|
|
79
95
|
});
|
|
96
|
+
|
|
97
|
+
expect(
|
|
98
|
+
buildConfigToPrepare({
|
|
99
|
+
kind: "next",
|
|
100
|
+
cwd: "frontend",
|
|
101
|
+
distDir: "dist",
|
|
102
|
+
tsconfig: "tsconfig.json",
|
|
103
|
+
inputs: [],
|
|
104
|
+
})
|
|
105
|
+
).toEqual({
|
|
106
|
+
inputs: [
|
|
107
|
+
"frontend/tsconfig.json",
|
|
108
|
+
"frontend/package.json",
|
|
109
|
+
"frontend/src",
|
|
110
|
+
"frontend/app",
|
|
111
|
+
"frontend/public",
|
|
112
|
+
"frontend/next.config.js",
|
|
113
|
+
"frontend/next.config.mjs",
|
|
114
|
+
"frontend/next.config.ts",
|
|
115
|
+
],
|
|
116
|
+
steps: [
|
|
117
|
+
{
|
|
118
|
+
kind: "module",
|
|
119
|
+
specifier: "@elench/testkit/setup/next-runtime-tsconfig#writeNextRuntimeTsconfig",
|
|
120
|
+
cwd: "frontend",
|
|
121
|
+
inputs: [],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
kind: "command",
|
|
125
|
+
cmd: "./node_modules/.bin/next build",
|
|
126
|
+
cwd: "frontend",
|
|
127
|
+
inputs: [],
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
});
|
|
80
131
|
});
|
|
81
132
|
});
|
|
@@ -94,6 +94,12 @@ export function parseModuleSpecifier(specifier) {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
export function isBareModuleSpecifier(modulePath) {
|
|
98
|
+
if (typeof modulePath !== "string" || modulePath.length === 0) return false;
|
|
99
|
+
if (modulePath.startsWith("./") || modulePath.startsWith("../")) return false;
|
|
100
|
+
return !path.isAbsolute(modulePath);
|
|
101
|
+
}
|
|
102
|
+
|
|
97
103
|
export function resolveConfiguredCwd(productDir, stepCwd) {
|
|
98
104
|
return path.resolve(productDir, stepCwd || ".");
|
|
99
105
|
}
|
|
@@ -112,7 +118,15 @@ export function collectConfiguredInputs(productDir, { inputs = [], steps = [] }
|
|
|
112
118
|
collected.add(resolveConfiguredPath(productDir, step.cwd, step.path));
|
|
113
119
|
}
|
|
114
120
|
if (step.kind === "module") {
|
|
115
|
-
|
|
121
|
+
const { modulePath } = parseModuleSpecifier(step.specifier);
|
|
122
|
+
const candidatePath = resolveConfiguredPath(productDir, step.cwd, modulePath);
|
|
123
|
+
if (modulePath.startsWith("@elench/testkit") || fs.existsSync(candidatePath)) {
|
|
124
|
+
if (!modulePath.startsWith("@elench/testkit")) {
|
|
125
|
+
collected.add(candidatePath);
|
|
126
|
+
}
|
|
127
|
+
} else if (!isBareModuleSpecifier(modulePath)) {
|
|
128
|
+
collected.add(candidatePath);
|
|
129
|
+
}
|
|
116
130
|
}
|
|
117
131
|
for (const input of step.inputs || []) {
|
|
118
132
|
collected.add(resolveConfiguredPath(productDir, step.cwd, input));
|
|
@@ -160,10 +174,15 @@ export function validateConfiguredCollection({ productDir, label, inputs = [], s
|
|
|
160
174
|
ensureExistingPath(resolveConfiguredPath(productDir, step.cwd, step.path), `${label} sql file`);
|
|
161
175
|
}
|
|
162
176
|
if (step.kind === "module") {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
177
|
+
const { modulePath } = parseModuleSpecifier(step.specifier);
|
|
178
|
+
const candidatePath = resolveConfiguredPath(productDir, step.cwd, modulePath);
|
|
179
|
+
if (modulePath.startsWith("@elench/testkit")) {
|
|
180
|
+
// Internal testkit module steps are resolved by the runtime alias map.
|
|
181
|
+
} else if (fs.existsSync(candidatePath)) {
|
|
182
|
+
ensureExistingPath(candidatePath, `${label} module`);
|
|
183
|
+
} else if (!isBareModuleSpecifier(modulePath)) {
|
|
184
|
+
ensureExistingPath(candidatePath, `${label} module`);
|
|
185
|
+
}
|
|
167
186
|
}
|
|
168
187
|
for (const input of step.inputs || []) {
|
|
169
188
|
ensureExistingPath(resolveConfiguredPath(productDir, step.cwd, input), `${label} step input`);
|
|
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, it } from "vitest";
|
|
|
5
5
|
import {
|
|
6
6
|
collectConfiguredInputs,
|
|
7
7
|
finalizeConfiguredStep,
|
|
8
|
+
isBareModuleSpecifier,
|
|
8
9
|
normalizeConfiguredStep,
|
|
9
10
|
parseModuleSpecifier,
|
|
10
11
|
validateConfiguredCollection,
|
|
@@ -70,4 +71,32 @@ describe("shared configured steps", () => {
|
|
|
70
71
|
})
|
|
71
72
|
).not.toThrow();
|
|
72
73
|
});
|
|
74
|
+
|
|
75
|
+
it("treats bare module specifiers as modules, not product-relative file paths", () => {
|
|
76
|
+
const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-configured-steps-"));
|
|
77
|
+
tempDirs.push(productDir);
|
|
78
|
+
fs.mkdirSync(path.join(productDir, "frontend"), { recursive: true });
|
|
79
|
+
|
|
80
|
+
const collection = {
|
|
81
|
+
inputs: [],
|
|
82
|
+
steps: [
|
|
83
|
+
{
|
|
84
|
+
kind: "module",
|
|
85
|
+
specifier: "@elench/testkit/setup/next-runtime-tsconfig#writeNextRuntimeTsconfig",
|
|
86
|
+
cwd: "frontend",
|
|
87
|
+
inputs: [],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
expect(isBareModuleSpecifier("@elench/testkit/setup/next-runtime-tsconfig")).toBe(true);
|
|
93
|
+
expect(collectConfiguredInputs(productDir, collection)).toEqual([]);
|
|
94
|
+
expect(() =>
|
|
95
|
+
validateConfiguredCollection({
|
|
96
|
+
productDir,
|
|
97
|
+
label: 'Service "frontend" runtime.prepare',
|
|
98
|
+
...collection,
|
|
99
|
+
})
|
|
100
|
+
).not.toThrow();
|
|
101
|
+
});
|
|
73
102
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.69",
|
|
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.69"
|
|
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.69",
|
|
4
4
|
"description": "CLI for discovering and running local HTTP, DAL, and Playwright test suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -64,10 +64,10 @@
|
|
|
64
64
|
"vitest": "^3.2.4"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@elench/next-analysis": "0.1.
|
|
68
|
-
"@elench/ts-analysis": "0.1.
|
|
69
|
-
"@elench/testkit-bridge": "0.1.
|
|
70
|
-
"@elench/testkit-protocol": "0.1.
|
|
67
|
+
"@elench/next-analysis": "0.1.69",
|
|
68
|
+
"@elench/ts-analysis": "0.1.69",
|
|
69
|
+
"@elench/testkit-bridge": "0.1.69",
|
|
70
|
+
"@elench/testkit-protocol": "0.1.69",
|
|
71
71
|
"@babel/code-frame": "^7.29.0",
|
|
72
72
|
"@oclif/core": "^4.10.6",
|
|
73
73
|
"esbuild": "^0.25.11",
|