@elench/testkit 0.1.118 → 0.1.120

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 (34) hide show
  1. package/lib/app/doctor.mjs +11 -113
  2. package/lib/cli/assistant/command-observer.mjs +1 -1
  3. package/lib/cli/assistant/state.mjs +2 -0
  4. package/lib/cli/commands/lint.mjs +41 -0
  5. package/lib/cli/entrypoint.mjs +1 -0
  6. package/lib/cli/operations/lint/operation.mjs +20 -0
  7. package/lib/cli/renderers/doctor/text.mjs +5 -0
  8. package/lib/cli/renderers/lint/text.mjs +20 -0
  9. package/lib/config/index.mjs +1 -0
  10. package/lib/config-api/database-steps.mjs +340 -0
  11. package/lib/config-api/index.d.ts +126 -3
  12. package/lib/config-api/index.mjs +176 -12
  13. package/lib/lint/index.mjs +689 -0
  14. package/lib/runner/template-steps.mjs +8 -0
  15. package/lib/runner/template.mjs +13 -0
  16. package/lib/runtime/index.d.ts +43 -0
  17. package/lib/runtime/index.mjs +24 -0
  18. package/lib/runtime-src/k6/http-assertions.js +82 -0
  19. package/lib/shared/configured-steps.mjs +16 -0
  20. package/lib/ui/index.d.ts +118 -0
  21. package/lib/ui/index.mjs +21 -0
  22. package/lib/ui/provisioning.mjs +283 -0
  23. package/lib/ui/sandbox.mjs +250 -0
  24. package/node_modules/@elench/next-analysis/package.json +1 -1
  25. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  26. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  27. package/node_modules/@elench/ts-analysis/package.json +1 -1
  28. package/node_modules/es-toolkit/CHANGELOG.md +801 -0
  29. package/node_modules/es-toolkit/src/compat/_internal/Equals.d.ts +1 -0
  30. package/node_modules/es-toolkit/src/compat/_internal/IsWritable.d.ts +3 -0
  31. package/node_modules/es-toolkit/src/compat/_internal/MutableList.d.ts +4 -0
  32. package/node_modules/es-toolkit/src/compat/_internal/RejectReadonly.d.ts +4 -0
  33. package/node_modules/esprima/ChangeLog +235 -0
  34. package/package.json +5 -5
