@elench/testkit 0.1.69 → 0.1.71
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 +70 -10
- package/lib/app/doctor.mjs +1 -1
- package/lib/app/typecheck.mjs +8 -4
- package/lib/bundler/index.mjs +17 -17
- package/lib/cli/command-helpers.mjs +1 -1
- package/lib/cli/commands/doctor.mjs +1 -1
- package/lib/cli/commands/typecheck.mjs +1 -1
- package/lib/cli/known-failures.mjs +4 -4
- package/lib/cli/presentation/discovery-reporter.mjs +2 -2
- package/lib/config/{setup-loader.mjs → config-loader.mjs} +32 -32
- package/lib/config/index.mjs +16 -16
- package/lib/config/runtime.mjs +1 -1
- package/lib/config/telemetry.mjs +4 -4
- package/lib/config/validation.mjs +1 -1
- package/lib/{setup → config-api}/index.d.ts +7 -7
- package/lib/{setup → config-api}/index.mjs +10 -10
- package/lib/{setup → config-api}/index.test.mjs +16 -3
- package/lib/{setup → config-api}/runtime.mjs +10 -10
- package/lib/coverage/index.test.mjs +3 -3
- package/lib/discovery/file-metadata.mjs +1 -1
- package/lib/discovery/file-metadata.test.mjs +2 -2
- package/lib/discovery/index.d.ts +1 -1
- package/lib/discovery/index.mjs +28 -28
- package/lib/discovery/index.test.mjs +10 -10
- package/lib/drizzle/index.d.ts +14 -0
- package/lib/drizzle/index.mjs +15 -0
- package/lib/drizzle/index.test.mjs +33 -0
- package/lib/env/index.d.ts +17 -0
- package/lib/env/index.mjs +65 -0
- package/lib/env/index.test.mjs +82 -0
- package/lib/known-failures/github.mjs +4 -4
- package/lib/package.test.mjs +24 -4
- package/lib/playwright/index.d.ts +21 -0
- package/lib/playwright/index.mjs +53 -0
- package/lib/playwright/index.test.mjs +43 -0
- package/lib/runner/template-steps.mjs +5 -5
- package/lib/runner/template.mjs +2 -2
- package/lib/runtime-src/k6/scenario-suite.js +1 -1
- package/lib/runtime-src/k6/suite.js +1 -1
- package/lib/shared/build-config.mjs +1 -1
- package/lib/shared/build-config.test.mjs +1 -1
- package/lib/shared/configured-steps.test.mjs +2 -2
- package/lib/toolchains/index.mjs +2 -2
- package/lib/vitest/index.d.ts +12 -0
- package/lib/vitest/index.mjs +31 -0
- package/lib/vitest/index.test.mjs +20 -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 +24 -8
- /package/lib/{setup → config-api}/next-runtime-tsconfig.mjs +0 -0
- /package/lib/{setup → config-api}/next-runtime-tsconfig.test.mjs +0 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
filesystem, starts local services, provisions Docker-managed local Postgres
|
|
5
5
|
databases, and runs HTTP, DAL, and Playwright suites.
|
|
6
6
|
|
|
7
|
-
The package is now driven by `testkit.
|
|
7
|
+
The package is now driven by `testkit.config.ts`, not `testkit.config.json`.
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
@@ -81,23 +81,83 @@ persisted under `.testkit/results/` and inspected on demand with `show`,
|
|
|
81
81
|
run counts, pass/fail/skip counts, average duration, and last observed status,
|
|
82
82
|
and those summaries are exposed in compact, verbose, and JSON discovery output.
|
|
83
83
|
|
|
84
|
+
## Tooling Adapters
|
|
85
|
+
|
|
86
|
+
`testkit` also ships tool-specific config helpers so consumer repos do not need
|
|
87
|
+
repo-local runtime policy files just to cooperate with managed runs.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
// drizzle.config.ts
|
|
91
|
+
import { defineConfig } from "@elench/testkit/drizzle";
|
|
92
|
+
|
|
93
|
+
export default defineConfig({
|
|
94
|
+
schema: "./src/db/schema/index.ts",
|
|
95
|
+
out: "./src/db/migrations",
|
|
96
|
+
dialect: "postgresql",
|
|
97
|
+
dbCredentials: {
|
|
98
|
+
url: process.env.DATABASE_URL!,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// vitest.config.ts
|
|
105
|
+
import { defineConfig } from "@elench/testkit/vitest";
|
|
106
|
+
|
|
107
|
+
export default defineConfig({
|
|
108
|
+
test: {
|
|
109
|
+
include: ["src/**/*.test.ts"],
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// playwright.config.ts
|
|
116
|
+
import { defineConfig } from "@elench/testkit/playwright";
|
|
117
|
+
import { devices } from "@playwright/test";
|
|
118
|
+
|
|
119
|
+
export default defineConfig({
|
|
120
|
+
testDir: "./__testkit__",
|
|
121
|
+
projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
|
|
122
|
+
}, {
|
|
123
|
+
dotenvFiles: [".env.local"],
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
For scripts and app-runtime code, `@elench/testkit/env` provides shared helpers
|
|
128
|
+
for managed runtime detection, dotenv loading, and local-database safety:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import {
|
|
132
|
+
assertLocalDatabaseUrl,
|
|
133
|
+
loadDotenvFiles,
|
|
134
|
+
shouldLoadDotenv,
|
|
135
|
+
} from "@elench/testkit/env";
|
|
136
|
+
|
|
137
|
+
if (shouldLoadDotenv()) {
|
|
138
|
+
loadDotenvFiles({ files: [".env", ".env.local"] });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
assertLocalDatabaseUrl(process.env, "seed.ts");
|
|
142
|
+
```
|
|
143
|
+
|
|
84
144
|
## Setup
|
|
85
145
|
|
|
86
|
-
Create `testkit.
|
|
146
|
+
Create `testkit.config.ts` at repo root:
|
|
87
147
|
|
|
88
148
|
```ts
|
|
89
149
|
import {
|
|
90
|
-
|
|
91
|
-
|
|
150
|
+
defineConfig,
|
|
151
|
+
defineFile,
|
|
92
152
|
nextApp,
|
|
93
153
|
nodeToolchain,
|
|
94
154
|
nodeApp,
|
|
95
155
|
seedCommand,
|
|
96
156
|
templateDatabase,
|
|
97
157
|
verifyModule,
|
|
98
|
-
} from "@elench/testkit/
|
|
158
|
+
} from "@elench/testkit/config";
|
|
99
159
|
|
|
100
|
-
export default
|
|
160
|
+
export default defineConfig({
|
|
101
161
|
execution: {
|
|
102
162
|
workers: 8,
|
|
103
163
|
fileTimeoutSeconds: 60,
|
|
@@ -157,15 +217,15 @@ export default defineTestkitSetup({
|
|
|
157
217
|
File-local execution metadata now lives next to the test when possible:
|
|
158
218
|
|
|
159
219
|
```ts
|
|
160
|
-
import {
|
|
220
|
+
import { defineFile } from "@elench/testkit/config";
|
|
161
221
|
|
|
162
|
-
export const testkit =
|
|
222
|
+
export const testkit = defineFile({
|
|
163
223
|
skip: "Billing is still stubbed locally",
|
|
164
224
|
locks: ["global-worker-loop"],
|
|
165
225
|
});
|
|
166
226
|
```
|
|
167
227
|
|
|
168
|
-
`testkit.
|
|
228
|
+
`testkit.config.ts` is optional for simple repos, but it is the primary escape hatch
|
|
169
229
|
for:
|
|
170
230
|
|
|
171
231
|
- worker count and per-file runtime budget
|
|
@@ -305,7 +365,7 @@ export default suite;
|
|
|
305
365
|
`testkit` suite files should default-export the suite object returned by
|
|
306
366
|
`defineHttpSuite(...)` or `defineDalSuite(...)`.
|
|
307
367
|
|
|
308
|
-
Named HTTP profiles live in `testkit.
|
|
368
|
+
Named HTTP profiles live in `testkit.config.ts` and can be referenced by name:
|
|
309
369
|
|
|
310
370
|
```ts
|
|
311
371
|
import { defineHttpSuite } from "@elench/testkit";
|
package/lib/app/doctor.mjs
CHANGED
|
@@ -13,7 +13,7 @@ export async function runDoctor(options = {}) {
|
|
|
13
13
|
checks.push({
|
|
14
14
|
code: "config-load",
|
|
15
15
|
level: "pass",
|
|
16
|
-
message: "Loaded testkit
|
|
16
|
+
message: "Loaded testkit config and service config",
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
const discovery = await discoverTests({ dir: productDir, diagnostics: "report" });
|
package/lib/app/typecheck.mjs
CHANGED
|
@@ -16,7 +16,7 @@ export async function runTestkitTypecheck(options = {}) {
|
|
|
16
16
|
const checks = [];
|
|
17
17
|
const rootTsconfig = writeRootTypecheckConfig({
|
|
18
18
|
productDir,
|
|
19
|
-
|
|
19
|
+
configFile: context.configFile,
|
|
20
20
|
outputDir: tempDir,
|
|
21
21
|
});
|
|
22
22
|
checks.push({
|
|
@@ -63,7 +63,7 @@ export async function runTestkitTypecheck(options = {}) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function writeRootTypecheckConfig({ productDir,
|
|
66
|
+
function writeRootTypecheckConfig({ productDir, configFile, outputDir }) {
|
|
67
67
|
const tsconfigPath = path.join(outputDir, "repo.tsconfig.json");
|
|
68
68
|
const extendsPath = findExistingTsconfig(productDir) || null;
|
|
69
69
|
const config = {
|
|
@@ -78,7 +78,7 @@ function writeRootTypecheckConfig({ productDir, setupFile, outputDir }) {
|
|
|
78
78
|
target: "ES2022",
|
|
79
79
|
},
|
|
80
80
|
include: [
|
|
81
|
-
|
|
81
|
+
configFile ? relativeJsonPath(tsconfigPath, configFile) : "testkit.config.ts",
|
|
82
82
|
"**/*.int.testkit.ts",
|
|
83
83
|
"**/*.e2e.testkit.ts",
|
|
84
84
|
"**/*.scenario.testkit.ts",
|
|
@@ -197,7 +197,11 @@ function serviceExists(rootDir, relativePath) {
|
|
|
197
197
|
function packageTypePaths(tsconfigPath) {
|
|
198
198
|
return {
|
|
199
199
|
"@elench/testkit": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "index.d.ts"))],
|
|
200
|
-
"@elench/testkit/
|
|
200
|
+
"@elench/testkit/config": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "config-api", "index.d.ts"))],
|
|
201
|
+
"@elench/testkit/drizzle": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "drizzle", "index.d.ts"))],
|
|
202
|
+
"@elench/testkit/env": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "env", "index.d.ts"))],
|
|
203
|
+
"@elench/testkit/playwright": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "playwright", "index.d.ts"))],
|
|
201
204
|
"@elench/testkit/runtime": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "runtime", "index.d.ts"))],
|
|
205
|
+
"@elench/testkit/vitest": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "vitest", "index.d.ts"))],
|
|
202
206
|
};
|
|
203
207
|
}
|
package/lib/bundler/index.mjs
CHANGED
|
@@ -4,11 +4,11 @@ import path from "path";
|
|
|
4
4
|
import crypto from "crypto";
|
|
5
5
|
import { build } from "esbuild";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
-
import {
|
|
7
|
+
import { findConfigFile } from "../config/config-loader.mjs";
|
|
8
8
|
|
|
9
9
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
10
10
|
const ROOT_ENTRY = path.join(PACKAGE_ROOT, "lib", "index.mjs");
|
|
11
|
-
const
|
|
11
|
+
const CONFIG_ENTRY = path.join(PACKAGE_ROOT, "lib", "config-api", "index.mjs");
|
|
12
12
|
const RUNTIME_ENTRY = path.join(PACKAGE_ROOT, "lib", "runtime", "index.mjs");
|
|
13
13
|
const bundleCache = new Map();
|
|
14
14
|
|
|
@@ -21,8 +21,8 @@ export async function bundleK6File({
|
|
|
21
21
|
const bundleDir = path.join(productDir, ".testkit", "_bundles", serviceName || "shared");
|
|
22
22
|
fs.mkdirSync(bundleDir, { recursive: true });
|
|
23
23
|
|
|
24
|
-
const
|
|
25
|
-
const cacheKey = await buildCacheKey(absoluteSource,
|
|
24
|
+
const configFile = findConfigFile(productDir);
|
|
25
|
+
const cacheKey = await buildCacheKey(absoluteSource, configFile);
|
|
26
26
|
const cached = bundleCache.get(cacheKey);
|
|
27
27
|
if (cached && fs.existsSync(cached)) {
|
|
28
28
|
return cached;
|
|
@@ -38,7 +38,7 @@ export async function bundleK6File({
|
|
|
38
38
|
);
|
|
39
39
|
fs.writeFileSync(entryFile, buildBundleEntryModule({
|
|
40
40
|
sourceFile: absoluteSource,
|
|
41
|
-
|
|
41
|
+
configFile,
|
|
42
42
|
}));
|
|
43
43
|
|
|
44
44
|
await build({
|
|
@@ -62,7 +62,7 @@ export async function bundleK6File({
|
|
|
62
62
|
return outputFile;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
async function buildCacheKey(sourceFile,
|
|
65
|
+
async function buildCacheKey(sourceFile, configFile = null) {
|
|
66
66
|
const source = await fs.promises.readFile(sourceFile, "utf8");
|
|
67
67
|
const packageJson = await fs.promises.readFile(path.join(PACKAGE_ROOT, "package.json"), "utf8");
|
|
68
68
|
const hash = crypto
|
|
@@ -73,9 +73,9 @@ async function buildCacheKey(sourceFile, setupFile = null) {
|
|
|
73
73
|
.update("\0")
|
|
74
74
|
.update(packageJson);
|
|
75
75
|
|
|
76
|
-
if (
|
|
77
|
-
hash.update("\0").update(
|
|
78
|
-
hash.update(await fs.promises.readFile(
|
|
76
|
+
if (configFile && fs.existsSync(configFile)) {
|
|
77
|
+
hash.update("\0").update(configFile).update("\0");
|
|
78
|
+
hash.update(await fs.promises.readFile(configFile, "utf8"));
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
return hash.digest("hex");
|
|
@@ -98,27 +98,27 @@ function testkitPackageAliasPlugin() {
|
|
|
98
98
|
function resolvePackageSubpath(specifier) {
|
|
99
99
|
const subpath = specifier.slice("@elench/testkit".length);
|
|
100
100
|
if (!subpath) return ROOT_ENTRY;
|
|
101
|
-
if (subpath === "/
|
|
101
|
+
if (subpath === "/config") return CONFIG_ENTRY;
|
|
102
102
|
if (subpath === "/runtime") return RUNTIME_ENTRY;
|
|
103
103
|
|
|
104
104
|
throw new Error(`Unsupported @elench/testkit import "${specifier}" in ${os.platform()}`);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
function buildBundleEntryModule({ sourceFile,
|
|
107
|
+
function buildBundleEntryModule({ sourceFile, configFile }) {
|
|
108
108
|
const sourceImport = JSON.stringify(sourceFile);
|
|
109
|
-
const
|
|
109
|
+
const configRegistration = configFile
|
|
110
110
|
? `
|
|
111
|
-
import * as
|
|
112
|
-
|
|
111
|
+
import * as repoConfigModule from ${JSON.stringify(configFile)};
|
|
112
|
+
registerRepoConfig(repoConfigModule.default || repoConfigModule || null);
|
|
113
113
|
`
|
|
114
114
|
: `
|
|
115
|
-
|
|
115
|
+
registerRepoConfig(null);
|
|
116
116
|
`;
|
|
117
117
|
|
|
118
118
|
return `
|
|
119
|
-
import {
|
|
119
|
+
import { registerRepoConfig } from "@elench/testkit/config";
|
|
120
120
|
import * as suiteModule from ${sourceImport};
|
|
121
|
-
${
|
|
121
|
+
${configRegistration}
|
|
122
122
|
const suite = normalizeTestkitSuite(suiteModule);
|
|
123
123
|
export const options = suite.options;
|
|
124
124
|
export function setup(...args) {
|
|
@@ -59,7 +59,7 @@ export const runFlags = {
|
|
|
59
59
|
default: false,
|
|
60
60
|
}),
|
|
61
61
|
"ignore-skip-rules": Flags.boolean({
|
|
62
|
-
description: "Run files even if testkit.
|
|
62
|
+
description: "Run files even if testkit.config.ts marks them skipped",
|
|
63
63
|
default: false,
|
|
64
64
|
}),
|
|
65
65
|
"output-mode": Flags.string({
|
|
@@ -2,7 +2,7 @@ import { Command, Flags } from "@oclif/core";
|
|
|
2
2
|
import { runDoctor } from "../../app/doctor.mjs";
|
|
3
3
|
|
|
4
4
|
export default class DoctorCommand extends Command {
|
|
5
|
-
static summary = "Run built-in
|
|
5
|
+
static summary = "Run built-in config, discovery, and hygiene checks";
|
|
6
6
|
|
|
7
7
|
static enableJsonFlag = true;
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@ import { Command, Flags } from "@oclif/core";
|
|
|
2
2
|
import { runTestkitTypecheck } from "../../app/typecheck.mjs";
|
|
3
3
|
|
|
4
4
|
export default class TypecheckCommand extends Command {
|
|
5
|
-
static summary = "Typecheck testkit
|
|
5
|
+
static summary = "Typecheck testkit config, helpers, and suites";
|
|
6
6
|
|
|
7
7
|
static enableJsonFlag = true;
|
|
8
8
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { resolveProductDir } from "../config/index.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { loadTestkitConfig } from "../config/config-loader.mjs";
|
|
5
5
|
import {
|
|
6
6
|
buildKnownFailureIssueValidationSummaryLines,
|
|
7
7
|
normalizeKnownFailureIssueValidationConfig,
|
|
@@ -106,14 +106,14 @@ export async function runKnownFailuresValidateCommand(options = {}) {
|
|
|
106
106
|
|
|
107
107
|
async function resolveKnownFailuresContext(options) {
|
|
108
108
|
const productDir = resolveProductDir(process.cwd(), options.dir);
|
|
109
|
-
const {
|
|
110
|
-
const reporting =
|
|
109
|
+
const { config } = await loadTestkitConfig(productDir);
|
|
110
|
+
const reporting = config?.reporting || {};
|
|
111
111
|
|
|
112
112
|
const knownFailuresRelativePath = normalizeOptionalString(options.input)
|
|
113
113
|
|| normalizeOptionalString(reporting.knownFailuresFile);
|
|
114
114
|
if (!knownFailuresRelativePath) {
|
|
115
115
|
throw new Error(
|
|
116
|
-
"Known failures file not configured. Set reporting.knownFailuresFile in testkit.
|
|
116
|
+
"Known failures file not configured. Set reporting.knownFailuresFile in testkit.config.ts or pass --input."
|
|
117
117
|
);
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -57,8 +57,8 @@ function buildCompactLines(result) {
|
|
|
57
57
|
function buildVerboseLines(result) {
|
|
58
58
|
const lines = [];
|
|
59
59
|
lines.push(`${colorHeading("Summary")} ${JSON.stringify(result.summary)}`);
|
|
60
|
-
if (result.
|
|
61
|
-
lines.push(`
|
|
60
|
+
if (result.configFile) {
|
|
61
|
+
lines.push(`config ${result.configFile}`);
|
|
62
62
|
}
|
|
63
63
|
if (result.history?.available) {
|
|
64
64
|
lines.push(`history ${result.history.path}`);
|
|
@@ -6,17 +6,17 @@ import { fileURLToPath, pathToFileURL } from "url";
|
|
|
6
6
|
|
|
7
7
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
8
8
|
const ROOT_ENTRY = path.join(PACKAGE_ROOT, "lib", "index.mjs");
|
|
9
|
-
const
|
|
9
|
+
const CONFIG_ENTRY = path.join(PACKAGE_ROOT, "lib", "config-api", "index.mjs");
|
|
10
10
|
const RUNTIME_ENTRY = path.join(PACKAGE_ROOT, "lib", "runtime", "index.mjs");
|
|
11
|
-
const
|
|
12
|
-
"testkit.
|
|
13
|
-
"testkit.
|
|
14
|
-
"testkit.
|
|
15
|
-
"testkit.
|
|
11
|
+
const CONFIG_FILES = [
|
|
12
|
+
"testkit.config.ts",
|
|
13
|
+
"testkit.config.mts",
|
|
14
|
+
"testkit.config.mjs",
|
|
15
|
+
"testkit.config.js",
|
|
16
16
|
];
|
|
17
17
|
|
|
18
|
-
export function
|
|
19
|
-
for (const candidate of
|
|
18
|
+
export function findConfigFile(productDir) {
|
|
19
|
+
for (const candidate of CONFIG_FILES) {
|
|
20
20
|
const absolute = path.join(productDir, candidate);
|
|
21
21
|
if (fs.existsSync(absolute)) {
|
|
22
22
|
return absolute;
|
|
@@ -25,54 +25,54 @@ export function findSetupFile(productDir) {
|
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export async function
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
28
|
+
export async function loadTestkitConfig(productDir) {
|
|
29
|
+
const configFile = findConfigFile(productDir);
|
|
30
|
+
if (!configFile) {
|
|
31
31
|
return {
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
config: {},
|
|
33
|
+
configFile: null,
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const bundleDir = path.join(productDir, ".testkit", "
|
|
37
|
+
const bundleDir = path.join(productDir, ".testkit", "_config");
|
|
38
38
|
fs.mkdirSync(bundleDir, { recursive: true });
|
|
39
39
|
|
|
40
|
-
const cacheKey =
|
|
41
|
-
const outputFile = path.join(bundleDir, `
|
|
40
|
+
const cacheKey = buildConfigCacheKey(configFile);
|
|
41
|
+
const outputFile = path.join(bundleDir, `config-${cacheKey.slice(0, 12)}.mjs`);
|
|
42
42
|
|
|
43
43
|
await build({
|
|
44
44
|
absWorkingDir: productDir,
|
|
45
45
|
bundle: true,
|
|
46
|
-
entryPoints: [
|
|
46
|
+
entryPoints: [configFile],
|
|
47
47
|
format: "esm",
|
|
48
48
|
legalComments: "none",
|
|
49
49
|
outfile: outputFile,
|
|
50
50
|
platform: "node",
|
|
51
51
|
sourcemap: "inline",
|
|
52
52
|
target: "es2020",
|
|
53
|
-
plugins: [
|
|
53
|
+
plugins: [testkitConfigAliasPlugin()],
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
const imported = await import(`${pathToFileURL(outputFile).href}?v=${cacheKey}`);
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
59
|
-
throw new Error(`testkit
|
|
57
|
+
const config = imported.default || imported.config || {};
|
|
58
|
+
if (!config || typeof config !== "object") {
|
|
59
|
+
throw new Error(`testkit config file must export an object: ${path.basename(configFile)}`);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
return {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
config,
|
|
64
|
+
configFile,
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function
|
|
69
|
-
const content = fs.readFileSync(
|
|
70
|
-
return crypto.createHash("sha256").update(
|
|
68
|
+
function buildConfigCacheKey(configFile) {
|
|
69
|
+
const content = fs.readFileSync(configFile, "utf8");
|
|
70
|
+
return crypto.createHash("sha256").update(configFile).update("\0").update(content).digest("hex");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function
|
|
73
|
+
function testkitConfigAliasPlugin() {
|
|
74
74
|
return {
|
|
75
|
-
name: "testkit-
|
|
75
|
+
name: "testkit-config-alias",
|
|
76
76
|
setup(buildApi) {
|
|
77
77
|
buildApi.onResolve({ filter: /^@elench\/testkit(?:\/.*)?$/ }, (args) => ({
|
|
78
78
|
namespace: "file",
|
|
@@ -85,14 +85,14 @@ function testkitSetupAliasPlugin() {
|
|
|
85
85
|
function resolvePackageSubpath(specifier) {
|
|
86
86
|
const subpath = specifier.slice("@elench/testkit".length);
|
|
87
87
|
if (!subpath) return ROOT_ENTRY;
|
|
88
|
-
if (subpath === "/
|
|
88
|
+
if (subpath === "/config") return CONFIG_ENTRY;
|
|
89
89
|
if (subpath === "/runtime") {
|
|
90
90
|
throw new Error(
|
|
91
|
-
"testkit.
|
|
92
|
-
"Use @elench/testkit/
|
|
91
|
+
"testkit.config.ts may not import @elench/testkit/runtime. " +
|
|
92
|
+
"Use @elench/testkit/config helpers instead."
|
|
93
93
|
);
|
|
94
94
|
}
|
|
95
95
|
if (subpath === "/runtime/index.mjs") return RUNTIME_ENTRY;
|
|
96
96
|
|
|
97
|
-
throw new Error(`Unsupported @elench/testkit import "${specifier}" while loading testkit
|
|
97
|
+
throw new Error(`Unsupported @elench/testkit import "${specifier}" while loading testkit config`);
|
|
98
98
|
}
|
package/lib/config/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { discoverProject } from "./discovery.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { loadTestkitConfig } from "./config-loader.mjs";
|
|
5
5
|
import { normalizeToolchainRegistry } from "../toolchains/index.mjs";
|
|
6
6
|
import { loadTestFileMetadataMap } from "../discovery/file-metadata.mjs";
|
|
7
7
|
import { mergeDiscoveryConfigs } from "../discovery/path-policy.mjs";
|
|
@@ -27,13 +27,13 @@ export { parseDotenv, resolveProductDir, resolveServiceCwd };
|
|
|
27
27
|
|
|
28
28
|
export async function loadConfigContext(opts = {}) {
|
|
29
29
|
const productDir = resolveProductDir(process.cwd(), opts.dir);
|
|
30
|
-
const
|
|
31
|
-
const {
|
|
32
|
-
const execution = normalizeRepoExecution(
|
|
33
|
-
const reporting = normalizeReportingConfig(
|
|
34
|
-
const toolchains = normalizeToolchainRegistry(
|
|
35
|
-
const discoveryConfig = normalizeRepoDiscoveryConfig(
|
|
36
|
-
const explicitServices =
|
|
30
|
+
const configContext = opts.configContext || (await loadTestkitConfig(productDir));
|
|
31
|
+
const { config, configFile } = configContext;
|
|
32
|
+
const execution = normalizeRepoExecution(config.execution);
|
|
33
|
+
const reporting = normalizeReportingConfig(config.reporting);
|
|
34
|
+
const toolchains = normalizeToolchainRegistry(config.toolchains);
|
|
35
|
+
const discoveryConfig = normalizeRepoDiscoveryConfig(config.discovery);
|
|
36
|
+
const explicitServices = config.services || {};
|
|
37
37
|
const discovery = discoverProject(productDir, explicitServices, {
|
|
38
38
|
...(opts.discoveryOptions || {}),
|
|
39
39
|
discovery: discoveryConfig,
|
|
@@ -50,8 +50,8 @@ export async function loadConfigContext(opts = {}) {
|
|
|
50
50
|
normalizeServiceConfig({
|
|
51
51
|
name,
|
|
52
52
|
productDir,
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
config,
|
|
54
|
+
configFile,
|
|
55
55
|
execution,
|
|
56
56
|
discovery: discoveryConfig,
|
|
57
57
|
reporting,
|
|
@@ -67,8 +67,8 @@ export async function loadConfigContext(opts = {}) {
|
|
|
67
67
|
|
|
68
68
|
return {
|
|
69
69
|
productDir,
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
config,
|
|
71
|
+
configFile,
|
|
72
72
|
execution,
|
|
73
73
|
discovery: discoveryConfig,
|
|
74
74
|
reporting,
|
|
@@ -98,8 +98,8 @@ export { resolveDalBinary, resolveK6Binary } from "./binaries.mjs";
|
|
|
98
98
|
function normalizeServiceConfig({
|
|
99
99
|
name,
|
|
100
100
|
productDir,
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
config,
|
|
102
|
+
configFile,
|
|
103
103
|
execution,
|
|
104
104
|
discovery,
|
|
105
105
|
reporting,
|
|
@@ -150,9 +150,9 @@ function normalizeServiceConfig({
|
|
|
150
150
|
return {
|
|
151
151
|
name,
|
|
152
152
|
productDir,
|
|
153
|
-
|
|
153
|
+
configFile,
|
|
154
154
|
stateDir: path.join(productDir, ".testkit", name),
|
|
155
|
-
telemetry: normalizeTelemetryConfig(
|
|
155
|
+
telemetry: normalizeTelemetryConfig(config.telemetry),
|
|
156
156
|
suites,
|
|
157
157
|
testkit: {
|
|
158
158
|
execution,
|
package/lib/config/runtime.mjs
CHANGED
|
@@ -24,7 +24,7 @@ export function normalizeReportingConfig(value) {
|
|
|
24
24
|
|
|
25
25
|
const knownFailuresFile = normalizeOptionalString(value.knownFailuresFile);
|
|
26
26
|
if (!knownFailuresFile) {
|
|
27
|
-
throw new Error('testkit.
|
|
27
|
+
throw new Error('testkit.config.ts reporting.knownFailuresFile must be a non-empty string');
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const issueValidation = normalizeKnownFailureIssueValidationConfig(value.issueValidation);
|
package/lib/config/telemetry.mjs
CHANGED
|
@@ -5,18 +5,18 @@ export function normalizeTelemetryConfig(telemetry) {
|
|
|
5
5
|
try {
|
|
6
6
|
parsed = new URL(telemetry.endpoint);
|
|
7
7
|
} catch {
|
|
8
|
-
throw new Error("testkit.
|
|
8
|
+
throw new Error("testkit.config telemetry.endpoint must be a valid URL");
|
|
9
9
|
}
|
|
10
10
|
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
11
|
-
throw new Error("testkit.
|
|
11
|
+
throw new Error("testkit.config telemetry.endpoint must use http or https");
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
if (telemetry.enabled === true) {
|
|
15
15
|
if (!telemetry.endpoint) {
|
|
16
|
-
throw new Error("testkit.
|
|
16
|
+
throw new Error("testkit.config telemetry.endpoint is required when telemetry.enabled is true");
|
|
17
17
|
}
|
|
18
18
|
if (!telemetry.tokenEnv) {
|
|
19
|
-
throw new Error("testkit.
|
|
19
|
+
throw new Error("testkit.config telemetry.tokenEnv is required when telemetry.enabled is true");
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
return {
|
|
@@ -32,7 +32,7 @@ export function validateServiceConfig({
|
|
|
32
32
|
|
|
33
33
|
if (usesLocalExecution && !local) {
|
|
34
34
|
throw new Error(
|
|
35
|
-
`Service "${name}" defines non-DAL suites but no local runtime could be resolved. Add it in testkit.
|
|
35
|
+
`Service "${name}" defines non-DAL suites but no local runtime could be resolved. Add it in testkit.config.ts.`
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -215,11 +215,11 @@ export interface NextAppOptions extends Omit<ServiceConfig, "local" | "runtime">
|
|
|
215
215
|
toolchain?: string | NodeToolchainConfig;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
export interface
|
|
218
|
+
export interface TestkitConfig {
|
|
219
219
|
discovery?: DiscoveryConfig;
|
|
220
220
|
execution?: TestkitExecutionConfig;
|
|
221
221
|
profiles?: {
|
|
222
|
-
http?: Record<string, HttpSuiteConfig
|
|
222
|
+
http?: Record<string, HttpSuiteConfig<any>>;
|
|
223
223
|
};
|
|
224
224
|
reporting?: {
|
|
225
225
|
knownFailuresFile?: string;
|
|
@@ -235,9 +235,9 @@ export interface TestkitSetup {
|
|
|
235
235
|
};
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
export declare function
|
|
238
|
+
export declare function defineConfig<T extends TestkitConfig>(config: T): T;
|
|
239
239
|
export declare function defineHttpProfile<T extends HttpSuiteConfig>(profile: T): T;
|
|
240
|
-
export declare function
|
|
240
|
+
export declare function defineFile<T extends TestkitFileMetadata>(metadata: T): T;
|
|
241
241
|
export declare function postgresDatabase(options?: Omit<LocalDatabaseConfig, "provider">): LocalDatabaseConfig;
|
|
242
242
|
export declare function commandStep(
|
|
243
243
|
cmd: string,
|
|
@@ -309,9 +309,9 @@ export declare function jsonSessionProfile(options?: {
|
|
|
309
309
|
usernameEnv?: string;
|
|
310
310
|
}): HttpSuiteConfig;
|
|
311
311
|
|
|
312
|
-
export declare function
|
|
313
|
-
export declare function
|
|
314
|
-
export declare function
|
|
312
|
+
export declare function registerRepoConfig(config: unknown): void;
|
|
313
|
+
export declare function getRepoConfig(): unknown;
|
|
314
|
+
export declare function clearRepoConfig(): void;
|
|
315
315
|
export declare function registerRuntimeContext(context: unknown): void;
|
|
316
316
|
export declare function getRuntimeContext(): unknown;
|
|
317
317
|
export declare function clearRuntimeContext(): void;
|