@elench/testkit 0.1.59 → 0.1.61

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.
Files changed (36) hide show
  1. package/lib/config/database.mjs +53 -0
  2. package/lib/config/database.test.mjs +29 -0
  3. package/lib/config/discovery-config.mjs +13 -0
  4. package/lib/config/env.mjs +55 -0
  5. package/lib/config/env.test.mjs +40 -0
  6. package/lib/config/index.mjs +21 -807
  7. package/lib/config/paths.mjs +28 -0
  8. package/lib/config/paths.test.mjs +27 -0
  9. package/lib/config/runtime.mjs +241 -0
  10. package/lib/config/runtime.test.mjs +56 -0
  11. package/lib/config/skip-config.mjs +189 -0
  12. package/lib/config/skip-config.test.mjs +63 -0
  13. package/lib/config/telemetry.mjs +28 -0
  14. package/lib/config/validation.mjs +124 -0
  15. package/lib/coverage/backend-discovery.mjs +183 -0
  16. package/lib/coverage/backend-discovery.test.mjs +52 -0
  17. package/lib/coverage/evidence.mjs +146 -0
  18. package/lib/coverage/evidence.test.mjs +64 -0
  19. package/lib/coverage/fs-walk.mjs +64 -0
  20. package/lib/coverage/graph-builder.mjs +167 -0
  21. package/lib/coverage/index.mjs +1 -776
  22. package/lib/coverage/index.test.mjs +183 -14
  23. package/lib/coverage/next-discovery.mjs +174 -0
  24. package/lib/coverage/next-static-analysis.mjs +728 -0
  25. package/lib/coverage/routing.mjs +86 -0
  26. package/lib/coverage/routing.test.mjs +52 -0
  27. package/lib/coverage/shared.mjs +197 -0
  28. package/lib/coverage/shared.test.mjs +39 -0
  29. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  30. package/node_modules/@elench/testkit-bridge/src/index.mjs +101 -15
  31. package/node_modules/@elench/testkit-bridge/src/index.test.mjs +36 -6
  32. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  33. package/node_modules/@elench/testkit-protocol/src/index.d.ts +1 -0
  34. package/node_modules/@elench/testkit-protocol/src/index.mjs +3 -1
  35. package/node_modules/@elench/testkit-protocol/src/index.test.mjs +14 -0
  36. package/package.json +5 -4
