@elench/testkit 0.1.127 → 0.1.129

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.
@@ -0,0 +1,80 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const DEFAULT_ASSET_EXTENSIONS = new Set([
5
+ ".csv",
6
+ ".css",
7
+ ".gql",
8
+ ".graphql",
9
+ ".html",
10
+ ".json",
11
+ ".md",
12
+ ".sql",
13
+ ".txt",
14
+ ".wasm",
15
+ ".yaml",
16
+ ".yml",
17
+ ]);
18
+
19
+ export async function copyTscRuntimeAssets(context) {
20
+ const sourceDir = normalizeRelativePath(context.args?.sourceDir || "src", "sourceDir");
21
+ const outDir = normalizeRelativePath(context.args?.outDir || "dist", "outDir");
22
+ const extensions = normalizeExtensions(context.args?.extensions);
23
+ const sourceRoot = path.resolve(context.cwd, sourceDir);
24
+ const outputRoot = path.resolve(context.prepareDir, outDir);
25
+
26
+ if (!context.prepareDir) {
27
+ throw new Error("copyTscRuntimeAssets requires a runtime prepare directory");
28
+ }
29
+ if (!fs.existsSync(sourceRoot)) return;
30
+
31
+ for (const filePath of walkFiles(sourceRoot)) {
32
+ const relative = path.relative(sourceRoot, filePath);
33
+ if (shouldSkipAsset(relative, extensions)) continue;
34
+ const destination = path.join(outputRoot, relative);
35
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
36
+ fs.copyFileSync(filePath, destination);
37
+ }
38
+ }
39
+
40
+ function normalizeRelativePath(value, label) {
41
+ const normalized = String(value || "").trim();
42
+ if (!normalized) throw new Error(`${label} must be a non-empty relative path`);
43
+ if (path.isAbsolute(normalized) || normalized.split(path.sep).includes("..")) {
44
+ throw new Error(`${label} must be a relative path inside the service cwd`);
45
+ }
46
+ return normalized;
47
+ }
48
+
49
+ function normalizeExtensions(value) {
50
+ if (value == null) return DEFAULT_ASSET_EXTENSIONS;
51
+ if (!Array.isArray(value)) throw new Error("extensions must be an array");
52
+ return new Set(
53
+ value.map((entry, index) => {
54
+ const normalized = String(entry || "").trim().toLowerCase();
55
+ if (!/^\.[a-z0-9]+$/i.test(normalized)) {
56
+ throw new Error(`extensions[${index}] must look like ".json"`);
57
+ }
58
+ return normalized;
59
+ })
60
+ );
61
+ }
62
+
63
+ function shouldSkipAsset(relativePath, extensions) {
64
+ const normalized = relativePath.split(path.sep).join("/");
65
+ if (normalized.includes("/__testkit__/") || normalized.startsWith("__testkit__/")) return true;
66
+ if (/(^|\/)[^/]+\.test\.[^/]+$/i.test(normalized)) return true;
67
+ if (/(^|\/)[^/]+\.spec\.[^/]+$/i.test(normalized)) return true;
68
+ return !extensions.has(path.extname(normalized).toLowerCase());
69
+ }
70
+
71
+ function* walkFiles(root) {
72
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
73
+ const entryPath = path.join(root, entry.name);
74
+ if (entry.isDirectory()) {
75
+ yield* walkFiles(entryPath);
76
+ continue;
77
+ }
78
+ if (entry.isFile()) yield entryPath;
79
+ }
80
+ }
@@ -29,6 +29,12 @@ const CONFIG_DATABASE_STEPS_ENTRY = path.join(
29
29
  "config-api",
30
30
  "database-steps.mjs"
31
31
  );
