@elench/testkit 0.1.114 → 0.1.116
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 +33 -8
- package/lib/cli/args.mjs +3 -3
- package/lib/cli/assistant/app.mjs +4 -2
- package/lib/cli/assistant/session.mjs +5 -1
- package/lib/cli/assistant/state.mjs +1 -2
- package/lib/cli/command-flags.mjs +4 -0
- package/lib/cli/commands/db/schema/refresh.mjs +21 -0
- package/lib/cli/commands/db/schema/verify.mjs +27 -0
- package/lib/cli/components/blocks/run-tree.mjs +7 -2
- package/lib/cli/components/hooks/use-element-layout.mjs +63 -0
- package/lib/cli/components/hooks/use-spinner-frame.mjs +26 -0
- package/lib/cli/entrypoint.mjs +1 -0
- package/lib/cli/operations/db/schema/refresh/operation.mjs +56 -0
- package/lib/cli/operations/db/{snapshot/capture → schema/verify}/operation.mjs +6 -27
- package/lib/cli/operations/run/operation.mjs +1 -0
- package/lib/cli/renderers/db-schema/text.mjs +7 -0
- package/lib/config/database.mjs +64 -0
- package/lib/config-api/index.d.ts +16 -1
- package/lib/config-api/index.mjs +31 -16
- package/lib/database/fingerprint.mjs +2 -0
- package/lib/database/index.mjs +142 -104
- package/lib/database/schema-source.mjs +295 -0
- package/lib/database/template-steps.mjs +158 -38
- package/lib/runner/orchestrator.mjs +4 -3
- package/lib/runner/template-steps.mjs +12 -1
- package/lib/runner/template.mjs +16 -1
- package/node_modules/@alcalzone/ansi-tokenize/README.md +0 -5
- package/node_modules/@alcalzone/ansi-tokenize/build/ansiCodes.d.ts +8 -0
- package/node_modules/@alcalzone/ansi-tokenize/build/ansiCodes.js +10 -8
- package/node_modules/@alcalzone/ansi-tokenize/build/ansiCodes.js.map +1 -1
- package/node_modules/@alcalzone/ansi-tokenize/build/tokenize.d.ts +1 -5
- package/node_modules/@alcalzone/ansi-tokenize/build/tokenize.js +9 -45
- package/node_modules/@alcalzone/ansi-tokenize/build/tokenize.js.map +1 -1
- package/node_modules/@alcalzone/ansi-tokenize/package.json +1 -1
- 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/node_modules/cli-boxes/index.d.ts +95 -90
- package/node_modules/cli-boxes/index.js +5 -2
- package/node_modules/cli-boxes/package.json +6 -13
- package/node_modules/cli-boxes/readme.md +15 -3
- package/node_modules/cli-truncate/index.d.ts +1 -1
- package/node_modules/cli-truncate/package.json +4 -4
- package/node_modules/cli-truncate/readme.md +1 -0
- package/node_modules/es-toolkit/CHANGELOG.md +801 -0
- package/node_modules/es-toolkit/src/compat/_internal/Equals.d.ts +1 -0
- package/node_modules/es-toolkit/src/compat/_internal/IsWritable.d.ts +3 -0
- package/node_modules/es-toolkit/src/compat/_internal/MutableList.d.ts +4 -0
- package/node_modules/es-toolkit/src/compat/_internal/RejectReadonly.d.ts +4 -0
- package/node_modules/esprima/ChangeLog +235 -0
- package/node_modules/ink/build/apply-styles.js +175 -0
- package/node_modules/ink/build/build-layout.js +77 -0
- package/node_modules/ink/build/calculate-wrapped-text.js +53 -0
- package/node_modules/ink/build/components/App.d.ts +1 -4
- package/node_modules/ink/build/components/App.js +22 -142
- package/node_modules/ink/build/components/App.js.map +1 -1
- package/node_modules/ink/build/components/AppContext.d.ts +3 -23
- package/node_modules/ink/build/components/AppContext.js +4 -7
- package/node_modules/ink/build/components/AppContext.js.map +1 -1
- package/node_modules/ink/build/components/Box.d.ts +3 -16
- package/node_modules/ink/build/components/Color.js +62 -0
- package/node_modules/ink/build/components/Cursor.d.ts +83 -0
- package/node_modules/ink/build/components/Cursor.js +53 -0
- package/node_modules/ink/build/components/Cursor.js.map +1 -0
- package/node_modules/ink/build/components/ErrorBoundary.d.ts +2 -2
- package/node_modules/ink/build/components/ErrorOverview.js +6 -6
- package/node_modules/ink/build/components/ErrorOverview.js.map +1 -1
- package/node_modules/ink/build/components/Static.js.map +1 -1
- package/node_modules/ink/build/components/StdinContext.d.ts +1 -7
- package/node_modules/ink/build/components/StdinContext.js +0 -1
- package/node_modules/ink/build/components/StdinContext.js.map +1 -1
- package/node_modules/ink/build/components/Text.d.ts +1 -1
- package/node_modules/ink/build/components/Text.js +1 -1
- package/node_modules/ink/build/components/Text.js.map +1 -1
- package/node_modules/ink/build/components/Transform.d.ts +1 -1
- package/node_modules/ink/build/devtools-window-polyfill.js +4 -7
- package/node_modules/ink/build/devtools-window-polyfill.js.map +1 -1
- package/node_modules/ink/build/devtools.js +6 -31
- package/node_modules/ink/build/devtools.js.map +1 -1
- package/node_modules/ink/build/dom.d.ts +1 -5
- package/node_modules/ink/build/dom.js +1 -20
- package/node_modules/ink/build/dom.js.map +1 -1
- package/node_modules/ink/build/experimental/apply-style.js +140 -0
- package/node_modules/ink/build/experimental/dom.js +123 -0
- package/node_modules/ink/build/experimental/output.js +91 -0
- package/node_modules/ink/build/experimental/reconciler.js +141 -0
- package/node_modules/ink/build/experimental/renderer.js +81 -0
- package/node_modules/ink/build/hooks/use-app.d.ts +1 -1
- package/node_modules/ink/build/hooks/use-app.js +1 -1
- package/node_modules/ink/build/hooks/use-cursor.d.ts +1 -1
- package/node_modules/ink/build/hooks/use-cursor.js +1 -1
- package/node_modules/ink/build/hooks/use-focus-manager.d.ts +2 -17
- package/node_modules/ink/build/hooks/use-focus-manager.js +1 -2
- package/node_modules/ink/build/hooks/use-focus-manager.js.map +1 -1
- package/node_modules/ink/build/hooks/use-focus.d.ts +1 -2
- package/node_modules/ink/build/hooks/use-focus.js +4 -5
- package/node_modules/ink/build/hooks/use-focus.js.map +1 -1
- package/node_modules/ink/build/hooks/use-input.d.ts +1 -2
- package/node_modules/ink/build/hooks/use-input.js +80 -82
- package/node_modules/ink/build/hooks/use-input.js.map +1 -1
- package/node_modules/ink/build/hooks/use-is-screen-reader-enabled.d.ts +1 -2
- package/node_modules/ink/build/hooks/use-is-screen-reader-enabled.js +1 -2
- package/node_modules/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -1
- package/node_modules/ink/build/hooks/use-stderr.d.ts +1 -1
- package/node_modules/ink/build/hooks/use-stderr.js +1 -1
- package/node_modules/ink/build/hooks/use-stdin.d.ts +2 -4
- package/node_modules/ink/build/hooks/use-stdin.js +1 -2
- package/node_modules/ink/build/hooks/use-stdin.js.map +1 -1
- package/node_modules/ink/build/hooks/use-stdout.d.ts +1 -1
- package/node_modules/ink/build/hooks/use-stdout.js +1 -1
- package/node_modules/ink/build/hooks/useInput.js +38 -0
- package/node_modules/ink/build/index.d.ts +1 -8
- package/node_modules/ink/build/index.js +0 -4
- package/node_modules/ink/build/index.js.map +1 -1
- package/node_modules/ink/build/ink.d.ts +3 -48
- package/node_modules/ink/build/ink.js +155 -325
- package/node_modules/ink/build/ink.js.map +1 -1
- package/node_modules/ink/build/input-parser.d.ts +1 -4
- package/node_modules/ink/build/input-parser.js +30 -70
- package/node_modules/ink/build/input-parser.js.map +1 -1
- package/node_modules/ink/build/instance.js +205 -0
- package/node_modules/ink/build/layout.d.ts +7 -0
- package/node_modules/ink/build/layout.js +33 -0
- package/node_modules/ink/build/layout.js.map +1 -0
- package/node_modules/ink/build/log-update.d.ts +0 -1
- package/node_modules/ink/build/log-update.js +1 -13
- package/node_modules/ink/build/log-update.js.map +1 -1
- package/node_modules/ink/build/measure-element.d.ts +0 -4
- package/node_modules/ink/build/measure-element.js +0 -4
- package/node_modules/ink/build/measure-element.js.map +1 -1
- package/node_modules/ink/build/options.d.ts +52 -0
- package/node_modules/ink/build/options.js +2 -0
- package/node_modules/ink/build/options.js.map +1 -0
- package/node_modules/ink/build/output.js +0 -25
- package/node_modules/ink/build/output.js.map +1 -1
- package/node_modules/ink/build/parse-keypress.d.ts +3 -1
- package/node_modules/ink/build/parse-keypress.js +17 -19
- package/node_modules/ink/build/parse-keypress.js.map +1 -1
- package/node_modules/ink/build/reconciler.js +27 -46
- package/node_modules/ink/build/reconciler.js.map +1 -1
- package/node_modules/ink/build/render-border.js +18 -29
- package/node_modules/ink/build/render-border.js.map +1 -1
- package/node_modules/ink/build/render-to-string.js +1 -2
- package/node_modules/ink/build/render-to-string.js.map +1 -1
- package/node_modules/ink/build/render.d.ts +2 -57
- package/node_modules/ink/build/render.js +11 -18
- package/node_modules/ink/build/render.js.map +1 -1
- package/node_modules/ink/build/screen-reader-update.d.ts +13 -0
- package/node_modules/ink/build/screen-reader-update.js +38 -0
- package/node_modules/ink/build/screen-reader-update.js.map +1 -0
- package/node_modules/ink/build/styles.d.ts +16 -78
- package/node_modules/ink/build/styles.js +31 -102
- package/node_modules/ink/build/styles.js.map +1 -1
- package/node_modules/ink/build/utils.d.ts +2 -9
- package/node_modules/ink/build/utils.js +3 -18
- package/node_modules/ink/build/utils.js.map +1 -1
- package/node_modules/ink/build/wrap-text.js +0 -7
- package/node_modules/ink/build/wrap-text.js.map +1 -1
- package/node_modules/ink/build/write-synchronized.d.ts +1 -1
- package/node_modules/ink/build/write-synchronized.js +2 -4
- package/node_modules/ink/build/write-synchronized.js.map +1 -1
- package/node_modules/ink/node_modules/emoji-regex/LICENSE-MIT.txt +20 -0
- package/node_modules/ink/node_modules/emoji-regex/README.md +107 -0
- package/node_modules/ink/node_modules/emoji-regex/index.d.ts +3 -0
- package/node_modules/ink/node_modules/emoji-regex/index.js +4 -0
- package/node_modules/ink/node_modules/emoji-regex/index.mjs +4 -0
- package/node_modules/ink/node_modules/emoji-regex/package.json +45 -0
- package/node_modules/{wrap-ansi → ink/node_modules/wrap-ansi}/index.d.ts +1 -1
- package/node_modules/ink/node_modules/wrap-ansi/index.js +222 -0
- package/node_modules/ink/node_modules/wrap-ansi/node_modules/string-width/index.d.ts +39 -0
- package/node_modules/ink/node_modules/wrap-ansi/node_modules/string-width/index.js +82 -0
- package/node_modules/ink/node_modules/wrap-ansi/node_modules/string-width/license +9 -0
- package/node_modules/ink/node_modules/wrap-ansi/node_modules/string-width/package.json +64 -0
- package/node_modules/ink/node_modules/wrap-ansi/node_modules/string-width/readme.md +66 -0
- package/node_modules/{wrap-ansi → ink/node_modules/wrap-ansi}/package.json +11 -11
- package/node_modules/{wrap-ansi → ink/node_modules/wrap-ansi}/readme.md +0 -2
- package/node_modules/ink/package.json +98 -34
- package/node_modules/ink/readme.md +48 -554
- package/node_modules/slice-ansi/index.d.ts +1 -1
- package/node_modules/slice-ansi/index.js +89 -146
- package/node_modules/slice-ansi/package.json +5 -5
- package/node_modules/slice-ansi/readme.md +0 -1
- package/node_modules/slice-ansi/tokenize-ansi.js +1 -1
- package/package.json +14 -10
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts +188 -0
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts.map +1 -0
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js +293 -0
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js.map +1 -0
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/package.json +25 -0
- package/lib/cli/commands/db/snapshot/capture.mjs +0 -26
- package/lib/cli/renderers/db-snapshot-capture/text.mjs +0 -3
- package/node_modules/@alcalzone/ansi-tokenize/build/consts.d.ts +0 -17
- package/node_modules/@alcalzone/ansi-tokenize/build/consts.js +0 -28
- package/node_modules/@alcalzone/ansi-tokenize/build/consts.js.map +0 -1
- package/node_modules/ink/build/components/AnimationContext.d.ts +0 -9
- package/node_modules/ink/build/components/AnimationContext.js +0 -13
- package/node_modules/ink/build/components/AnimationContext.js.map +0 -1
- package/node_modules/ink/build/hooks/use-animation.d.ts +0 -49
- package/node_modules/ink/build/hooks/use-animation.js +0 -87
- package/node_modules/ink/build/hooks/use-animation.js.map +0 -1
- package/node_modules/ink/build/hooks/use-box-metrics.d.ts +0 -59
- package/node_modules/ink/build/hooks/use-box-metrics.js +0 -88
- package/node_modules/ink/build/hooks/use-box-metrics.js.map +0 -1
- package/node_modules/ink/build/hooks/use-paste.d.ts +0 -35
- package/node_modules/ink/build/hooks/use-paste.js +0 -62
- package/node_modules/ink/build/hooks/use-paste.js.map +0 -1
- package/node_modules/ink/build/hooks/use-window-size.d.ts +0 -18
- package/node_modules/ink/build/hooks/use-window-size.js +0 -22
- package/node_modules/ink/build/hooks/use-window-size.js.map +0 -1
- package/node_modules/wrap-ansi/index.js +0 -468
- /package/node_modules/{wrap-ansi → ink/node_modules/wrap-ansi}/license +0 -0
package/README.md
CHANGED
|
@@ -70,8 +70,9 @@ npx @elench/testkit assistant --message "/logs api"
|
|
|
70
70
|
# Automatic regression intelligence
|
|
71
71
|
# Configure testkit.regressions.json and testkit classifies new vs known regressions automatically during runs
|
|
72
72
|
|
|
73
|
-
#
|
|
74
|
-
npx @elench/testkit db
|
|
73
|
+
# Diagnostics: refresh or verify the source schema cache
|
|
74
|
+
npx @elench/testkit db schema refresh --service api
|
|
75
|
+
npx @elench/testkit db schema verify --service api
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
`testkit` is assistant-first in an interactive TTY. The interactive assistant
|
|
@@ -251,9 +252,10 @@ export default defineConfig({
|
|
|
251
252
|
port: 3004,
|
|
252
253
|
envFiles: [".env.testkit"],
|
|
253
254
|
database: database.postgres({
|
|
255
|
+
sourceSchema: database.schema.fromEnv("PRODUCTION_DATABASE_URL"),
|
|
254
256
|
template: {
|
|
255
257
|
inputs: ["db/schema.sql", "scripts/seed.ts"],
|
|
256
|
-
|
|
258
|
+
migrate: [{ kind: "sql-file", path: "db/schema.sql" }],
|
|
257
259
|
seed: [{ kind: "command", run: "npm run db:seed" }],
|
|
258
260
|
verify: [{ kind: "module", target: "src/testkit/verify-seed.ts#verifySeed" }],
|
|
259
261
|
},
|
|
@@ -306,8 +308,8 @@ for:
|
|
|
306
308
|
- repo-managed Node toolchains for prepare/start commands
|
|
307
309
|
- one-time runtime preparation steps for stable shared servers
|
|
308
310
|
- local DB binding configuration
|
|
311
|
+
- source-backed schema verification
|
|
309
312
|
- template database migrate / seed / verify stages
|
|
310
|
-
- template schema snapshot capture
|
|
311
313
|
- explicit per-file or per-suite locks
|
|
312
314
|
- named HTTP suite profiles
|
|
313
315
|
- automatic regression classification for new vs known failures
|
|
@@ -322,12 +324,28 @@ right way to move expensive browser targets from `next dev` / watch mode to
|
|
|
322
324
|
stable build-and-start flows.
|
|
323
325
|
|
|
324
326
|
`database.template` is the database-side equivalent for reusable template DB
|
|
325
|
-
state.
|
|
327
|
+
state. When `database.sourceSchema` is configured, Testkit treats the configured
|
|
328
|
+
source database as the schema source of truth. A normal `testkit run` refreshes
|
|
329
|
+
`.testkit/db/<service>/source-schema.sql` from the source database when needed,
|
|
330
|
+
applies that cached schema to the local template DB, runs local template setup,
|
|
331
|
+
and verifies that the replayed local schema still matches the source dump. If
|
|
332
|
+
local replay differs, Testkit refreshes from the source once and retries. If it
|
|
333
|
+
still differs, the run fails with schema diagnostics under
|
|
334
|
+
`.testkit/results/schema`.
|
|
335
|
+
|
|
336
|
+
Template setup executes in three explicit phases:
|
|
326
337
|
|
|
327
338
|
- `migrate`
|
|
328
339
|
- `seed`
|
|
329
340
|
- `verify`
|
|
330
341
|
|
|
342
|
+
Schema drift is checked after each successful `migrate` and `seed` step, and
|
|
343
|
+
`verify` only runs once local replay matches the source schema. Source refreshes
|
|
344
|
+
use `pg_dump --schema-only --no-owner --no-privileges`, so seed/reference data
|
|
345
|
+
is never written into the baseline. Keep schema-changing setup in its own step
|
|
346
|
+
where possible; a single command that changes schema and then fails before
|
|
347
|
+
exiting cannot be refreshed at the midpoint.
|
|
348
|
+
|
|
331
349
|
For most repos, prefer declarative step objects directly inside
|
|
332
350
|
`database.postgres({ template: ... })` and `runtime.prepare.steps`.
|
|
333
351
|
The supported shapes are:
|
|
@@ -667,10 +685,15 @@ services that define `database: database.postgres(...)`.
|
|
|
667
685
|
- template databases are cached
|
|
668
686
|
- runtime databases are cloned from templates when binding is `per-runtime`
|
|
669
687
|
- shared databases are reused when binding is `shared`
|
|
670
|
-
-
|
|
671
|
-
|
|
688
|
+
- source schema caches are refreshed only from the configured source database
|
|
689
|
+
- template fingerprints are derived automatically from env files, source schema
|
|
690
|
+
cache, migrate/seed config, and repo contents
|
|
672
691
|
|
|
673
|
-
|
|
692
|
+
`db schema refresh` forces a source database dump into the `.testkit` source
|
|
693
|
+
schema cache. `db schema verify` prepares local templates and verifies local
|
|
694
|
+
replay against the cached/refreshed source schema. `--skip-schema-source-verify`
|
|
695
|
+
is available as a narrow escape hatch when users need to run tests while schema
|
|
696
|
+
verification is temporarily blocked.
|
|
674
697
|
|
|
675
698
|
## Development Tests
|
|
676
699
|
|
|
@@ -679,4 +702,6 @@ npm test
|
|
|
679
702
|
npm run test:unit
|
|
680
703
|
npm run test:integration
|
|
681
704
|
npm run test:system
|
|
705
|
+
npm run test:live:neon
|
|
706
|
+
npm run test:database-version:compat
|
|
682
707
|
```
|
package/lib/cli/args.mjs
CHANGED
|
@@ -16,11 +16,11 @@ export function resolveCliSelection({ first, second, third }) {
|
|
|
16
16
|
let dbAction = null;
|
|
17
17
|
|
|
18
18
|
if (first === "db") {
|
|
19
|
-
if (second === "
|
|
20
|
-
dbAction =
|
|
19
|
+
if (second === "schema" && (third === "refresh" || third === "verify")) {
|
|
20
|
+
dbAction = `schema-${third}`;
|
|
21
21
|
return { lifecycle, positionalType, dbAction };
|
|
22
22
|
}
|
|
23
|
-
throw new Error('Unknown db command. Expected "db
|
|
23
|
+
throw new Error('Unknown db command. Expected "db schema refresh" or "db schema verify".');
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
if (second || third) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { createElement, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { Box, Text, useApp,
|
|
2
|
+
import { Box, Text, useApp, useCursor, useInput, useStdout } from "ink";
|
|
3
3
|
import { bold, cyan, dim, green, red, yellow } from "../terminal/colors.mjs";
|
|
4
4
|
import { RunTreeView } from "../components/blocks/run-tree.mjs";
|
|
5
|
+
import { useElementLayout } from "../components/hooks/use-element-layout.mjs";
|
|
5
6
|
import { colorCodeLine } from "./code-block.mjs";
|
|
6
7
|
import { getComposerDisplayModel } from "./composer.mjs";
|
|
7
8
|
import { MarkdownBlock } from "./markdown-block.mjs";
|
|
@@ -370,7 +371,8 @@ function colorCommandStatus(block, status) {
|
|
|
370
371
|
|
|
371
372
|
function ComposerBar({ view, busy }) {
|
|
372
373
|
const ref = useRef(null);
|
|
373
|
-
const
|
|
374
|
+
const { stdout } = useStdout();
|
|
375
|
+
const metrics = useElementLayout(ref, { stdout });
|
|
374
376
|
const { setCursorPosition } = useCursor();
|
|
375
377
|
const display = getComposerDisplayModel(
|
|
376
378
|
{
|
|
@@ -81,7 +81,11 @@ export async function runAssistantConversationTurn({
|
|
|
81
81
|
onEvent(event) {
|
|
82
82
|
appendProviderTrace(tracePath, event);
|
|
83
83
|
onProviderEvent?.(event);
|
|
84
|
-
if (
|
|
84
|
+
if (
|
|
85
|
+
event.display !== false &&
|
|
86
|
+
!event.transient &&
|
|
87
|
+
(event.type === "status" || event.type === "tool-start" || event.type === "tool-update" || event.type === "tool-end")
|
|
88
|
+
) {
|
|
85
89
|
onStatus?.(formatProviderEvent(event));
|
|
86
90
|
}
|
|
87
91
|
},
|
|
@@ -757,14 +757,13 @@ function createProviderTurnState() {
|
|
|
757
757
|
assistantMessageId: null,
|
|
758
758
|
assistantMessageIdsByProviderItem: new Map(),
|
|
759
759
|
providerToolMessageIdsByProviderItem: new Map(),
|
|
760
|
-
|
|
760
|
+
lastActivityText: null,
|
|
761
761
|
};
|
|
762
762
|
}
|
|
763
763
|
|
|
764
764
|
function handleProviderEvent(turn, event, { appendMessage, updateMessage, setStatus } = {}) {
|
|
765
765
|
if (!event) return;
|
|
766
766
|
if (event.transient || event.display === false) {
|
|
767
|
-
if (event.type === "status" && event.text) setStatus?.(event.text);
|
|
768
767
|
return;
|
|
769
768
|
}
|
|
770
769
|
if (event.type === "assistant-delta") {
|
|
@@ -48,6 +48,10 @@ export const runFlags = {
|
|
|
48
48
|
description: "Run files even if testkit.config.ts marks them skipped",
|
|
49
49
|
default: false,
|
|
50
50
|
}),
|
|
51
|
+
"skip-schema-source-verify": Flags.boolean({
|
|
52
|
+
description: "Skip the check that local schema replay matches the source database schema",
|
|
53
|
+
default: false,
|
|
54
|
+
}),
|
|
51
55
|
"output-mode": Flags.string({
|
|
52
56
|
description: "Reporter mode",
|
|
53
57
|
options: ["compact", "debug", "events"],
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import { sharedFlags } from "../../../command-flags.mjs";
|
|
3
|
+
import { executeDatabaseSchemaRefreshOperation } from "../../../operations/db/schema/refresh/operation.mjs";
|
|
4
|
+
import { renderDatabaseSchemaRefreshResult } from "../../../renderers/db-schema/text.mjs";
|
|
5
|
+
|
|
6
|
+
export default class DbSchemaRefreshCommand extends Command {
|
|
7
|
+
static summary = "Refresh a source database schema cache";
|
|
8
|
+
|
|
9
|
+
static enableJsonFlag = true;
|
|
10
|
+
|
|
11
|
+
static flags = sharedFlags;
|
|
12
|
+
|
|
13
|
+
async run() {
|
|
14
|
+
const { flags } = await this.parse(DbSchemaRefreshCommand);
|
|
15
|
+
const result = await executeDatabaseSchemaRefreshOperation(flags);
|
|
16
|
+
if (!this.jsonEnabled()) {
|
|
17
|
+
for (const line of renderDatabaseSchemaRefreshResult(result)) this.log(line);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command, Flags } from "@oclif/core";
|
|
2
|
+
import { sharedFlags } from "../../../command-flags.mjs";
|
|
3
|
+
import { executeDatabaseSchemaVerifyOperation } from "../../../operations/db/schema/verify/operation.mjs";
|
|
4
|
+
import { renderDatabaseSchemaVerifyResult } from "../../../renderers/db-schema/text.mjs";
|
|
5
|
+
|
|
6
|
+
export default class DbSchemaVerifyCommand extends Command {
|
|
7
|
+
static summary = "Verify local schema replay against the source database schema";
|
|
8
|
+
|
|
9
|
+
static enableJsonFlag = true;
|
|
10
|
+
|
|
11
|
+
static flags = {
|
|
12
|
+
...sharedFlags,
|
|
13
|
+
"skip-schema-source-verify": Flags.boolean({
|
|
14
|
+
description: "Skip the check that local schema replay matches the source database schema",
|
|
15
|
+
default: false,
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
async run() {
|
|
20
|
+
const { flags } = await this.parse(DbSchemaVerifyCommand);
|
|
21
|
+
const result = await executeDatabaseSchemaVerifyOperation(flags);
|
|
22
|
+
if (!this.jsonEnabled()) {
|
|
23
|
+
for (const line of renderDatabaseSchemaVerifyResult(result)) this.log(line);
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { createElement, useEffect, useMemo, useState } from "react";
|
|
2
|
-
import { Box, Text,
|
|
2
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
3
3
|
import figures from "figures";
|
|
4
4
|
import { formatDuration } from "../../../runner/formatting.mjs";
|
|
5
5
|
import {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { renderSummaryBox } from "../primitives/summary-box.mjs";
|
|
15
15
|
import { getTerminalWidth } from "../../terminal/layout.mjs";
|
|
16
16
|
import { renderFailureDetail, renderPassedDetail } from "../../renderers/run/inline-detail.mjs";
|
|
17
|
+
import { useSpinnerFrame } from "../hooks/use-spinner-frame.mjs";
|
|
17
18
|
|
|
18
19
|
const SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
19
20
|
|
|
@@ -27,7 +28,11 @@ export function RunTreeView({
|
|
|
27
28
|
const { exit } = useApp();
|
|
28
29
|
const controlledSnapshot = Boolean(snapshotOverride);
|
|
29
30
|
const [snapshot, setSnapshot] = useState(() => snapshotOverride || runState.getSnapshot());
|
|
30
|
-
const
|
|
31
|
+
const frame = useSpinnerFrame({
|
|
32
|
+
frameCount: SPINNER_FRAMES.length,
|
|
33
|
+
intervalMs: 80,
|
|
34
|
+
isActive: !controlledSnapshot && !snapshot.finished,
|
|
35
|
+
});
|
|
31
36
|
const spinnerFrame = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
|
|
32
37
|
|
|
33
38
|
useEffect(() => {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
|
|
3
|
+
const EMPTY_LAYOUT = Object.freeze({
|
|
4
|
+
width: 0,
|
|
5
|
+
height: 0,
|
|
6
|
+
left: 0,
|
|
7
|
+
top: 0,
|
|
8
|
+
hasMeasured: false,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export function readElementLayout(ref) {
|
|
12
|
+
const node = ref?.current || null;
|
|
13
|
+
const rawLayout = node?.yogaNode?.getComputedLayout?.() || null;
|
|
14
|
+
if (!node || !rawLayout) return EMPTY_LAYOUT;
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
width: normalizeLayoutNumber(rawLayout.width),
|
|
18
|
+
height: normalizeLayoutNumber(rawLayout.height),
|
|
19
|
+
left: normalizeLayoutNumber(rawLayout.left),
|
|
20
|
+
top: normalizeLayoutNumber(rawLayout.top),
|
|
21
|
+
hasMeasured: true,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function layoutsEqual(left, right) {
|
|
26
|
+
return (
|
|
27
|
+
left.width === right.width &&
|
|
28
|
+
left.height === right.height &&
|
|
29
|
+
left.left === right.left &&
|
|
30
|
+
left.top === right.top &&
|
|
31
|
+
left.hasMeasured === right.hasMeasured
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useElementLayout(ref, { stdout } = {}) {
|
|
36
|
+
const [layout, setLayout] = useState(EMPTY_LAYOUT);
|
|
37
|
+
|
|
38
|
+
const updateLayout = useCallback(() => {
|
|
39
|
+
const nextLayout = readElementLayout(ref);
|
|
40
|
+
setLayout((currentLayout) => layoutsEqual(currentLayout, nextLayout) ? currentLayout : nextLayout);
|
|
41
|
+
}, [ref]);
|
|
42
|
+
|
|
43
|
+
useEffect(updateLayout);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!stdout?.on) return undefined;
|
|
47
|
+
|
|
48
|
+
stdout.on("resize", updateLayout);
|
|
49
|
+
return () => {
|
|
50
|
+
if (stdout.off) {
|
|
51
|
+
stdout.off("resize", updateLayout);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
stdout.removeListener?.("resize", updateLayout);
|
|
55
|
+
};
|
|
56
|
+
}, [stdout, updateLayout]);
|
|
57
|
+
|
|
58
|
+
return useMemo(() => layout, [layout]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeLayoutNumber(value) {
|
|
62
|
+
return Number.isFinite(value) ? value : 0;
|
|
63
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export function nextSpinnerFrame(frame, frameCount) {
|
|
4
|
+
const normalizedFrameCount = Number.isInteger(frameCount) && frameCount > 0 ? frameCount : 1;
|
|
5
|
+
return (frame + 1) % normalizedFrameCount;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function useSpinnerFrame({
|
|
9
|
+
frameCount,
|
|
10
|
+
intervalMs = 80,
|
|
11
|
+
isActive = true,
|
|
12
|
+
} = {}) {
|
|
13
|
+
const [frame, setFrame] = useState(0);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!isActive) return undefined;
|
|
17
|
+
|
|
18
|
+
const timer = setInterval(() => {
|
|
19
|
+
setFrame((currentFrame) => nextSpinnerFrame(currentFrame, frameCount));
|
|
20
|
+
}, intervalMs);
|
|
21
|
+
|
|
22
|
+
return () => clearInterval(timer);
|
|
23
|
+
}, [frameCount, intervalMs, isActive]);
|
|
24
|
+
|
|
25
|
+
return frame;
|
|
26
|
+
}
|
package/lib/cli/entrypoint.mjs
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { loadManagedConfigs, resolveTargetConfig, collectRequiredConfigs, topologicallySortConfigs } from "../../../../../app/configs.mjs";
|
|
5
|
+
import { resolveProductDir } from "../../../../../config/index.mjs";
|
|
6
|
+
import { prepareDatabaseRuntime } from "../../../../../database/index.mjs";
|
|
7
|
+
import { forceRefreshSourceSchemaCache, getSourceSchemaCachePath } from "../../../../../database/schema-source.mjs";
|
|
8
|
+
import { createRunReporter } from "../../../../renderers/run/text-reporter.mjs";
|
|
9
|
+
import { createRunLogRegistry } from "../../../../../runner/logs.mjs";
|
|
10
|
+
import { createSetupOperationRegistry } from "../../../../../runner/setup-operations.mjs";
|
|
11
|
+
import { resolveRuntimeInstanceConfigs } from "../../../../../runner/template.mjs";
|
|
12
|
+
|
|
13
|
+
export async function executeDatabaseSchemaRefreshOperation(options = {}) {
|
|
14
|
+
const productDir = resolveProductDir(process.cwd(), options.dir);
|
|
15
|
+
const { configs } = await loadManagedConfigs({ dir: productDir });
|
|
16
|
+
const target = resolveTargetConfig(configs, options.service);
|
|
17
|
+
const selectedConfigs = collectRequiredConfigs(configs, target.name);
|
|
18
|
+
const runtimeRoot = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-schema-refresh-"));
|
|
19
|
+
const runtimeDir = path.join(runtimeRoot, "runtime-1");
|
|
20
|
+
const resolvedConfigs = resolveRuntimeInstanceConfigs(selectedConfigs, "runtime-1", runtimeDir, {
|
|
21
|
+
graphDirName: `schema-refresh-${target.name}`,
|
|
22
|
+
portNamespaceIndex: 0,
|
|
23
|
+
portNamespaceStride: 1,
|
|
24
|
+
});
|
|
25
|
+
const resolvedTarget = resolvedConfigs.find((config) => config.name === target.name);
|
|
26
|
+
if (!resolvedTarget) throw new Error(`Resolved runtime config missing target service "${target.name}"`);
|
|
27
|
+
|
|
28
|
+
const reporter = createRunReporter({ outputMode: options.debug ? "debug" : "compact" });
|
|
29
|
+
const logRegistry = createRunLogRegistry(productDir);
|
|
30
|
+
const setupRegistry = createSetupOperationRegistry({ logRegistry });
|
|
31
|
+
try {
|
|
32
|
+
for (const config of topologicallySortConfigs(resolvedConfigs)) {
|
|
33
|
+
if (config.name === resolvedTarget.name) break;
|
|
34
|
+
if (config.testkit.database?.provider === "local") {
|
|
35
|
+
await prepareDatabaseRuntime(config, { reporter, logRegistry, setupRegistry });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const state = await forceRefreshSourceSchemaCache(resolvedTarget, null, {
|
|
39
|
+
reporter,
|
|
40
|
+
logRegistry,
|
|
41
|
+
setupRegistry,
|
|
42
|
+
});
|
|
43
|
+
const outputPath = getSourceSchemaCachePath(resolvedTarget);
|
|
44
|
+
return {
|
|
45
|
+
ok: true,
|
|
46
|
+
productDir,
|
|
47
|
+
service: target.name,
|
|
48
|
+
outputPath,
|
|
49
|
+
outputLabel: path.relative(productDir, outputPath) || path.basename(outputPath),
|
|
50
|
+
envName: state.envName || null,
|
|
51
|
+
};
|
|
52
|
+
} finally {
|
|
53
|
+
logRegistry.closeAll();
|
|
54
|
+
fs.rmSync(runtimeRoot, { recursive: true, force: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -3,69 +3,48 @@ import os from "os";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { loadManagedConfigs, resolveTargetConfig, collectRequiredConfigs, topologicallySortConfigs } from "../../../../../app/configs.mjs";
|
|
5
5
|
import { resolveProductDir } from "../../../../../config/index.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { prepareDatabaseRuntime } from "../../../../../database/index.mjs";
|
|
7
7
|
import { createRunReporter } from "../../../../renderers/run/text-reporter.mjs";
|
|
8
8
|
import { createRunLogRegistry } from "../../../../../runner/logs.mjs";
|
|
9
9
|
import { createSetupOperationRegistry } from "../../../../../runner/setup-operations.mjs";
|
|
10
10
|
import { resolveRuntimeInstanceConfigs } from "../../../../../runner/template.mjs";
|
|
11
11
|
|
|
12
|
-
export async function
|
|
12
|
+
export async function executeDatabaseSchemaVerifyOperation(options = {}) {
|
|
13
13
|
const productDir = resolveProductDir(process.cwd(), options.dir);
|
|
14
14
|
const { configs } = await loadManagedConfigs({ dir: productDir });
|
|
15
15
|
const target = resolveTargetConfig(configs, options.service);
|
|
16
|
-
const outputPath = normalizeOptionalString(options.output);
|
|
17
|
-
if (!outputPath) {
|
|
18
|
-
throw new Error("Snapshot capture requires --output");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
16
|
const selectedConfigs = collectRequiredConfigs(configs, target.name);
|
|
22
|
-
const runtimeRoot = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-
|
|
17
|
+
const runtimeRoot = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-schema-verify-"));
|
|
23
18
|
const runtimeDir = path.join(runtimeRoot, "runtime-1");
|
|
24
19
|
const resolvedConfigs = resolveRuntimeInstanceConfigs(selectedConfigs, "runtime-1", runtimeDir, {
|
|
25
|
-
graphDirName: `
|
|
20
|
+
graphDirName: `schema-verify-${target.name}`,
|
|
26
21
|
portNamespaceIndex: 0,
|
|
27
22
|
portNamespaceStride: 1,
|
|
28
23
|
});
|
|
29
24
|
const resolvedTarget = resolvedConfigs.find((config) => config.name === target.name);
|
|
30
|
-
if (!resolvedTarget) {
|
|
31
|
-
throw new Error(`Resolved runtime config missing target service "${target.name}"`);
|
|
32
|
-
}
|
|
25
|
+
if (!resolvedTarget) throw new Error(`Resolved runtime config missing target service "${target.name}"`);
|
|
33
26
|
|
|
34
|
-
const absoluteOutputPath = path.resolve(productDir, outputPath);
|
|
35
27
|
const reporter = createRunReporter({ outputMode: options.debug ? "debug" : "compact" });
|
|
36
28
|
const logRegistry = createRunLogRegistry(productDir);
|
|
37
29
|
const setupRegistry = createSetupOperationRegistry({ logRegistry });
|
|
38
30
|
try {
|
|
39
31
|
for (const config of topologicallySortConfigs(resolvedConfigs)) {
|
|
40
|
-
if (config.name === resolvedTarget.name) continue;
|
|
41
32
|
if (config.testkit.database?.provider === "local") {
|
|
42
33
|
await prepareDatabaseRuntime(config, {
|
|
43
34
|
reporter,
|
|
44
35
|
logRegistry,
|
|
45
36
|
setupRegistry,
|
|
37
|
+
skipSchemaSourceVerify: options["skip-schema-source-verify"],
|
|
46
38
|
});
|
|
47
39
|
}
|
|
48
40
|
}
|
|
49
|
-
await captureDatabaseTemplateSnapshot(resolvedTarget, absoluteOutputPath, {
|
|
50
|
-
reporter,
|
|
51
|
-
logRegistry,
|
|
52
|
-
setupRegistry,
|
|
53
|
-
});
|
|
54
41
|
return {
|
|
55
42
|
ok: true,
|
|
56
43
|
productDir,
|
|
57
44
|
service: target.name,
|
|
58
|
-
outputPath: absoluteOutputPath,
|
|
59
|
-
outputLabel: path.relative(productDir, absoluteOutputPath) || path.basename(absoluteOutputPath),
|
|
60
45
|
};
|
|
61
46
|
} finally {
|
|
62
47
|
logRegistry.closeAll();
|
|
63
48
|
fs.rmSync(runtimeRoot, { recursive: true, force: true });
|
|
64
49
|
}
|
|
65
50
|
}
|
|
66
|
-
|
|
67
|
-
function normalizeOptionalString(value) {
|
|
68
|
-
if (typeof value !== "string") return null;
|
|
69
|
-
const normalized = value.trim();
|
|
70
|
-
return normalized.length > 0 ? normalized : null;
|
|
71
|
-
}
|
|
@@ -47,6 +47,7 @@ export async function buildRunRequest(flags, positionalType = null, cwd = proces
|
|
|
47
47
|
writeStatus: flags["write-status"],
|
|
48
48
|
allowPartialStatus: flags["allow-partial-status"],
|
|
49
49
|
ignoreSkipRules: flags["ignore-skip-rules"],
|
|
50
|
+
skipSchemaSourceVerify: flags["skip-schema-source-verify"],
|
|
50
51
|
},
|
|
51
52
|
};
|
|
52
53
|
}
|
package/lib/config/database.mjs
CHANGED
|
@@ -29,6 +29,7 @@ export function normalizeDatabaseConfig(explicitService, serviceName) {
|
|
|
29
29
|
image: database.image || DEFAULT_LOCAL_IMAGE,
|
|
30
30
|
user: database.user || DEFAULT_LOCAL_USER,
|
|
31
31
|
password: database.password || DEFAULT_LOCAL_PASSWORD,
|
|
32
|
+
sourceSchema: normalizeSourceSchemaConfig(database.sourceSchema, serviceName),
|
|
32
33
|
template: normalizeDatabaseTemplateConfig(database.template, serviceName),
|
|
33
34
|
serviceName,
|
|
34
35
|
};
|
|
@@ -46,6 +47,11 @@ export function normalizeDatabaseTemplateConfig(value, serviceName) {
|
|
|
46
47
|
if (!value || typeof value !== "object") {
|
|
47
48
|
throw new Error(`Service "${serviceName}" database.template must be an object`);
|
|
48
49
|
}
|
|
50
|
+
if (Object.prototype.hasOwnProperty.call(value, "schema")) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Service "${serviceName}" database.template.schema has been removed. Configure database.sourceSchema instead.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
49
55
|
|
|
50
56
|
return {
|
|
51
57
|
inputs: normalizeConfiguredInputs(value.inputs, `Service "${serviceName}" database.template`),
|
|
@@ -54,3 +60,61 @@ export function normalizeDatabaseTemplateConfig(value, serviceName) {
|
|
|
54
60
|
verify: normalizeConfiguredSteps(value.verify, `Service "${serviceName}" database.template.verify`),
|
|
55
61
|
};
|
|
56
62
|
}
|
|
63
|
+
|
|
64
|
+
function normalizeSourceSchemaConfig(value, serviceName) {
|
|
65
|
+
if (value === false || value === null) return null;
|
|
66
|
+
if (value === undefined) {
|
|
67
|
+
return {
|
|
68
|
+
kind: "auto",
|
|
69
|
+
cachePath: null,
|
|
70
|
+
refresh: { mode: "always" },
|
|
71
|
+
unavailable: "auto",
|
|
72
|
+
verify: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
76
|
+
throw new Error(`Service "${serviceName}" database.sourceSchema must be an object or false`);
|
|
77
|
+
}
|
|
78
|
+
const kind = value.kind || "env";
|
|
79
|
+
if (kind !== "env") {
|
|
80
|
+
throw new Error(`Service "${serviceName}" database.sourceSchema.kind must be "env"`);
|
|
81
|
+
}
|
|
82
|
+
if (typeof value.env !== "string" || value.env.trim().length === 0) {
|
|
83
|
+
throw new Error(`Service "${serviceName}" database.sourceSchema.env must be a non-empty string`);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
kind,
|
|
87
|
+
env: value.env.trim(),
|
|
88
|
+
cachePath: normalizeOptionalString(value.cachePath, `Service "${serviceName}" database.sourceSchema.cachePath`),
|
|
89
|
+
refresh: normalizeSourceSchemaRefresh(value.refresh, serviceName),
|
|
90
|
+
unavailable: normalizeSourceSchemaUnavailable(value.unavailable, serviceName),
|
|
91
|
+
verify: value.verify !== false,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizeSourceSchemaRefresh(value, serviceName) {
|
|
96
|
+
if (value == null || value === "always") return { mode: "always" };
|
|
97
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
98
|
+
const ttlSeconds = value.ttlSeconds;
|
|
99
|
+
if (!Number.isInteger(ttlSeconds) || ttlSeconds < 0) {
|
|
100
|
+
throw new Error(`Service "${serviceName}" database.sourceSchema.refresh.ttlSeconds must be a non-negative integer`);
|
|
101
|
+
}
|
|
102
|
+
return { mode: "ttl", ttlSeconds };
|
|
103
|
+
}
|
|
104
|
+
throw new Error(`Service "${serviceName}" database.sourceSchema.refresh must be "always" or { ttlSeconds }`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeSourceSchemaUnavailable(value, serviceName) {
|
|
108
|
+
if (value == null || value === "auto" || value === "fail" || value === "warn-cache") {
|
|
109
|
+
return value || "auto";
|
|
110
|
+
}
|
|
111
|
+
throw new Error(`Service "${serviceName}" database.sourceSchema.unavailable must be "auto", "fail", or "warn-cache"`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function normalizeOptionalString(value, label) {
|
|
115
|
+
if (value == null) return null;
|
|
116
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
117
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
118
|
+
}
|
|
119
|
+
return value.trim();
|
|
120
|
+
}
|
|
@@ -9,12 +9,23 @@ export interface DatabaseTemplateConfig {
|
|
|
9
9
|
|
|
10
10
|
export interface DatabaseTemplateOptions {
|
|
11
11
|
inputs?: string[];
|
|
12
|
-
schema?: string | TemplateSqlFileStepConfig;
|
|
13
12
|
migrate?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[];
|
|
14
13
|
seed?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[];
|
|
15
14
|
verify?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[];
|
|
16
15
|
}
|
|
17
16
|
|
|
17
|
+
export interface DatabaseSourceSchemaOptions {
|
|
18
|
+
cachePath?: string;
|
|
19
|
+
refresh?: "always" | { ttlSeconds: number };
|
|
20
|
+
unavailable?: "auto" | "fail" | "warn-cache";
|
|
21
|
+
verify?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DatabaseSourceSchemaConfig extends DatabaseSourceSchemaOptions {
|
|
25
|
+
kind: "env";
|
|
26
|
+
env: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
export interface TemplateStepBaseConfig {
|
|
19
30
|
cwd?: string;
|
|
20
31
|
inputs?: string[];
|
|
@@ -78,6 +89,7 @@ export interface LocalDatabaseConfig {
|
|
|
78
89
|
image?: string;
|
|
79
90
|
password?: string;
|
|
80
91
|
reset?: boolean;
|
|
92
|
+
sourceSchema?: DatabaseSourceSchemaConfig | null;
|
|
81
93
|
template?: DatabaseTemplateConfig;
|
|
82
94
|
user?: string;
|
|
83
95
|
}
|
|
@@ -409,6 +421,9 @@ export declare const app: {
|
|
|
409
421
|
next(options: NextAppOptions): ServiceConfig;
|
|
410
422
|
};
|
|
411
423
|
export declare const database: {
|
|
424
|
+
schema: {
|
|
425
|
+
fromEnv(envName: string, options?: DatabaseSourceSchemaOptions): DatabaseSourceSchemaConfig;
|
|
426
|
+
};
|
|
412
427
|
postgres(
|
|
413
428
|
options?: Omit<LocalDatabaseConfig, "provider" | "template"> & {
|
|
414
429
|
template?: DatabaseTemplateOptions;
|