@@ -9,9 +9,9 @@ export interface DatabaseTemplateConfig {
9
9
 
10
10
  export interface DatabaseTemplateOptions {
11
11
  inputs?: string[];
12
- migrate?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[];
13
- seed?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[];
14
- verify?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[];
12
+ migrate?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
13
+ seed?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
14
+ verify?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
15
15
  }
16
16
 
17
17
  export interface DatabaseSourceSchemaOptions {
@@ -26,6 +26,7 @@ export interface DatabaseSourceSchemaConfig extends DatabaseSourceSchemaOptions
26
26
  }
27
27
 
28
28
  export interface TemplateStepBaseConfig {
29
+ args?: unknown;
29
30
  cwd?: string;
30
31
  inputs?: string[];
31
32
  }
@@ -50,6 +51,8 @@ export type TemplateLifecycleStepConfig =
50
51
  | TemplateSqlFileStepConfig
51
52
  | TemplateModuleStepConfig;
52
53
 
54
+ export interface TemplateLifecycleStepOptions extends TemplateStepBaseConfig {}
55
+
53
56
  export interface TscBuildConfig {
54
57
  kind: "tsc";
55
58
  cwd?: string;
@@ -160,6 +163,81 @@ export interface RegressionSyncConfig {
160
163
  cacheTtlSeconds?: number;
161
164
  }
162
165
 
166
+ export interface TestkitBoundaryLintConfig {
167
+ allowed?: string[];
168
+ severity?: "error" | "warn" | false;
169
+ trackedOnly?: boolean;
170
+ }
171
+
172
+ export interface TestkitLintConfig {
173
+ disable?: string[];
174
+ rules?: Partial<Record<
175
+ | "missingImports"
176
+ | "uiRuntimeImports"
177
+ | "uiSpecShape"
178
+ | "dalParallelSafety"
179
+ | "legacyHttpAssertions"
180
+ | "legacyDalAssertions"
181
+ | "configImports",
182
+ boolean
183
+ >> & {
184
+ testkitBoundary?: TestkitBoundaryLintConfig | boolean;
185
+ };
186
+ testkitBoundary?: TestkitBoundaryLintConfig | boolean;
187
+ ui?: {
188
+ maxLines?: number;
189
+ maxTests?: number;
190
+ };
191
+ }
192
+
193
+ export interface UiProvisionProfileConfig {
194
+ afterSignupSql?: string | string[];
195
+ email?: string;
196
+ emailDomain?: string;
197
+ loginBody?: Record<string, unknown>;
198
+ loginExpect?: number | number[];
199
+ loginPath?: string;
200
+ name?: string;
201
+ organizationName?: string;
202
+ password?: string;
203
+ sessionPath?: string;
204
+ signup?: false;
205
+ signupBody?: Record<string, unknown>;
206
+ signupExpect?: number | number[];
207
+ signupPath?: string;
208
+ }
209
+
210
+ export interface UiAuthConfig {
211
+ afterSignupSql?: string | string[];
212
+ authenticatedSelector?: string;
213
+ backendBaseUrl?: string;
214
+ browserState?: {
215
+ localStorage?: Record<string, unknown>;
216
+ };
217
+ databaseUrl?: string;
218
+ emailSelector?: string;
219
+ frontendBaseUrl?: string;
220
+ homePath?: string;
221
+ loginBody?: Record<string, unknown>;
222
+ loginExpect?: number | number[];
223
+ loginPagePath?: string;
224
+ loginPath?: string;
225
+ passwordSelector?: string;
226
+ profiles?: Record<string, UiProvisionProfileConfig>;
227
+ sessionPath?: string;
228
+ signup?: false;
229
+ signupBody?: Record<string, unknown>;
230
+ signupExpect?: number | number[];
231
+ signupPath?: string;
232
+ storage?: Record<string, unknown>;
233
+ storagePath?: string;
234
+ submitSelector?: string;
235
+ }
236
+
237
+ export interface UiConfig {
238
+ auth?: UiAuthConfig;
239
+ }
240
+
163
241
  export interface DiscoveryConfig {
164
242
  roots?: string[];
165
243
  exclude?: string[];
@@ -379,6 +457,8 @@ export interface NodeAppOptions extends Omit<ServiceConfig, "local" | "runtime"
379
457
  }
380
458
 
381
459
  export interface NextAppOptions extends Omit<ServiceConfig, "local" | "runtime" | "env"> {
460
+ api?: string | { baseUrl: string };
461
+ auth?: "disabled-clerk" | { kind: "disabled-clerk" };
382
462
  baseUrl?: string;
383
463
  build?: BuildConfig | null;
384
464
  buildInputs?: string[];
@@ -396,6 +476,7 @@ export interface NextAppOptions extends Omit<ServiceConfig, "local" | "runtime"
396
476
  export interface TestkitConfig {
397
477
  discovery?: DiscoveryConfig;
398
478
  execution?: TestkitExecutionConfig;
479
+ lint?: TestkitLintConfig;
399
480
  profiles?: {
400
481
  http?: Record<string, HttpSuiteConfig<any>>;
401
482
  };
@@ -411,6 +492,15 @@ export interface TestkitConfig {
411
492
  timeoutMs?: number;
412
493
  tokenEnv?: string;
413
494
  };
495
+ ui?: UiConfig;
496
+ }
497
+
498
+ export interface NodeNextPresetOptions {
499
+ fileTimeoutSeconds?: number;
500
+ install?: "require-host" | "download";
501
+ node?: string;
502
+ npm?: string;
503
+ workers?: number;
414
504
  }
415
505
 
416
506
  export declare function defineConfig<T extends TestkitConfig>(config: T): T;
@@ -423,22 +513,55 @@ export declare const database: {
423
513
  schema: {
424
514
  fromEnv(envName: string, options?: DatabaseSourceSchemaOptions): DatabaseSourceSchemaConfig;
425
515
  };
516
+ steps: {
517
+ materializePostgresBinding(options?: unknown): TemplateModuleStepConfig;
518
+ runSql(options?: unknown): TemplateModuleStepConfig;
519
+ seedRows(options?: unknown): TemplateModuleStepConfig;
520
+ updateRows(options?: unknown): TemplateModuleStepConfig;
521
+ verifyRows(options?: unknown): TemplateModuleStepConfig;
522
+ verifySeed(options?: unknown): TemplateModuleStepConfig;
523
+ };
524
+ values: {
525
+ env(name: string): unknown;
526
+ json(value: unknown): unknown;
527
+ now(): unknown;
528
+ postgresConnectionFromEnv(prefix: string): unknown;
529
+ };
426
530
  postgres(
427
531
  options?: Omit<LocalDatabaseConfig, "provider" | "template"> & {
532
+ inputs?: string[];
533
+ migrate?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
534
+ seed?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
428
535
  template?: DatabaseTemplateOptions;
536
+ verify?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
429
537
  }
430
538
  ): LocalDatabaseConfig;
431
539
  fixture(
432
540
  options?: Omit<LocalDatabaseConfig, "provider" | "template"> & {
541
+ inputs?: string[];
542
+ migrate?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
543
+ seed?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
433
544
  template?: DatabaseTemplateOptions;
545
+ verify?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
434
546
  discovery?: DiscoveryConfig;
435
547
  envFiles?: string[];
436
548
  }
437
549
  ): ServiceConfig;
438
550
  };
551
+ export declare const step: {
552
+ command(run: string, options?: TemplateLifecycleStepOptions): TemplateCommandStepConfig;
553
+ module(target: string, options?: TemplateLifecycleStepOptions): TemplateModuleStepConfig;
554
+ sqlFile(path: string, options?: TemplateLifecycleStepOptions): TemplateSqlFileStepConfig;
555
+ };
556
+ export declare const presets: {
557
+ nodeNext(options?: NodeNextPresetOptions): Pick<TestkitConfig, "execution" | "toolchains">;
558
+ };
439
559
  export declare const toolchain: {
440
560
  node(options?: NodeToolchainConfig): NodeToolchainConfig;
441
561
  };
562
+ export declare const ui: {
563
+ auth(options?: UiAuthConfig): UiConfig;
564
+ };
442
565
  export declare const auth: {
443
566
  fixture(options: { contract: JsonSessionContract; topology: AuthTopology }): AuthFixture;
444
567
  contracts: {
@@ -53,6 +53,38 @@ function buildDatabaseTemplateConfig(options = {}) {
53
53
  };
54
54
  }
55
55
 
56
+ function commandStep(run, options = {}) {
57
+ return {
58
+ kind: "command",
59
+ run,
60
+ ...copyStepOptions(options),
61
+ };
62
+ }
63
+
64
+ function sqlFileStep(filePath, options = {}) {
65
+ return {
66
+ kind: "sql-file",
67
+ path: filePath,
68
+ ...copyStepOptions(options),
69
+ };
70
+ }
71
+
72
+ function moduleStep(target, options = {}) {
73
+ return {
74
+ kind: "module",
75
+ target,
76
+ ...copyStepOptions(options),
77
+ };
78
+ }
79
+
80
+ function copyStepOptions(options = {}) {
81
+ return {
82
+ ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
83
+ ...(options.inputs !== undefined ? { inputs: [...options.inputs] } : {}),
84
+ ...(options.args !== undefined ? { args: options.args } : {}),
85
+ };
86
+ }
87
+
56
88
  function sourceSchemaFromEnv(envName, options = {}) {
57
89
  if (typeof envName !== "string" || envName.trim().length === 0) {
58
90
  throw new Error("database.schema.fromEnv(...) requires a non-empty env var name");
@@ -69,15 +101,65 @@ function sourceSchemaFromEnv(envName, options = {}) {
69
101
  };
70
102
  }
71
103
 
104
+ function databaseStepTarget(exportName) {
105
+ return `@elench/testkit/config/database-steps#${exportName}`;
106
+ }
107
+
108
+ function verifySeedStep(options = {}) {
109
+ return moduleStep(databaseStepTarget("verifySeed"), { args: options });
110
+ }
111
+
112
+ function verifyRowsStep(options = {}) {
113
+ return moduleStep(databaseStepTarget("verifyRows"), { args: options });
114
+ }
115
+
116
+ function runSqlStep(options = {}) {
117
+ return moduleStep(databaseStepTarget("runSql"), { args: options });
118
+ }
119
+
120
+ function seedRowsStep(options = {}) {
121
+ return moduleStep(databaseStepTarget("seedRows"), { args: options });
122
+ }
123
+
124
+ function materializePostgresBindingStep(options = {}) {
125
+ return moduleStep(databaseStepTarget("materializePostgresBinding"), { args: options });
126
+ }
127
+
128
+ function updateRowsStep(options = {}) {
129
+ return moduleStep(databaseStepTarget("updateRows"), { args: options });
130
+ }
131
+
132
+ function nowValue() {
133
+ return { kind: "now" };
134
+ }
135
+
136
+ function envValue(name) {
137
+ return { kind: "env", name: normalizeDatabaseEnvToken(name, "database.values.env(...) name", false) };
138
+ }
139
+
140
+ function jsonValue(value) {
141
+ return { kind: "json", value };
142
+ }
143
+
144
+ function postgresConnectionFromEnvValue(prefix) {
145
+ return {
146
+ kind: "postgres-connection-from-env",
147
+ prefix: normalizeDatabaseEnvToken(prefix, "database.values.postgresConnectionFromEnv(...) prefix", false),
148
+ };
149
+ }
150
+
72
151
  function postgresFixture(options = {}) {
73
- const { discovery, envFiles, template, ...databaseOptions } = options;
74
- for (const legacyKey of ["inputs", "schema", "migrate", "seed", "verify"]) {
152
+ const { discovery, envFiles, template, inputs, migrate, seed, verify, ...databaseOptions } = options;
153
+ for (const legacyKey of ["schema"]) {
75
154
  if (Object.prototype.hasOwnProperty.call(options, legacyKey)) {
76
155
  throw new Error(
77
- `database.fixture(...) no longer accepts top-level "${legacyKey}". Move lifecycle config under database.fixture({ template: { ... } }).`
156
+ `database.fixture(...) no longer accepts "${legacyKey}". Configure database.sourceSchema instead.`
78
157
  );
79
158
  }
80
159
  }
160
+ const resolvedTemplate = template || hasTemplateLifecycle(options)
161
+ ? buildDatabaseTemplateConfig({ inputs, migrate, seed, verify, ...(template || {}) })
162
+ : undefined;
81
163
  return {
82
164
  discovery: discovery || {
83
165
  roots: [".testkit-fixture"],
@@ -85,16 +167,22 @@ function postgresFixture(options = {}) {
85
167
  envFiles,
86
168
  local: false,
87
169
  database: postgresDatabase(
88
- template
170
+ resolvedTemplate
89
171
  ? {
90
172
  ...databaseOptions,
91
- template: buildDatabaseTemplateConfig(template),
173
+ template: resolvedTemplate,
92
174
  }
93
175
  : databaseOptions
94
176
  ),
95
177
  };
96
178
  }
97
179
 
180
+ function hasTemplateLifecycle(options = {}) {
181
+ return ["inputs", "migrate", "seed", "verify"].some((key) =>
182
+ Object.prototype.hasOwnProperty.call(options, key)
183
+ );
184
+ }
185
+
98
186
  function nodeToolchain(options = {}) {
99
187
  return {
100
188
  kind: "node",
@@ -102,6 +190,15 @@ function nodeToolchain(options = {}) {
102
190
  };
103
191
  }
104
192
 
193
+ function uiAuth(options = {}) {
194
+ return {
195
+ auth: {
196
+ ...options,
197
+ ...(options.profiles ? { profiles: { ...options.profiles } } : {}),
198
+ },
199
+ };
200
+ }
201
+
105
202
  function tscBuild(options = {}) {
106
203
  return {
107
204
  kind: "tsc",
@@ -195,6 +292,8 @@ function nodeApp(options = {}) {
195
292
 
196
293
  function nextApp(options = {}) {
197
294
  const {
295
+ api,
296
+ auth: authMode,
198
297
  baseUrl: explicitBaseUrl,
199
298
  browser,
200
299
  build: explicitBuild,
@@ -214,7 +313,10 @@ function nextApp(options = {}) {
214
313
  } = options;
215
314
 
216
315
  const normalizedPort = requiredNumber(port, "app.next port");
217
- const normalizedEnv = normalizePresetEnv(env);
316
+ const normalizedEnv = {
317
+ ...normalizeNextPresetEnv({ api, authMode }),
318
+ ...normalizePresetEnv(env),
319
+ };
218
320
  const baseUrl = explicitBaseUrl || "http://127.0.0.1:{port}";
219
321
  const build =
220
322
  explicitBuild === undefined
@@ -266,12 +368,26 @@ export const database = {
266
368
  schema: {
267
369
  fromEnv: sourceSchemaFromEnv,
268
370
  },
371
+ steps: {
372
+ materializePostgresBinding: materializePostgresBindingStep,
373
+ runSql: runSqlStep,
374
+ seedRows: seedRowsStep,
375
+ updateRows: updateRowsStep,
376
+ verifyRows: verifyRowsStep,
377
+ verifySeed: verifySeedStep,
378
+ },
379
+ values: {
380
+ env: envValue,
381
+ json: jsonValue,
382
+ now: nowValue,
383
+ postgresConnectionFromEnv: postgresConnectionFromEnvValue,
384
+ },
269
385
  postgres(options = {}) {
270
- const { template, sourceSchema, ...databaseOptions } = options;
271
- for (const legacyKey of ["inputs", "schema", "migrate", "seed", "verify"]) {
386
+ const { template, sourceSchema, inputs, migrate, seed, verify, ...databaseOptions } = options;
387
+ for (const legacyKey of ["schema"]) {
272
388
  if (Object.prototype.hasOwnProperty.call(options, legacyKey)) {
273
389
  throw new Error(
274
- `database.postgres(...) no longer accepts top-level "${legacyKey}". Move lifecycle config under database.postgres({ template: { ... } }).`
390
+ `database.postgres(...) no longer accepts "${legacyKey}". Use database.postgres({ sourceSchema: database.schema.fromEnv(...) }) instead.`
275
391
  );
276
392
  }
277
393
  }
@@ -281,11 +397,14 @@ export const database = {
281
397
  ...databaseOptions,
282
398
  sourceSchema,
283
399
  };
400
+ const resolvedTemplate = template || hasTemplateLifecycle(options)
401
+ ? buildDatabaseTemplateConfig({ inputs, migrate, seed, verify, ...(template || {}) })
402
+ : undefined;
284
403
  return postgresDatabase(
285
- template
404
+ resolvedTemplate
286
405
  ? {
287
406
  ...normalizedDatabaseOptions,
288
- template: buildDatabaseTemplateConfig(template),
407
+ template: resolvedTemplate,
289
408
  }
290
409
  : normalizedDatabaseOptions
291
410
  );
@@ -293,10 +412,38 @@ export const database = {
293
412
  fixture: postgresFixture,
294
413
  };
295
414
 
415
+ export const step = {
416
+ command: commandStep,
417
+ module: moduleStep,
418
+ sqlFile: sqlFileStep,
419
+ };
420
+
421
+ export const presets = {
422
+ nodeNext(options = {}) {
423
+ return {
424
+ execution: {
425
+ workers: options.workers ?? 1,
426
+ fileTimeoutSeconds: options.fileTimeoutSeconds ?? 120,
427
+ },
428
+ toolchains: {
429
+ node: nodeToolchain({
430
+ install: options.install ?? "download",
431
+ ...(options.node ? { node: options.node } : {}),
432
+ ...(options.npm ? { npm: options.npm } : {}),
433
+ }),
434
+ },
435
+ };
436
+ },
437
+ };
438
+
296
439
  export const toolchain = {
297
440
  node: nodeToolchain,
298
441
  };
299
442
 
443
+ export const ui = {
444
+ auth: uiAuth,
445
+ };
446
+
300
447
  export const auth = {
301
448
  fixture: createAuthFixture,
302
449
  contracts: {
@@ -383,6 +530,22 @@ function normalizeDatabaseEnvToken(value, label, sanitize = true) {
383
530
  return normalized;
384
531
  }
385
532
 
533
+ function normalizeNextPresetEnv({ api, authMode } = {}) {
534
+ const env = {};
535
+ if (api) {
536
+ const baseUrlToken = typeof api === "string" ? api : api.baseUrl;
537
+ if (baseUrlToken) {
538
+ env.NEXT_PUBLIC_API_URL = baseUrlToken;
539
+ }
540
+ }
541
+ if (authMode === "disabled-clerk" || authMode?.kind === "disabled-clerk") {
542
+ env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = "testkit_disabled";
543
+ env.CLERK_SECRET_KEY = "testkit_disabled";
544
+ env.CLERK_WEBHOOK_SECRET = "testkit_disabled";
545
+ }
546
+ return env;
547
+ }
548
+
386
549
  function resolveNodeAppStart(build, entry) {
387
550
  if (build?.kind === "tsc") {
388
551
  const compiled = compiledEntryFromSource(entry || build.entry || "src/index.ts", build.outDir || "dist");
@@ -393,7 +556,8 @@ function resolveNodeAppStart(build, entry) {
393
556
 
394
557
  function normalizeTemplateStepList(value) {
395
558
  if (value == null) return [];
396
- return Array.isArray(value) ? [...value] : [value];
559
+ const values = Array.isArray(value) ? value : [value];
560
+ return values.map((entry) => typeof entry === "string" ? commandStep(entry) : entry);
397
561
  }
398
562
 
399
563
  function compiledEntryFromSource(entry, outDir) {