@elench/testkit 0.1.118 → 0.1.119
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/lib/app/doctor.mjs +11 -113
- package/lib/cli/assistant/command-observer.mjs +1 -1
- package/lib/cli/assistant/state.mjs +2 -0
- package/lib/cli/commands/lint.mjs +37 -0
- package/lib/cli/entrypoint.mjs +1 -0
- package/lib/cli/operations/lint/operation.mjs +12 -0
- package/lib/cli/renderers/doctor/text.mjs +5 -0
- package/lib/cli/renderers/lint/text.mjs +20 -0
- package/lib/config-api/database-steps.mjs +132 -0
- package/lib/config-api/index.d.ts +36 -3
- package/lib/config-api/index.mjs +118 -12
- package/lib/lint/index.mjs +569 -0
- package/lib/runner/template-steps.mjs +8 -0
- package/lib/runtime/index.d.ts +43 -0
- package/lib/runtime/index.mjs +24 -0
- package/lib/runtime-src/k6/http-assertions.js +82 -0
- package/lib/shared/configured-steps.mjs +16 -0
- package/lib/ui/index.d.ts +46 -0
- package/lib/ui/index.mjs +11 -0
- package/lib/ui/sandbox.mjs +115 -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 +5 -5
- 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/app/doctor.mjs
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
1
|
import path from "path";
|
|
3
|
-
import ts from "typescript";
|
|
4
2
|
import { discoverTests } from "../discovery/index.mjs";
|
|
5
3
|
import { loadConfigContext } from "../config/index.mjs";
|
|
6
4
|
import { runTestkitTypecheck } from "./typecheck.mjs";
|
|
7
|
-
import {
|
|
5
|
+
import { runLint } from "../lint/index.mjs";
|
|
8
6
|
|
|
9
7
|
export async function runDoctor(options = {}) {
|
|
10
8
|
const checks = [];
|
|
11
9
|
const productDir = options.dir ? path.resolve(process.cwd(), options.dir) : process.cwd();
|
|
12
10
|
|
|
13
|
-
await loadConfigContext({ dir: productDir });
|
|
11
|
+
const context = await loadConfigContext({ dir: productDir });
|
|
14
12
|
checks.push({
|
|
15
13
|
code: "config-load",
|
|
16
14
|
level: "pass",
|
|
@@ -29,26 +27,18 @@ export async function runDoctor(options = {}) {
|
|
|
29
27
|
details: discoveryErrors,
|
|
30
28
|
});
|
|
31
29
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
level: playwrightViolations.length === 0 ? "pass" : "fail",
|
|
36
|
-
message:
|
|
37
|
-
playwrightViolations.length === 0
|
|
38
|
-
? "No runtime @playwright/test imports found in testkit UI suites"
|
|
39
|
-
: `Found ${playwrightViolations.length} UI runtime import violation(s); import from @elench/testkit/ui instead`,
|
|
40
|
-
details: playwrightViolations,
|
|
30
|
+
const lint = await runLint({
|
|
31
|
+
dir: productDir,
|
|
32
|
+
...(context.config?.lint || {}),
|
|
41
33
|
});
|
|
42
|
-
|
|
43
|
-
const configImportViolations = findConfigImportViolations(productDir);
|
|
44
34
|
checks.push({
|
|
45
|
-
code: "
|
|
46
|
-
level:
|
|
35
|
+
code: "lint",
|
|
36
|
+
level: lint.ok ? "pass" : "fail",
|
|
47
37
|
message:
|
|
48
|
-
|
|
49
|
-
?
|
|
50
|
-
: `Found ${
|
|
51
|
-
details:
|
|
38
|
+
lint.ok
|
|
39
|
+
? `No Testkit lint violations across ${lint.summary.testkitFiles} suite file(s)`
|
|
40
|
+
: `Found ${lint.summary.violations} Testkit lint violation(s)`,
|
|
41
|
+
details: lint.violations,
|
|
52
42
|
});
|
|
53
43
|
|
|
54
44
|
const hasBrowserOrNextWork = discovery.files.some((entry) => entry.type === "ui");
|
|
@@ -88,95 +78,3 @@ export async function runDoctor(options = {}) {
|
|
|
88
78
|
checks,
|
|
89
79
|
};
|
|
90
80
|
}
|
|
91
|
-
|
|
92
|
-
function findPlaywrightRuntimeImportViolations(productDir) {
|
|
93
|
-
const violations = [];
|
|
94
|
-
for (const absolutePath of collectFiles(productDir)) {
|
|
95
|
-
const sourceText = fs.readFileSync(absolutePath, "utf8");
|
|
96
|
-
const sourceFile = ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
97
|
-
|
|
98
|
-
for (const statement of sourceFile.statements) {
|
|
99
|
-
if (!ts.isImportDeclaration(statement)) continue;
|
|
100
|
-
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
101
|
-
if (statement.moduleSpecifier.text !== "@playwright/test") continue;
|
|
102
|
-
const clause = statement.importClause;
|
|
103
|
-
if (clause?.isTypeOnly) continue;
|
|
104
|
-
if (!clause) {
|
|
105
|
-
violations.push(relativeViolation(productDir, absolutePath, sourceFile, statement));
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (clause.name) {
|
|
109
|
-
violations.push(relativeViolation(productDir, absolutePath, sourceFile, statement));
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if (!clause.namedBindings) {
|
|
113
|
-
violations.push(relativeViolation(productDir, absolutePath, sourceFile, statement));
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
if (ts.isNamespaceImport(clause.namedBindings)) {
|
|
117
|
-
violations.push(relativeViolation(productDir, absolutePath, sourceFile, statement));
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (clause.namedBindings.elements.some((entry) => !entry.isTypeOnly)) {
|
|
121
|
-
violations.push(relativeViolation(productDir, absolutePath, sourceFile, statement));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return violations;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function findConfigImportViolations(productDir) {
|
|
129
|
-
const configFile = findConfigFile(productDir);
|
|
130
|
-
if (!configFile || !fs.existsSync(configFile)) return [];
|
|
131
|
-
|
|
132
|
-
const sourceText = fs.readFileSync(configFile, "utf8");
|
|
133
|
-
const sourceFile = ts.createSourceFile(configFile, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
134
|
-
const violations = [];
|
|
135
|
-
|
|
136
|
-
for (const statement of sourceFile.statements) {
|
|
137
|
-
if (!ts.isImportDeclaration(statement)) continue;
|
|
138
|
-
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
139
|
-
const specifier = statement.moduleSpecifier.text;
|
|
140
|
-
if (!isRepoLocalConfigImportViolation(specifier)) continue;
|
|
141
|
-
const position = sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile));
|
|
142
|
-
violations.push({
|
|
143
|
-
file: path.relative(productDir, configFile).split(path.sep).join("/"),
|
|
144
|
-
line: position.line + 1,
|
|
145
|
-
specifier,
|
|
146
|
-
snippet: statement.getText(sourceFile),
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return violations;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function collectFiles(rootDir, out = []) {
|
|
154
|
-
if (!fs.existsSync(rootDir)) return out;
|
|
155
|
-
for (const entry of fs.readdirSync(rootDir, { withFileTypes: true })) {
|
|
156
|
-
const absolutePath = path.join(rootDir, entry.name);
|
|
157
|
-
if (entry.isDirectory()) {
|
|
158
|
-
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".testkit") continue;
|
|
159
|
-
collectFiles(absolutePath, out);
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
if (entry.isFile() && (entry.name.endsWith(".ui.testkit.ts") || entry.name.endsWith(".ui.testkit.ts"))) {
|
|
163
|
-
out.push(absolutePath);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return out.sort((left, right) => left.localeCompare(right));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function isRepoLocalConfigImportViolation(specifier) {
|
|
170
|
-
if (typeof specifier !== "string") return false;
|
|
171
|
-
if (!specifier.startsWith(".") && !specifier.startsWith("/")) return false;
|
|
172
|
-
return specifier.includes("__testkit__");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function relativeViolation(productDir, absolutePath, sourceFile, statement) {
|
|
176
|
-
const position = sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile));
|
|
177
|
-
return {
|
|
178
|
-
file: path.relative(productDir, absolutePath).split(path.sep).join("/"),
|
|
179
|
-
line: position.line + 1,
|
|
180
|
-
snippet: statement.getText(sourceFile),
|
|
181
|
-
};
|
|
182
|
-
}
|
|
@@ -3,7 +3,7 @@ import path from "path";
|
|
|
3
3
|
import { isAssistantRunCommand } from "./command-classifier.mjs";
|
|
4
4
|
|
|
5
5
|
const POLL_INTERVAL_MS = 150;
|
|
6
|
-
const OBSERVED_KINDS = new Set(["run", "discover", "status", "doctor", "typecheck"]);
|
|
6
|
+
const OBSERVED_KINDS = new Set(["run", "discover", "status", "doctor", "lint", "typecheck"]);
|
|
7
7
|
|
|
8
8
|
export function createAssistantCommandObserver({
|
|
9
9
|
productDir,
|
|
@@ -10,6 +10,7 @@ import { createRunState } from "../state/run/state.mjs";
|
|
|
10
10
|
import { buildContextSelection } from "../../results/context.mjs";
|
|
11
11
|
import { renderDiscoverResult } from "../renderers/discover/text.mjs";
|
|
12
12
|
import { renderDoctorResult } from "../renderers/doctor/text.mjs";
|
|
13
|
+
import { renderLintResult } from "../renderers/lint/text.mjs";
|
|
13
14
|
import { renderStatusResult } from "../renderers/status/text.mjs";
|
|
14
15
|
import { renderTypecheckResult } from "../renderers/typecheck/text.mjs";
|
|
15
16
|
import { isProviderInstalled } from "./providers/index.mjs";
|
|
@@ -1092,6 +1093,7 @@ function renderObservedCommandResult(command) {
|
|
|
1092
1093
|
return normalizeRenderedLines((result.results || []).flatMap((entry) => renderStatusResult(entry)));
|
|
1093
1094
|
}
|
|
1094
1095
|
if (command.kind === "doctor") return normalizeRenderedLines(renderDoctorResult(result));
|
|
1096
|
+
if (command.kind === "lint") return normalizeRenderedLines(renderLintResult(result));
|
|
1095
1097
|
if (command.kind === "typecheck") return normalizeRenderedLines(renderTypecheckResult(result));
|
|
1096
1098
|
return [];
|
|
1097
1099
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Command, Flags } from "@oclif/core";
|
|
2
|
+
import { executeLintOperation } from "../operations/lint/operation.mjs";
|
|
3
|
+
import { renderLintResult } from "../renderers/lint/text.mjs";
|
|
4
|
+
import { withAssistantCommandResult } from "../assistant/command-results.mjs";
|
|
5
|
+
|
|
6
|
+
export default class LintCommand extends Command {
|
|
7
|
+
static summary = "Run built-in Testkit repository hygiene checks";
|
|
8
|
+
|
|
9
|
+
static enableJsonFlag = true;
|
|
10
|
+
|
|
11
|
+
static flags = {
|
|
12
|
+
dir: Flags.string({
|
|
13
|
+
description: "Product directory",
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async run() {
|
|
18
|
+
return withAssistantCommandResult("lint", async () => {
|
|
19
|
+
const { flags } = await this.parse(LintCommand);
|
|
20
|
+
const result = await executeLintOperation(flags);
|
|
21
|
+
|
|
22
|
+
if (!this.jsonEnabled()) {
|
|
23
|
+
for (const line of renderLintResult(result)) {
|
|
24
|
+
this.log(line);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!result.ok) {
|
|
29
|
+
const error = new Error("testkit lint failed");
|
|
30
|
+
error.result = result;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return result;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
package/lib/cli/entrypoint.mjs
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { runLint } from "../../../lint/index.mjs";
|
|
2
|
+
import { loadTestkitConfig } from "../../../config/config-loader.mjs";
|
|
3
|
+
import { resolveProductDir } from "../../../config/paths.mjs";
|
|
4
|
+
|
|
5
|
+
export async function executeLintOperation(flags = {}) {
|
|
6
|
+
const productDir = resolveProductDir(process.cwd(), flags.dir);
|
|
7
|
+
const { config } = await loadTestkitConfig(productDir);
|
|
8
|
+
return runLint({
|
|
9
|
+
dir: productDir,
|
|
10
|
+
...(config.lint || {}),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -2,6 +2,11 @@ export function renderDoctorResult(result) {
|
|
|
2
2
|
const lines = [`testkit doctor ${result.ok ? "passed" : "failed"} for ${result.productDir}`];
|
|
3
3
|
for (const check of result.checks || []) {
|
|
4
4
|
lines.push(`${check.level.toUpperCase()} ${check.code} ${check.message}`);
|
|
5
|
+
for (const detail of (check.details || []).slice(0, 5)) {
|
|
6
|
+
if (detail?.ruleId) {
|
|
7
|
+
lines.push(` ${detail.ruleId} ${detail.file}:${detail.line} ${detail.message}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
5
10
|
}
|
|
6
11
|
return lines;
|
|
7
12
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function renderLintResult(result) {
|
|
2
|
+
const lines = [
|
|
3
|
+
`testkit lint ${result.ok ? "passed" : "failed"} for ${result.productDir}`,
|
|
4
|
+
`Checked ${result.summary.files} source file(s), ${result.summary.testkitFiles} testkit suite file(s).`,
|
|
5
|
+
];
|
|
6
|
+
|
|
7
|
+
if (result.violations.length === 0) {
|
|
8
|
+
return lines;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
lines.push("");
|
|
12
|
+
for (const violation of result.violations) {
|
|
13
|
+
const location = violation.line ? `${violation.file}:${violation.line}` : violation.file;
|
|
14
|
+
lines.push(`${location} ${violation.ruleId}: ${violation.message}`);
|
|
15
|
+
if (violation.snippet) {
|
|
16
|
+
lines.push(` ${violation.snippet}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return lines;
|
|
20
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { execa } from "execa";
|
|
2
|
+
|
|
3
|
+
export async function verifySeed(context = {}) {
|
|
4
|
+
const args = context.args || {};
|
|
5
|
+
const table = requireIdentifier(args.table, "verifySeed.args.table");
|
|
6
|
+
const where = normalizeOptionalSql(args.where);
|
|
7
|
+
const minRows = normalizeNonNegativeInteger(args.minRows ?? 1, "verifySeed.args.minRows");
|
|
8
|
+
const databaseUrl = requireDatabaseUrl(context);
|
|
9
|
+
const query = `select count(*)::int from ${table}${where ? ` where ${where}` : ""}`;
|
|
10
|
+
const count = Number(await runScalar(databaseUrl, query, context));
|
|
11
|
+
|
|
12
|
+
if (!Number.isInteger(count) || count < minRows) {
|
|
13
|
+
throw new Error(`Expected at least ${minRows} row(s) in ${table}, found ${count || 0}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function materializePostgresBinding(context = {}) {
|
|
18
|
+
const args = context.args || {};
|
|
19
|
+
const table = requireIdentifier(args.table, "materializePostgresBinding.args.table");
|
|
20
|
+
const keyColumn = requireIdentifier(
|
|
21
|
+
args.keyColumn || "slug",
|
|
22
|
+
"materializePostgresBinding.args.keyColumn"
|
|
23
|
+
);
|
|
24
|
+
const key = normalizeRequiredValue(args.key, "materializePostgresBinding.args.key");
|
|
25
|
+
const values = normalizeObject(args.values, "materializePostgresBinding.args.values");
|
|
26
|
+
const databaseUrl = requireDatabaseUrl(context);
|
|
27
|
+
|
|
28
|
+
const columns = Object.keys(values);
|
|
29
|
+
if (columns.length === 0) {
|
|
30
|
+
throw new Error("materializePostgresBinding.args.values must contain at least one column");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const assignments = columns.map((column) => {
|
|
34
|
+
const identifier = requireIdentifier(column, `materializePostgresBinding.args.values.${column}`);
|
|
35
|
+
return `${identifier} = ${toSqlLiteral(values[column])}`;
|
|
36
|
+
});
|
|
37
|
+
const query =
|
|
38
|
+
`with updated as (update ${table} set ${assignments.join(", ")} ` +
|
|
39
|
+
`where ${keyColumn} = ${toSqlLiteral(key)} returning 1) select count(*)::int from updated`;
|
|
40
|
+
const result = await runPsql(databaseUrl, query, context);
|
|
41
|
+
const updated = Number(String(result.stdout || "").trim() || "0");
|
|
42
|
+
if (updated !== 1) {
|
|
43
|
+
throw new Error(`Expected to materialize 1 ${table} row for ${keyColumn}=${key}, updated ${updated || 0}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function requireDatabaseUrl(context) {
|
|
48
|
+
const databaseUrl = String(context.databaseUrl || context.env?.DATABASE_URL || "").trim();
|
|
49
|
+
if (!databaseUrl) {
|
|
50
|
+
throw new Error("Database template step requires DATABASE_URL");
|
|
51
|
+
}
|
|
52
|
+
return databaseUrl;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runScalar(databaseUrl, query, context) {
|
|
56
|
+
const result = await runPsql(databaseUrl, query, context);
|
|
57
|
+
return String(result.stdout || "").trim();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function runPsql(databaseUrl, query, context) {
|
|
61
|
+
const result = await execa(
|
|
62
|
+
"psql",
|
|
63
|
+
[
|
|
64
|
+
databaseUrl,
|
|
65
|
+
"-v",
|
|
66
|
+
"ON_ERROR_STOP=1",
|
|
67
|
+
"-X",
|
|
68
|
+
"-q",
|
|
69
|
+
"-t",
|
|
70
|
+
"-A",
|
|
71
|
+
"-c",
|
|
72
|
+
query,
|
|
73
|
+
],
|
|
74
|
+
{
|
|
75
|
+
cwd: context.cwd || context.productDir || process.cwd(),
|
|
76
|
+
env: context.env || process.env,
|
|
77
|
+
stdout: "pipe",
|
|
78
|
+
stderr: "pipe",
|
|
79
|
+
reject: false,
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
if (result.exitCode !== 0) {
|
|
83
|
+
throw new Error(result.stderr || result.shortMessage || `psql failed with exit code ${result.exitCode}`);
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function requireIdentifier(value, label) {
|
|
89
|
+
const normalized = String(value || "").trim();
|
|
90
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?$/.test(normalized)) {
|
|
91
|
+
throw new Error(`${label} must be a SQL identifier or schema-qualified identifier`);
|
|
92
|
+
}
|
|
93
|
+
return normalized;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeOptionalSql(value) {
|
|
97
|
+
if (value == null || value === "") return "";
|
|
98
|
+
const normalized = String(value).trim();
|
|
99
|
+
if (!normalized || /;|--|\/\*/.test(normalized)) {
|
|
100
|
+
throw new Error("verifySeed.args.where must be a single SQL predicate without comments or semicolons");
|
|
101
|
+
}
|
|
102
|
+
return normalized;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normalizeNonNegativeInteger(value, label) {
|
|
106
|
+
const normalized = Number(value);
|
|
107
|
+
if (!Number.isInteger(normalized) || normalized < 0) {
|
|
108
|
+
throw new Error(`${label} must be a non-negative integer`);
|
|
109
|
+
}
|
|
110
|
+
return normalized;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function normalizeObject(value, label) {
|
|
114
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
115
|
+
throw new Error(`${label} must be an object`);
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeRequiredValue(value, label) {
|
|
121
|
+
if (value == null || String(value).length === 0) {
|
|
122
|
+
throw new Error(`${label} is required`);
|
|
123
|
+
}
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function toSqlLiteral(value) {
|
|
128
|
+
if (value === null) return "null";
|
|
129
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
130
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
131
|
+
return `'${String(value).replaceAll("'", "''")}'`;
|
|
132
|
+
}
|
|
@@ -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;
|
|
@@ -379,6 +382,8 @@ export interface NodeAppOptions extends Omit<ServiceConfig, "local" | "runtime"
|
|
|
379
382
|
}
|
|
380
383
|
|
|
381
384
|
export interface NextAppOptions extends Omit<ServiceConfig, "local" | "runtime" | "env"> {
|
|
385
|
+
api?: string | { baseUrl: string };
|
|
386
|
+
auth?: "disabled-clerk" | { kind: "disabled-clerk" };
|
|
382
387
|
baseUrl?: string;
|
|
383
388
|
build?: BuildConfig | null;
|
|
384
389
|
buildInputs?: string[];
|
|
@@ -413,6 +418,14 @@ export interface TestkitConfig {
|
|
|
413
418
|
};
|
|
414
419
|
}
|
|
415
420
|
|
|
421
|
+
export interface NodeNextPresetOptions {
|
|
422
|
+
fileTimeoutSeconds?: number;
|
|
423
|
+
install?: "require-host" | "download";
|
|
424
|
+
node?: string;
|
|
425
|
+
npm?: string;
|
|
426
|
+
workers?: number;
|
|
427
|
+
}
|
|
428
|
+
|
|
416
429
|
export declare function defineConfig<T extends TestkitConfig>(config: T): T;
|
|
417
430
|
export declare function defineFile<T extends TestkitFileMetadata>(metadata: T): T;
|
|
418
431
|
export declare const app: {
|
|
@@ -423,19 +436,39 @@ export declare const database: {
|
|
|
423
436
|
schema: {
|
|
424
437
|
fromEnv(envName: string, options?: DatabaseSourceSchemaOptions): DatabaseSourceSchemaConfig;
|
|
425
438
|
};
|
|
439
|
+
steps: {
|
|
440
|
+
materializePostgresBinding(options?: unknown): TemplateModuleStepConfig;
|
|
441
|
+
verifySeed(options?: unknown): TemplateModuleStepConfig;
|
|
442
|
+
};
|
|
426
443
|
postgres(
|
|
427
444
|
options?: Omit<LocalDatabaseConfig, "provider" | "template"> & {
|
|
445
|
+
inputs?: string[];
|
|
446
|
+
migrate?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
|
|
447
|
+
seed?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
|
|
428
448
|
template?: DatabaseTemplateOptions;
|
|
449
|
+
verify?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
|
|
429
450
|
}
|
|
430
451
|
): LocalDatabaseConfig;
|
|
431
452
|
fixture(
|
|
432
453
|
options?: Omit<LocalDatabaseConfig, "provider" | "template"> & {
|
|
454
|
+
inputs?: string[];
|
|
455
|
+
migrate?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
|
|
456
|
+
seed?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
|
|
433
457
|
template?: DatabaseTemplateOptions;
|
|
458
|
+
verify?: TemplateLifecycleStepConfig | TemplateLifecycleStepConfig[] | string | string[];
|
|
434
459
|
discovery?: DiscoveryConfig;
|
|
435
460
|
envFiles?: string[];
|
|
436
461
|
}
|
|
437
462
|
): ServiceConfig;
|
|
438
463
|
};
|
|
464
|
+
export declare const step: {
|
|
465
|
+
command(run: string, options?: TemplateLifecycleStepOptions): TemplateCommandStepConfig;
|
|
466
|
+
module(target: string, options?: TemplateLifecycleStepOptions): TemplateModuleStepConfig;
|
|
467
|
+
sqlFile(path: string, options?: TemplateLifecycleStepOptions): TemplateSqlFileStepConfig;
|
|
468
|
+
};
|
|
469
|
+
export declare const presets: {
|
|
470
|
+
nodeNext(options?: NodeNextPresetOptions): Pick<TestkitConfig, "execution" | "toolchains">;
|
|
471
|
+
};
|
|
439
472
|
export declare const toolchain: {
|
|
440
473
|
node(options?: NodeToolchainConfig): NodeToolchainConfig;
|
|
441
474
|
};
|