32
+ const CONFIG_BUILD_ASSETS_ENTRY = path.join(
33
+ PACKAGE_ROOT,
34
+ "lib",
35
+ "config-api",
36
+ "build-assets.mjs"
37
+ );
32
38
  const CONFIG_NEXT_TSCONFIG_ENTRY = path.join(
33
39
  PACKAGE_ROOT,
34
40
  "lib",
@@ -274,6 +280,7 @@ function resolvePackageSubpath(specifier) {
274
280
  const subpath = specifier.slice("@elench/testkit".length);
275
281
  if (!subpath) return ROOT_ENTRY;
276
282
  if (subpath === "/config") return CONFIG_ENTRY;
283
+ if (subpath === "/config/build-assets") return CONFIG_BUILD_ASSETS_ENTRY;
277
284
  if (subpath === "/config/database-steps") return CONFIG_DATABASE_STEPS_ENTRY;
278
285
  if (subpath === "/config/next-runtime-tsconfig") return CONFIG_NEXT_TSCONFIG_ENTRY;
279
286
  if (subpath === "/drizzle") return DRIZZLE_ENTRY;
@@ -146,6 +146,7 @@ export function buildConfigToPrepare(build) {
146
146
 
147
147
  const inputs = build.inputs.length > 0 ? [...build.inputs] : defaultTscInputs(build);
148
148
  const cmd = `./node_modules/.bin/tsc -p ${build.tsconfig} --outDir "{prepareDir}/${build.outDir}"`;
149
+ const sourceDir = sourceAssetRootFromEntry(build.entry);
149
150
  return {
150
151
  inputs,
151
152
  steps: [
@@ -155,6 +156,16 @@ export function buildConfigToPrepare(build) {
155
156
  cwd: build.cwd || undefined,
156
157
  inputs: [],
157
158
  },
159
+ {
160
+ kind: "module",
161
+ specifier: "@elench/testkit/config/build-assets#copyTscRuntimeAssets",
162
+ cwd: build.cwd || undefined,
163
+ inputs: [],
164
+ args: {
165
+ sourceDir,
166
+ outDir: build.outDir,
167
+ },
168
+ },
158
169
  ],
159
170
  };
160
171
  }
@@ -173,6 +184,14 @@ function sourceEntryToOutputPath(entry) {
173
184
  return withoutExtension;
174
185
  }
175
186
 
187
+ function sourceAssetRootFromEntry(entry) {
188
+ const normalized = String(entry).split(path.sep).join("/");
189
+ if (normalized.startsWith("src/")) return "src";
190
+ const parts = normalized.split("/").filter(Boolean);
191
+ if (parts.length > 1) return parts[0];
192
+ return ".";
193
+ }
194
+
176
195
  function defaultTscInputs(build) {
177
196
  const inputs = new Set([
178
197
  prefixConfiguredPath(build.cwd, build.tsconfig),
@@ -246,7 +246,11 @@ function renderTemplate(value, context) {
246
246
  }
247
247
 
248
248
  function renderSql(statement, context) {
249
- return String(statement).replace(/\{([A-Za-z0-9_.[\]-]+)\}/g, (_match, key) => toSqlLiteral(readPath(context, key)));
249
+ const source = String(statement);
250
+ return source.replace(/\{([A-Za-z0-9_.[\]-]+)\}/g, (match, key, offset) => {
251
+ const value = readPath(context, key);
252
+ return isInsideSingleQuotedSqlString(source, offset) ? toSqlStringContent(value) : toSqlLiteral(value);
253
+ });
250
254
  }
251
255
 
252
256
  function readPath(source, path) {
@@ -262,7 +266,26 @@ function toSqlLiteral(value) {
262
266
  if (Array.isArray(value)) return `array[${value.map((entry) => toSqlLiteral(entry)).join(", ")}]`;
263
267
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
264
268
  if (typeof value === "boolean") return value ? "true" : "false";
265
- return `'${String(value).replaceAll("'", "''")}'`;
269
+ return `'${toSqlStringContent(value)}'`;
270
+ }
271
+
272
+ function toSqlStringContent(value) {
273
+ if (value === null || value === undefined) return "";
274
+ if (Array.isArray(value)) return value.map((entry) => String(entry)).join(",");
275
+ return String(value).replaceAll("'", "''");
276
+ }
277
+
278
+ function isInsideSingleQuotedSqlString(source, offset) {
279
+ let inString = false;
280
+ for (let index = 0; index < offset; index += 1) {
281
+ if (source[index] !== "'") continue;
282
+ if (inString && source[index + 1] === "'") {
283
+ index += 1;
284
+ continue;
285
+ }
286
+ inString = !inString;
287
+ }
288
+ return inString;
266
289
  }
267
290
 
268
291
  function isExpectedStatus(status, expected) {
@@ -53,16 +53,24 @@ export function createSandboxId(options = {}) {
53
53
  }
54
54
 
55
55
  export function resolveFrontendBaseUrl(options = {}) {
56
+ const config = readUiAuthConfig();
56
57
  return resolveBaseUrl(
57
58
  options.frontendBaseUrl ||
58
59
  options.baseUrl ||
60
+ config.frontendBaseUrl ||
59
61
  process.env.TESTKIT_FRONTEND_BASE_URL ||
60
62
  process.env.BASE_URL
61
63
  );
62
64
  }
63
65
 
64
66
  export function resolveBackendBaseUrl(options = {}) {
65
- return resolveBaseUrl(options.backendBaseUrl || process.env.TESTKIT_BACKEND_BASE_URL || process.env.API_BASE_URL);
67
+ const config = readUiAuthConfig();
68
+ return resolveBaseUrl(
69
+ options.backendBaseUrl ||
70
+ config.backendBaseUrl ||
71
+ process.env.TESTKIT_BACKEND_BASE_URL ||
72
+ process.env.API_BASE_URL
73
+ );
66
74
  }
67
75
 
68
76
  export function assertSafeUiTarget(url, options = {}) {
@@ -243,6 +251,19 @@ function resolveBaseUrl(value) {
243
251
  return assertSafeUiTarget(normalized);
244
252
  }
245
253
 
254
+ function readUiAuthConfig(env = process.env) {
255
+ const raw = env.TESTKIT_UI_CONFIG_JSON;
256
+ if (!raw) return {};
257
+ try {
258
+ const parsed = JSON.parse(raw);
259
+ const root = parsed && typeof parsed === "object" ? parsed : {};
260
+ const auth = root.auth || root;
261
+ return auth && typeof auth === "object" ? auth : {};
262
+ } catch {
263
+ return {};
264
+ }
265
+ }
266
+
246
267
  function parseUrl(value) {
247
268
  try {
248
269
  return new URL(value);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/next-analysis",
3
- "version": "0.1.127",
3
+ "version": "0.1.129",
4
4
  "description": "SWC-backed Next.js source analysis primitives for Erench tools",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit-bridge",
3
- "version": "0.1.127",
3
+ "version": "0.1.129",
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.127"
25
+ "@elench/testkit-protocol": "0.1.129"
26
26
  },
27
27
  "private": false
28
28
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit-protocol",
3
- "version": "0.1.127",
3
+ "version": "0.1.129",
4
4
  "description": "Shared browser protocol for testkit bridge and extension consumers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/ts-analysis",
3
- "version": "0.1.127",
3
+ "version": "0.1.129",
4
4
  "description": "TypeScript compiler-backed source analysis primitives for Erench tools",
5
5
  "type": "module",
6
6
  "exports": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit",
3
- "version": "0.1.127",
3
+ "version": "0.1.129",
4
4
  "description": "Assistant-first CLI for running, inspecting, and debugging local testkit suites",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -95,10 +95,10 @@
95
95
  },
96
96
  "dependencies": {
97
97
  "@babel/code-frame": "^7.29.0",
98
- "@elench/next-analysis": "0.1.127",
99
- "@elench/testkit-bridge": "0.1.127",
100
- "@elench/testkit-protocol": "0.1.127",
101
- "@elench/ts-analysis": "0.1.127",
98
+ "@elench/next-analysis": "0.1.129",
99
+ "@elench/testkit-bridge": "0.1.129",
100
+ "@elench/testkit-protocol": "0.1.129",
101
+ "@elench/ts-analysis": "0.1.129",
102
102
  "@oclif/core": "^4.10.6",
103
103
  "@playwright/test": "^1.52.0",
104
104
  "esbuild": "^0.25.11",