@@ -0,0 +1,53 @@
1
+ import { normalizeDatabaseBinding } from "../runner/execution-config.mjs";
2
+ import { normalizeOptionalString, normalizeTemplateInputs, normalizeTemplateLifecycleSteps } from "./runtime.mjs";
3
+
4
+ const DEFAULT_LOCAL_IMAGE = "pgvector/pgvector:pg16";
5
+ const DEFAULT_LOCAL_USER = "testkit";
6
+ const DEFAULT_LOCAL_PASSWORD = "testkit";
7
+
8
+ export function normalizeDatabaseConfig(explicitService, serviceName) {
9
+ if (explicitService.databaseFrom) return undefined;
10
+ if (!explicitService.database) return undefined;
11
+
12
+ const database =
13
+ explicitService.database.provider === "local"
14
+ ? explicitService.database
15
+ : {
16
+ provider: "local",
17
+ ...explicitService.database,
18
+ };
19
+
20
+ return {
21
+ ...database,
22
+ binding: normalizeDatabaseBinding(database.binding || "per-runtime", `Service "${serviceName}" database.binding`),
23
+ provider: "local",
24
+ selectedBackend: "local",
25
+ reset: database.reset !== false,
26
+ image: database.image || DEFAULT_LOCAL_IMAGE,
27
+ user: database.user || DEFAULT_LOCAL_USER,
28
+ password: database.password || DEFAULT_LOCAL_PASSWORD,
29
+ template: normalizeDatabaseTemplateConfig(database.template, serviceName),
30
+ serviceName,
31
+ };
32
+ }
33
+
34
+ export function normalizeDatabaseTemplateConfig(value, serviceName) {
35
+ if (value == null) {
36
+ return {
37
+ inputs: [],
38
+ migrate: [],
39
+ seed: [],
40
+ verify: [],
41
+ };
42
+ }
43
+ if (!value || typeof value !== "object") {
44
+ throw new Error(`Service "${serviceName}" database.template must be an object`);
45
+ }
46
+
47
+ return {
48
+ inputs: normalizeTemplateInputs(value.inputs, `Service "${serviceName}" database.template`),
49
+ migrate: normalizeTemplateLifecycleSteps(value.migrate, `Service "${serviceName}" database.template.migrate`),
50
+ seed: normalizeTemplateLifecycleSteps(value.seed, `Service "${serviceName}" database.template.seed`),
51
+ verify: normalizeTemplateLifecycleSteps(value.verify, `Service "${serviceName}" database.template.verify`),
52
+ };
53
+ }
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { normalizeDatabaseConfig } from "./database.mjs";
3
+
4
+ describe("config database helpers", () => {
5
+ it("normalizes local database config with template defaults", () => {
6
+ expect(
7
+ normalizeDatabaseConfig(
8
+ {
9
+ database: {
10
+ template: {
11
+ migrate: [{ kind: "sql-file", path: "sql/migrate.sql" }],
12
+ },
13
+ },
14
+ },
15
+ "web"
16
+ )
17
+ ).toMatchObject({
18
+ provider: "local",
19
+ selectedBackend: "local",
20
+ reset: true,
21
+ template: {
22
+ inputs: [],
23
+ migrate: [{ kind: "sql-file", path: "sql/migrate.sql" }],
24
+ seed: [],
25
+ verify: [],
26
+ },
27
+ });
28
+ });
29
+ });
@@ -0,0 +1,13 @@
1
+ import { normalizeDiscoveryConfig } from "../discovery/path-policy.mjs";
2
+
3
+ export function normalizeRepoDiscoveryConfig(value) {
4
+ return normalizeDiscoveryConfig(value, { allowRoots: true });
5
+ }
6
+
7
+ export function normalizeServiceDiscoveryConfig(value, serviceName) {
8
+ try {
9
+ return normalizeDiscoveryConfig(value, { allowRoots: true });
10
+ } catch (error) {
11
+ throw new Error(`Service "${serviceName}" has invalid discovery config: ${error.message}`);
12
+ }
13
+ }
@@ -0,0 +1,55 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { resolveServiceCwd } from "./paths.mjs";
4
+
5
+ export function parseDotenv(filePath) {
6
+ if (!fs.existsSync(filePath)) return {};
7
+ return parseDotenvString(fs.readFileSync(filePath, "utf8"));
8
+ }
9
+
10
+ export function parseDotenvString(source) {
11
+ const env = {};
12
+ for (const line of String(source).split("\n")) {
13
+ const trimmed = line.trim();
14
+ if (!trimmed || trimmed.startsWith("#")) continue;
15
+ const eq = trimmed.indexOf("=");
16
+ if (eq === -1) continue;
17
+ const key = trimmed.slice(0, eq).trim();
18
+ let val = trimmed.slice(eq + 1).trim();
19
+ if ((val.startsWith("'") && val.endsWith("'")) || (val.startsWith("\"") && val.endsWith("\""))) {
20
+ val = val.slice(1, -1);
21
+ }
22
+ env[key] = val;
23
+ }
24
+ return env;
25
+ }
26
+
27
+ export function inferEnvFiles(productDir, explicitService, local) {
28
+ if (explicitService.envFile || explicitService.envFiles) {
29
+ const files = [];
30
+ if (explicitService.envFile) files.push(explicitService.envFile);
31
+ if (Array.isArray(explicitService.envFiles)) files.push(...explicitService.envFiles);
32
+ return files;
33
+ }
34
+
35
+ const candidates = [];
36
+ const serviceCwd = local?.cwd || ".";
37
+ if (serviceCwd !== ".") {
38
+ candidates.push(path.posix.join(serviceCwd, ".env.testkit"));
39
+ candidates.push(path.posix.join(serviceCwd, ".env"));
40
+ }
41
+ candidates.push(".env.testkit");
42
+ candidates.push(".env");
43
+
44
+ return [...new Set(candidates)]
45
+ .map((candidate) => candidate.split(path.sep).join("/"))
46
+ .filter((candidate) => fs.existsSync(resolveServiceCwd(productDir, candidate)));
47
+ }
48
+
49
+ export function loadServiceEnv(productDir, envFiles) {
50
+ const env = {};
51
+ for (const envFile of envFiles) {
52
+ Object.assign(env, parseDotenv(resolveServiceCwd(productDir, envFile)));
53
+ }
54
+ return env;
55
+ }
@@ -0,0 +1,40 @@
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 { inferEnvFiles, parseDotenvString } from "./env.mjs";
6
+
7
+ const cleanups = [];
8
+
9
+ afterEach(() => {
10
+ while (cleanups.length > 0) {
11
+ cleanups.pop()();
12
+ }
13
+ });
14
+
15
+ describe("config env helpers", () => {
16
+ it("parses dotenv content with comments and quoted values", () => {
17
+ expect(
18
+ parseDotenvString(`
19
+ # comment
20
+ FOO=bar
21
+ BAR="quoted value"
22
+ BAZ='single quoted'
23
+ `)
24
+ ).toEqual({
25
+ FOO: "bar",
26
+ BAR: "quoted value",
27
+ BAZ: "single quoted",
28
+ });
29
+ });
30
+
31
+ it("infers env files from service cwd and root", () => {
32
+ const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-env-"));
33
+ cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
34
+ fs.mkdirSync(path.join(productDir, "apps", "web"), { recursive: true });
35
+ fs.writeFileSync(path.join(productDir, ".env"), "ROOT=1\n");
36
+ fs.writeFileSync(path.join(productDir, "apps", "web", ".env.testkit"), "WEB=1\n");
37
+
38
+ expect(inferEnvFiles(productDir, {}, { cwd: "apps/web" })).toEqual(["apps/web/.env.testkit", ".env"]);
39
+ });
40
+ });