frg-data-diff 2.0.0

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 (62) hide show
  1. package/README.md +628 -0
  2. package/dist/apply/apply-diff.d.ts +24 -0
  3. package/dist/apply/apply-diff.js +205 -0
  4. package/dist/apply/conflict.d.ts +20 -0
  5. package/dist/apply/conflict.js +14 -0
  6. package/dist/apply/plan.d.ts +13 -0
  7. package/dist/apply/plan.js +37 -0
  8. package/dist/cli/apply.d.ts +8 -0
  9. package/dist/cli/apply.js +270 -0
  10. package/dist/cli/generator.d.ts +9 -0
  11. package/dist/cli/generator.js +804 -0
  12. package/dist/cli/pg-triggers.d.ts +2 -0
  13. package/dist/cli/pg-triggers.js +185 -0
  14. package/dist/cli/root.d.ts +8 -0
  15. package/dist/cli/root.js +231 -0
  16. package/dist/cli/sql.d.ts +9 -0
  17. package/dist/cli/sql.js +158 -0
  18. package/dist/config/config-schema.d.ts +380 -0
  19. package/dist/config/config-schema.js +95 -0
  20. package/dist/config/load-config.d.ts +12 -0
  21. package/dist/config/load-config.js +87 -0
  22. package/dist/config/resolve-options.d.ts +95 -0
  23. package/dist/config/resolve-options.js +183 -0
  24. package/dist/config/write-config.d.ts +18 -0
  25. package/dist/config/write-config.js +103 -0
  26. package/dist/db/connection.d.ts +28 -0
  27. package/dist/db/connection.js +34 -0
  28. package/dist/db/metadata.d.ts +70 -0
  29. package/dist/db/metadata.js +238 -0
  30. package/dist/db/pg-triggers.d.ts +27 -0
  31. package/dist/db/pg-triggers.js +59 -0
  32. package/dist/db/sql.d.ts +72 -0
  33. package/dist/db/sql.js +250 -0
  34. package/dist/diff/diff-schema.d.ts +380 -0
  35. package/dist/diff/diff-schema.js +67 -0
  36. package/dist/diff/generate-diff.d.ts +20 -0
  37. package/dist/diff/generate-diff.js +224 -0
  38. package/dist/diff/pg-triggers-diff.d.ts +11 -0
  39. package/dist/diff/pg-triggers-diff.js +161 -0
  40. package/dist/diff/serialize-value.d.ts +35 -0
  41. package/dist/diff/serialize-value.js +242 -0
  42. package/dist/diff/write-diff-yaml.d.ts +3 -0
  43. package/dist/diff/write-diff-yaml.js +48 -0
  44. package/dist/schema-diff/generate-schema-diff.d.ts +12 -0
  45. package/dist/schema-diff/generate-schema-diff.js +355 -0
  46. package/dist/schema-diff/generate-schema-sql.d.ts +20 -0
  47. package/dist/schema-diff/generate-schema-sql.js +187 -0
  48. package/dist/schema-diff/schema-diff-schema.d.ts +1088 -0
  49. package/dist/schema-diff/schema-diff-schema.js +70 -0
  50. package/dist/shared/env-values.d.ts +11 -0
  51. package/dist/shared/env-values.js +100 -0
  52. package/dist/shared/generator-wizard.d.ts +10 -0
  53. package/dist/shared/generator-wizard.js +337 -0
  54. package/dist/shared/identifiers.d.ts +24 -0
  55. package/dist/shared/identifiers.js +45 -0
  56. package/dist/shared/prompts.d.ts +26 -0
  57. package/dist/shared/prompts.js +104 -0
  58. package/dist/shared/summary.d.ts +33 -0
  59. package/dist/shared/summary.js +41 -0
  60. package/dist/sql/generate-sql.d.ts +19 -0
  61. package/dist/sql/generate-sql.js +223 -0
  62. package/package.json +39 -0
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.prompt = prompt;
37
+ exports.confirmProceed = confirmProceed;
38
+ exports.confirmCreateConfig = confirmCreateConfig;
39
+ exports.confirmUpdateConfig = confirmUpdateConfig;
40
+ exports.confirmDirenvAllow = confirmDirenvAllow;
41
+ const fs = __importStar(require("fs"));
42
+ const readline = __importStar(require("readline"));
43
+ let nonTtyAnswers;
44
+ let nonTtyAnswerIndex = 0;
45
+ /**
46
+ * Prompts the user with the given message and waits for input.
47
+ * Returns the trimmed input string.
48
+ */
49
+ function prompt(message) {
50
+ if (!process.stdin.isTTY) {
51
+ nonTtyAnswers ??= fs.readFileSync(0, "utf-8").split(/\r?\n/);
52
+ process.stdout.write(message);
53
+ return Promise.resolve((nonTtyAnswers[nonTtyAnswerIndex++] ?? "").trim());
54
+ }
55
+ const rl = readline.createInterface({
56
+ input: process.stdin,
57
+ output: process.stdout,
58
+ });
59
+ return new Promise((resolve) => {
60
+ rl.question(message, (answer) => {
61
+ rl.close();
62
+ resolve(answer.trim());
63
+ });
64
+ });
65
+ }
66
+ /**
67
+ * Asks the user to type "yes" to proceed.
68
+ * Returns true if the user typed exactly "yes", false otherwise.
69
+ */
70
+ async function confirmProceed(message = 'Proceed? Type "yes" to continue: ', defaultYes = false) {
71
+ const answer = (await prompt(message)).trim().toLowerCase();
72
+ if (answer === "") {
73
+ return defaultYes;
74
+ }
75
+ if (answer === "no") {
76
+ return false;
77
+ }
78
+ return answer === "yes";
79
+ }
80
+ /**
81
+ * Asks the user whether to create a config file.
82
+ * Returns true if the user typed exactly "yes".
83
+ */
84
+ async function confirmCreateConfig() {
85
+ const message = "\nNo .frg-data-diff.config.json file was found.\nCreate one from these options? [yes]: ";
86
+ return confirmProceed(message, true);
87
+ }
88
+ /**
89
+ * Asks the user whether to update an existing config file with the current options.
90
+ * Returns true if the user typed exactly "yes".
91
+ */
92
+ async function confirmUpdateConfig() {
93
+ const message = "\nSave these options back to .frg-data-diff.config.json? [yes]: ";
94
+ return confirmProceed(message, true);
95
+ }
96
+ /**
97
+ * Asks the user whether to run `direnv allow` in the current directory.
98
+ * Returns true if the user typed exactly "yes".
99
+ */
100
+ async function confirmDirenvAllow() {
101
+ const message = '\nA direnv installation was detected.\nRun "direnv allow" for this directory now? [yes]: ';
102
+ return confirmProceed(message, true);
103
+ }
104
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Summary of apply results.
3
+ */
4
+ export interface ApplySummary {
5
+ applied: {
6
+ inserts: number;
7
+ updates: number;
8
+ deletes: number;
9
+ };
10
+ skipped: SkippedRow[];
11
+ conflicts: ConflictRow[];
12
+ }
13
+ export interface SkippedRow {
14
+ table: string;
15
+ operation: "insert" | "update" | "delete";
16
+ pk: Record<string, unknown>;
17
+ reason: string;
18
+ }
19
+ export interface ConflictRow {
20
+ table: string;
21
+ operation: "insert" | "update" | "delete";
22
+ pk: Record<string, unknown>;
23
+ reason: string;
24
+ }
25
+ /**
26
+ * Creates an empty summary object.
27
+ */
28
+ export declare function createEmptySummary(): ApplySummary;
29
+ /**
30
+ * Prints a human-readable summary to stdout.
31
+ */
32
+ export declare function printSummary(summary: ApplySummary, dryRun: boolean): void;
33
+ //# sourceMappingURL=summary.d.ts.map
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createEmptySummary = createEmptySummary;
4
+ exports.printSummary = printSummary;
5
+ /**
6
+ * Creates an empty summary object.
7
+ */
8
+ function createEmptySummary() {
9
+ return {
10
+ applied: {
11
+ inserts: 0,
12
+ updates: 0,
13
+ deletes: 0,
14
+ },
15
+ skipped: [],
16
+ conflicts: [],
17
+ };
18
+ }
19
+ /**
20
+ * Prints a human-readable summary to stdout.
21
+ */
22
+ function printSummary(summary, dryRun) {
23
+ const prefix = dryRun ? "[DRY RUN] " : "";
24
+ console.log(`\n${prefix}Apply summary:`);
25
+ console.log(` Inserts applied: ${summary.applied.inserts}`);
26
+ console.log(` Updates applied: ${summary.applied.updates}`);
27
+ console.log(` Deletes applied: ${summary.applied.deletes}`);
28
+ if (summary.skipped.length > 0) {
29
+ console.log(` Skipped rows: ${summary.skipped.length}`);
30
+ for (const s of summary.skipped) {
31
+ console.log(` - ${s.table} [${s.operation}] pk=${JSON.stringify(s.pk)}: ${s.reason}`);
32
+ }
33
+ }
34
+ if (summary.conflicts.length > 0) {
35
+ console.log(` Conflicts: ${summary.conflicts.length}`);
36
+ for (const c of summary.conflicts) {
37
+ console.log(` - ${c.table} [${c.operation}] pk=${JSON.stringify(c.pk)}: ${c.reason}`);
38
+ }
39
+ }
40
+ }
41
+ //# sourceMappingURL=summary.js.map
@@ -0,0 +1,19 @@
1
+ import { type DiffJson } from "../diff/diff-schema";
2
+ export interface GenerateSqlOptions {
3
+ applyInserts: boolean;
4
+ applyUpdates: boolean;
5
+ applyDeletes: boolean;
6
+ transaction: boolean;
7
+ onProgress?: (message: string) => void;
8
+ }
9
+ export interface GeneratedSqlResult {
10
+ sql: string;
11
+ summary: {
12
+ inserts: number;
13
+ updates: number;
14
+ deletes: number;
15
+ };
16
+ }
17
+ export declare function buildSqlOutputPath(inputPath: string): string;
18
+ export declare function generateSqlScript(diff: DiffJson, options: GenerateSqlOptions): GeneratedSqlResult;
19
+ //# sourceMappingURL=generate-sql.d.ts.map
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildSqlOutputPath = buildSqlOutputPath;
4
+ exports.generateSqlScript = generateSqlScript;
5
+ const identifiers_1 = require("../shared/identifiers");
6
+ function buildSqlOutputPath(inputPath) {
7
+ return inputPath.endsWith(".json")
8
+ ? inputPath.replace(/\.json$/i, ".sql")
9
+ : `${inputPath}.sql`;
10
+ }
11
+ function generateSqlScript(diff, options) {
12
+ const lines = [];
13
+ const summary = {
14
+ inserts: 0,
15
+ updates: 0,
16
+ deletes: 0,
17
+ };
18
+ lines.push("-- Generated by frg-data-diff");
19
+ lines.push(`-- Diff format: ${diff.format}`);
20
+ lines.push(`-- Generated at: ${diff.generatedAt}`);
21
+ lines.push(`-- Source schema: ${diff.source.schema}`);
22
+ lines.push(`-- Destination schema: ${diff.dest.schema}`);
23
+ if (options.transaction) {
24
+ lines.push("");
25
+ lines.push("BEGIN;");
26
+ }
27
+ reportProgress(options.onProgress, `Generating data SQL for ${diff.tables.length} table diff(s)...`);
28
+ for (const [tableIndex, tableDiff] of diff.tables.entries()) {
29
+ const progressLabel = `[data-sql ${tableIndex + 1}/${diff.tables.length}] ${tableDiff.schema}.${tableDiff.table}`;
30
+ reportProgress(options.onProgress, `${progressLabel}: ${summarizeDataSqlWork(tableDiff, options)}`);
31
+ (0, identifiers_1.validateIdentifier)(tableDiff.schema, "schema name");
32
+ (0, identifiers_1.validateIdentifier)(tableDiff.table, "table name");
33
+ (0, identifiers_1.validateIdentifiers)(tableDiff.primaryKey, "primary key column");
34
+ const statements = [];
35
+ if (options.applyInserts) {
36
+ for (const insert of tableDiff.inserts) {
37
+ statements.push(buildInsertStatement(tableDiff.schema, tableDiff.table, insert, tableDiff.columnTypes));
38
+ summary.inserts++;
39
+ }
40
+ }
41
+ if (options.applyUpdates) {
42
+ for (const update of tableDiff.updates) {
43
+ statements.push(buildUpdateStatement(tableDiff.schema, tableDiff.table, tableDiff.primaryKey, update, tableDiff.columnTypes));
44
+ summary.updates++;
45
+ }
46
+ }
47
+ if (options.applyDeletes) {
48
+ for (const del of tableDiff.deletes) {
49
+ statements.push(buildDeleteStatement(tableDiff.schema, tableDiff.table, tableDiff.primaryKey, del, tableDiff.columnTypes));
50
+ summary.deletes++;
51
+ }
52
+ }
53
+ if (statements.length === 0) {
54
+ reportProgress(options.onProgress, `${progressLabel}: no SQL statements generated`);
55
+ continue;
56
+ }
57
+ reportProgress(options.onProgress, `${progressLabel}: generated ${statements.length} SQL statement(s)`);
58
+ lines.push("");
59
+ lines.push(`-- ${tableDiff.schema}.${tableDiff.table}`);
60
+ lines.push(...statements);
61
+ }
62
+ if (options.transaction) {
63
+ lines.push("");
64
+ lines.push("COMMIT;");
65
+ }
66
+ lines.push("");
67
+ return {
68
+ sql: lines.join("\n"),
69
+ summary,
70
+ };
71
+ }
72
+ function buildInsertStatement(schema, table, record, columnTypes) {
73
+ const columns = Object.keys(record.row);
74
+ (0, identifiers_1.validateIdentifiers)(columns, "insert column");
75
+ const qualifiedTable = (0, identifiers_1.quoteQualifiedTable)(schema, table);
76
+ const quotedColumns = columns.map(identifiers_1.quoteIdentifier).join(", ");
77
+ const values = columns
78
+ .map((column) => toSqlLiteral(record.row[column], "value", column, columnTypes?.[column]))
79
+ .join(", ");
80
+ return `INSERT INTO ${qualifiedTable} (${quotedColumns}) VALUES (${values});`;
81
+ }
82
+ function buildUpdateStatement(schema, table, pkColumns, record, columnTypes) {
83
+ const setColumns = Object.keys(record.changes);
84
+ (0, identifiers_1.validateIdentifiers)(setColumns, "update column");
85
+ const qualifiedTable = (0, identifiers_1.quoteQualifiedTable)(schema, table);
86
+ const setClause = setColumns
87
+ .map((column) => `${(0, identifiers_1.quoteIdentifier)(column)} = ${toSqlLiteral(record.changes[column].to, "value", column, columnTypes?.[column])}`)
88
+ .join(", ");
89
+ const pkWhere = buildSqlWhereClause(pkColumns.map((column) => [column, record.pk[column]]), "pk", columnTypes);
90
+ const guardWhere = buildSqlWhereClause(Object.entries(record.guard).filter(([column]) => !pkColumns.includes(column)), "guard", columnTypes);
91
+ const whereClause = guardWhere ? `${pkWhere} AND ${guardWhere}` : pkWhere;
92
+ return `UPDATE ${qualifiedTable} SET ${setClause} WHERE ${whereClause};`;
93
+ }
94
+ function buildDeleteStatement(schema, table, pkColumns, record, columnTypes) {
95
+ const qualifiedTable = (0, identifiers_1.quoteQualifiedTable)(schema, table);
96
+ const pkWhere = buildSqlWhereClause(pkColumns.map((column) => [column, record.pk[column]]), "pk", columnTypes);
97
+ const guardWhere = buildSqlWhereClause(Object.entries(record.guard).filter(([column]) => !pkColumns.includes(column)), "guard", columnTypes);
98
+ const whereClause = guardWhere ? `${pkWhere} AND ${guardWhere}` : pkWhere;
99
+ return `DELETE FROM ${qualifiedTable} WHERE ${whereClause};`;
100
+ }
101
+ function buildSqlWhereClause(entries, mode, columnTypes) {
102
+ const filteredEntries = entries.filter(([column, value]) => !(mode === "guard" && shouldSkipPointGuard(column, value)));
103
+ (0, identifiers_1.validateIdentifiers)(filteredEntries.map(([column]) => column), `${mode} column`);
104
+ return filteredEntries
105
+ .map(([column, value]) => {
106
+ const columnType = columnTypes?.[column];
107
+ const useJsonbComparison = mode === "guard" &&
108
+ (isJsonColumnType(columnType) || (!columnType && isPlainObject(value)));
109
+ const left = useJsonbComparison
110
+ ? `${(0, identifiers_1.quoteIdentifier)(column)}::jsonb`
111
+ : (0, identifiers_1.quoteIdentifier)(column);
112
+ return `${left} IS NOT DISTINCT FROM ${toSqlLiteral(value, useJsonbComparison ? "json_guard" : "value", column, columnType)}`;
113
+ })
114
+ .join(" AND ");
115
+ }
116
+ function toSqlLiteral(value, mode, columnName, columnType) {
117
+ if (value === null || value === undefined) {
118
+ return "NULL";
119
+ }
120
+ if (isJsonColumnType(columnType)) {
121
+ const jsonLiteral = sqlStringLiteral(JSON.stringify(value));
122
+ if (mode === "json_guard") {
123
+ return `${jsonLiteral}::jsonb`;
124
+ }
125
+ return `${jsonLiteral}::${columnType}`;
126
+ }
127
+ if (typeof value === "number") {
128
+ if (!Number.isFinite(value)) {
129
+ throw new Error(`Cannot generate SQL for non-finite number: ${value}`);
130
+ }
131
+ return String(value);
132
+ }
133
+ if (typeof value === "boolean") {
134
+ return value ? "TRUE" : "FALSE";
135
+ }
136
+ if (typeof value === "string") {
137
+ const literal = sqlStringLiteral(value);
138
+ if (columnName && looksLikePointValue(columnName, value)) {
139
+ return `${literal}::point`;
140
+ }
141
+ return mode === "json_guard" ? `${literal}::jsonb` : literal;
142
+ }
143
+ if (Array.isArray(value)) {
144
+ return sqlStringLiteral(buildPostgresArrayLiteral(value));
145
+ }
146
+ if (isByteaValue(value)) {
147
+ return `decode(${sqlStringLiteral(value.$value)}, 'base64')`;
148
+ }
149
+ if (isPlainObject(value)) {
150
+ const literal = sqlStringLiteral(JSON.stringify(value));
151
+ return mode === "json_guard" ? `${literal}::jsonb` : literal;
152
+ }
153
+ return sqlStringLiteral(String(value));
154
+ }
155
+ function sqlStringLiteral(value) {
156
+ return `'${value.replace(/'/g, "''")}'`;
157
+ }
158
+ function buildPostgresArrayLiteral(values) {
159
+ return `{${values.map((value) => buildPostgresArrayElement(value)).join(",")}}`;
160
+ }
161
+ function buildPostgresArrayElement(value) {
162
+ if (value === null || value === undefined) {
163
+ return "NULL";
164
+ }
165
+ if (Array.isArray(value)) {
166
+ return buildPostgresArrayLiteral(value);
167
+ }
168
+ if (typeof value === "number") {
169
+ if (!Number.isFinite(value)) {
170
+ throw new Error(`Cannot generate SQL for non-finite number: ${value}`);
171
+ }
172
+ return String(value);
173
+ }
174
+ if (typeof value === "boolean") {
175
+ return value ? "true" : "false";
176
+ }
177
+ if (isByteaValue(value)) {
178
+ return quotePostgresArrayString(value.$value);
179
+ }
180
+ if (isPlainObject(value)) {
181
+ return quotePostgresArrayString(JSON.stringify(value));
182
+ }
183
+ return quotePostgresArrayString(String(value));
184
+ }
185
+ function quotePostgresArrayString(value) {
186
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
187
+ }
188
+ function summarizeDataSqlWork(tableDiff, options) {
189
+ const insertsToApply = options.applyInserts ? tableDiff.inserts.length : 0;
190
+ const updatesToApply = options.applyUpdates ? tableDiff.updates.length : 0;
191
+ const deletesToApply = options.applyDeletes ? tableDiff.deletes.length : 0;
192
+ return `processing ${insertsToApply} insert(s), ${updatesToApply} update(s), ${deletesToApply} delete(s)`;
193
+ }
194
+ function reportProgress(callback, message) {
195
+ if (callback) {
196
+ callback(message);
197
+ }
198
+ }
199
+ function isPlainObject(value) {
200
+ return (typeof value === "object" &&
201
+ value !== null &&
202
+ !Array.isArray(value) &&
203
+ !isByteaValue(value));
204
+ }
205
+ function isByteaValue(value) {
206
+ return (typeof value === "object" &&
207
+ value !== null &&
208
+ !Array.isArray(value) &&
209
+ value["$type"] === "bytea" &&
210
+ typeof value["$value"] === "string");
211
+ }
212
+ function looksLikePointValue(columnName, value) {
213
+ return (columnName.toLowerCase().includes("point") &&
214
+ /^\(\s*-?\d+(?:\.\d+)?\s*,\s*-?\d+(?:\.\d+)?\s*\)$/.test(value));
215
+ }
216
+ function shouldSkipPointGuard(columnName, value) {
217
+ return typeof value === "string" && looksLikePointValue(columnName, value);
218
+ }
219
+ function isJsonColumnType(columnType) {
220
+ const normalizedColumnType = columnType?.toLowerCase();
221
+ return normalizedColumnType === "json" || normalizedColumnType === "jsonb";
222
+ }
223
+ //# sourceMappingURL=generate-sql.js.map
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "frg-data-diff",
3
+ "version": "2.0.0",
4
+ "description": "Safe PostgreSQL data diff and apply tooling. Compare data between two PostgreSQL databases and apply changes safely.",
5
+ "bin": "./dist/cli/root.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "prepack": "npm run build",
9
+ "prepublishOnly": "npm run typecheck && npm run test:unit",
10
+ "test": "npm run test:unit && npm run test:integration && npm run test:guided",
11
+ "test:unit": "./scripts/preserve-envrc.sh vitest run tests/unit",
12
+ "pretest:integration": "docker compose down -v --remove-orphans && docker compose up -d --wait",
13
+ "test:integration": "./scripts/preserve-envrc.sh vitest run tests/integration",
14
+ "posttest:integration": "docker compose down -v --remove-orphans",
15
+ "test:guided": "./guided-manual-tests/run-all.sh",
16
+ "typecheck": "tsc --noEmit",
17
+ "lint": "npx prettier --write ."
18
+ },
19
+ "dependencies": {
20
+ "commander": "^12.0.0",
21
+ "pg": "^8.11.0",
22
+ "yaml": "^2.9.0",
23
+ "zod": "^3.22.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^20.0.0",
27
+ "@types/pg": "^8.11.0",
28
+ "@vitest/coverage-v8": "^3.1.0",
29
+ "eslint": "^8.57.0",
30
+ "@typescript-eslint/parser": "^7.0.0",
31
+ "@typescript-eslint/eslint-plugin": "^7.0.0",
32
+ "typescript": "^5.4.0",
33
+ "vitest": "^3.1.0"
34
+ },
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ },
38
+ "license": "MIT"
39
+ }