@xubylele/schema-forge 1.6.1 → 1.8.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.
package/README.md CHANGED
@@ -13,6 +13,7 @@ A modern CLI tool for database schema management with a clean DSL and automatic
13
13
  - **Default Change Detection** - Detects added/removed/modified column defaults and generates ALTER COLUMN SET/DROP DEFAULT
14
14
  - **Postgres/Supabase** - Currently supports PostgreSQL and Supabase
15
15
  - **Constraint Diffing** - Detects UNIQUE and PRIMARY KEY changes with deterministic constraint names
16
+ - **Live PostgreSQL Introspection** - Extract normalized schema directly from `information_schema`
16
17
 
17
18
  ## Installation
18
19
 
@@ -28,6 +29,19 @@ Or use directly with npx (no installation required):
28
29
  npx @xubylele/schema-forge init
29
30
  ```
30
31
 
32
+ ### Programmatic API
33
+
34
+ Use the programmatic API from Node (e.g. scripts, GitHub Actions) instead of invoking the CLI via shell:
35
+
36
+ ```js
37
+ const { generate, EXIT_CODES } = require('@xubylele/schema-forge/api');
38
+
39
+ const result = await generate({ name: 'MyMigration' });
40
+ if (result.exitCode !== EXIT_CODES.SUCCESS) process.exit(result.exitCode);
41
+ ```
42
+
43
+ **Exports:** `init`, `generate`, `diff`, `doctor`, `validate`, `introspect`, `importSchema` (each returns `Promise<RunResult>`), `RunResult` (`{ exitCode: number }`), `EXIT_CODES`, and option types (`GenerateOptions`, `DiffOptions`, etc.). Entrypoint: `@xubylele/schema-forge/api`. Exit code semantics: [docs/exit-codes.json](docs/exit-codes.json).
44
+
31
45
  ## Development
32
46
 
33
47
  Clone the repository and install dependencies:
@@ -56,6 +70,17 @@ Run tests:
56
70
  npm test
57
71
  ```
58
72
 
73
+ Run real-db drift integration tests:
74
+
75
+ ```bash
76
+ npm run test:integration:drift
77
+ ```
78
+
79
+ Notes:
80
+
81
+ - Local explicit run: set `SF_RUN_REAL_DB_TESTS=true` (uses Testcontainers `postgres:16-alpine`, Docker required).
82
+ - CI/service mode: set `SF_USE_CI_POSTGRES=true` and `DATABASE_URL` to reuse an existing Postgres service.
83
+
59
84
  ## Getting Started
60
85
 
61
86
  Here's a quick walkthrough to get started with SchemaForge:
@@ -205,9 +230,13 @@ schema-forge diff [--safe] [--force]
205
230
 
206
231
  - `--safe` - Block execution if destructive operations are detected (exits with error)
207
232
  - `--force` - Bypass safety checks and proceed with displaying destructive SQL (shows warning)
233
+ - `--url` - PostgreSQL connection URL for live database diff (fallback: `DATABASE_URL`)
234
+ - `--schema` - Comma-separated schema names to introspect (default: `public`)
208
235
 
209
236
  Shows what SQL would be generated if you ran `generate`. Useful for previewing changes. Safety behavior is the same as `generate` command. In CI environments, exits with code 3 if destructive operations are detected unless `--force` is used. See [CI Behavior](#ci-behavior) for more details.
210
237
 
238
+ When `--url` (or `DATABASE_URL`) is provided, `diff` compares your target DSL schema against the live PostgreSQL schema introspected from `information_schema`.
239
+
211
240
  ### `schema-forge import`
212
241
 
213
242
  Reconstruct `schemaforge/schema.sf` from existing PostgreSQL/Supabase SQL migrations.
@@ -234,6 +263,36 @@ Detect destructive or risky schema changes before generating/applying migrations
234
263
  schema-forge validate
235
264
  ```
236
265
 
266
+ Live drift validation:
267
+
268
+ ```bash
269
+ schema-forge validate --url "$DATABASE_URL" --json
270
+ ```
271
+
272
+ Live `--json` output returns a structured `DriftReport`:
273
+
274
+ ```json
275
+ {
276
+ "missingTables": ["users_archive"],
277
+ "extraTables": ["audit_log"],
278
+ "columnDifferences": [
279
+ {
280
+ "tableName": "users",
281
+ "missingInLive": ["nickname"],
282
+ "extraInLive": ["last_login"]
283
+ }
284
+ ],
285
+ "typeMismatches": [
286
+ {
287
+ "tableName": "users",
288
+ "columnName": "email",
289
+ "expectedType": "varchar",
290
+ "actualType": "text"
291
+ }
292
+ ]
293
+ }
294
+ ```
295
+
237
296
  Validation checks include:
238
297
 
239
298
  - Dropped tables (`DROP_TABLE`, error)
@@ -247,16 +306,49 @@ Use JSON mode for CI and automation:
247
306
  schema-forge validate --json
248
307
  ```
249
308
 
250
- Exit codes (also see [CI Behavior](#ci-behavior)):
309
+ Live mode options:
310
+
311
+ - `--url` - PostgreSQL connection URL for live drift validation (fallback: `DATABASE_URL`)
312
+ - `--schema` - Comma-separated schema names to introspect (default: `public`)
251
313
 
252
- - `3` in CI environment if destructive findings detected
253
- - `1` if one or more `error` findings are detected
254
- - `0` if no `error` findings are detected (warnings alone do not fail)
314
+ In live mode, exit code `2` is used when drift is detected between `state.json` and the live database. For all exit codes used by `validate`, see [Exit code standards](#exit-code-standards).
255
315
 
256
- Exit codes:
316
+ ### `schema-forge doctor`
257
317
 
258
- - `1` when one or more `error` findings are detected
259
- - `0` when no `error` findings are detected (warnings alone do not fail)
318
+ Check live database drift against your tracked `state.json`.
319
+
320
+ ```bash
321
+ schema-forge doctor --url "$DATABASE_URL"
322
+ ```
323
+
324
+ Use JSON mode for CI and automation:
325
+
326
+ ```bash
327
+ schema-forge doctor --url "$DATABASE_URL" --json
328
+ ```
329
+
330
+ Options:
331
+
332
+ - `--url` - PostgreSQL connection URL (fallback: `DATABASE_URL`)
333
+ - `--schema` - Comma-separated schema names to introspect (default: `public`)
334
+ - `--json` - Output structured drift report JSON
335
+
336
+ Exit codes: see [Exit code standards](#exit-code-standards) (doctor uses 0, 2).
337
+
338
+ ### `schema-forge introspect`
339
+
340
+ Extract normalized schema directly from a live PostgreSQL database.
341
+
342
+ ```bash
343
+ schema-forge introspect --url "$DATABASE_URL" --json
344
+ ```
345
+
346
+ **Options:**
347
+
348
+ - `--url` - PostgreSQL connection URL (fallback: `DATABASE_URL`)
349
+ - `--schema` - Comma-separated schema names to introspect (default: `public`)
350
+ - `--json` - Output normalized schema as JSON
351
+ - `--out <path>` - Write normalized schema JSON to a file
260
352
 
261
353
  ## CI Behavior
262
354
 
@@ -269,16 +361,29 @@ CI mode is automatically activated when either environment variable is set:
269
361
  - `CI=true`
270
362
  - `CONTINUOUS_INTEGRATION=true`
271
363
 
272
- ### Exit Codes
364
+ ### Exit code standards
365
+
366
+ SchemaForge uses specific exit codes for deterministic CI and script behavior. The following is the single source of truth.
367
+
368
+ | Exit Code | Name | Meaning |
369
+ | --------- | ---- | ------- |
370
+ | `0` | SUCCESS | No changes or no destructive operations detected |
371
+ | `1` | VALIDATION_ERROR | Invalid DSL, config errors, missing files, or operation declined (e.g. with `--safe`) |
372
+ | `2` | DRIFT_DETECTED | Drift between expected state and live database schema |
373
+ | `3` | CI_DESTRUCTIVE | Destructive operations detected in CI without `--force` |
374
+
375
+ **Per-command exit codes:**
273
376
 
274
- SchemaForge uses specific exit codes for different scenarios:
377
+ | Command | Possible exit codes |
378
+ | ------- | -------------------- |
379
+ | `validate` | 0, 1, 2, 3 |
380
+ | `doctor` | 0, 2 |
381
+ | `diff`, `generate` | 0, 1, 3 |
382
+ | `init`, `import` | 0 |
275
383
 
276
- | Exit Code | Meaning |
277
- | --------- | ------- |
278
- | `0` | Success - no changes or no destructive operations detected |
279
- | `1` | General error - validation failed, operation declined, missing files, etc. |
280
- | `2` | Schema validation error - invalid DSL syntax or structure |
281
- | `3` | **CI Destructive** - destructive operations detected in CI environment without `--force` |
384
+ (Global CLI errors, e.g. unknown command or missing config, exit with 1.)
385
+
386
+ A machine-readable exit code contract is available at [docs/exit-codes.json](docs/exit-codes.json). It includes a `version` field and an optional `commands` map for tooling.
282
387
 
283
388
  ### Destructive Operations in CI
284
389
 
@@ -312,6 +417,19 @@ When `CI=true`, SchemaForge will:
312
417
  - ✅ Allow explicit override with `--force` flag
313
418
  - ❌ Not accept user input for confirmation
314
419
 
420
+ ### Drift Integration Tests in CI
421
+
422
+ For drift reliability checks against a real database, run:
423
+
424
+ ```bash
425
+ npm run test:integration:drift
426
+ ```
427
+
428
+ The integration harness supports two deterministic paths:
429
+
430
+ - `SF_USE_CI_POSTGRES=true` + `DATABASE_URL`: uses the CI Postgres service directly.
431
+ - No CI Postgres env: spins up an isolated Testcontainers Postgres instance.
432
+
315
433
  ### Using `--safe` in CI
316
434
 
317
435
  The `--safe` flag is compatible with CI and blocks execution of destructive operations:
@@ -323,6 +441,20 @@ schema-forge generate --safe
323
441
 
324
442
  This is useful for strict CI pipelines where all destructive changes must be reviewed and merged separately.
325
443
 
444
+ ### Using exit codes in CI
445
+
446
+ In CI, rely on the process exit code to fail the job when validation or safety checks fail. Example with a shell script:
447
+
448
+ ```bash
449
+ schema-forge validate --json
450
+ if [ $? -ne 0 ]; then
451
+ echo "Schema validation failed (exit code: $?)"
452
+ exit 1
453
+ fi
454
+ ```
455
+
456
+ When using the [schema-forge-action](https://github.com/xubylele/schema-forge-action), the action passes through the CLI exit code: a non-zero exit from Schema Forge fails the job with that same code, so no extra script is needed.
457
+
326
458
  ## Constraint Change Detection
327
459
 
328
460
  SchemaForge detects and generates migrations for:
package/dist/api.d.ts ADDED
@@ -0,0 +1,101 @@
1
+ interface DiffOptions {
2
+ safe?: boolean;
3
+ force?: boolean;
4
+ url?: string;
5
+ schema?: string;
6
+ }
7
+
8
+ interface DoctorOptions {
9
+ json?: boolean;
10
+ url?: string;
11
+ schema?: string;
12
+ }
13
+
14
+ interface GenerateOptions {
15
+ name?: string;
16
+ safe?: boolean;
17
+ force?: boolean;
18
+ }
19
+
20
+ interface ImportOptions {
21
+ out?: string;
22
+ }
23
+
24
+ interface IntrospectOptions {
25
+ url?: string;
26
+ schema?: string;
27
+ json?: boolean;
28
+ out?: string;
29
+ }
30
+
31
+ interface ValidateOptions {
32
+ json?: boolean;
33
+ url?: string;
34
+ schema?: string;
35
+ }
36
+
37
+ /**
38
+ * Exit codes used throughout the CLI for deterministic behavior
39
+ *
40
+ * @see SF-106 Standardize Exit Codes
41
+ */
42
+ declare const EXIT_CODES: {
43
+ /** Successful operation */
44
+ readonly SUCCESS: 0;
45
+ /** Validation error (invalid DSL syntax, config errors, missing files, etc.) */
46
+ readonly VALIDATION_ERROR: 1;
47
+ /** Drift detected - Reserved for future use when comparing actual DB state vs schema */
48
+ readonly DRIFT_DETECTED: 2;
49
+ /** Destructive operation detected in CI environment without --force */
50
+ readonly CI_DESTRUCTIVE: 3;
51
+ };
52
+
53
+ /**
54
+ * Programmatic API for Schema Forge.
55
+ * Use this entrypoint when integrating from Node (e.g. scripts, GitHub Actions)
56
+ * instead of invoking the CLI via shell.
57
+ *
58
+ * @example
59
+ * const { generate, EXIT_CODES } = require('@xubylele/schema-forge/api');
60
+ * const result = await generate({ name: 'MyMigration' });
61
+ * if (result.exitCode !== EXIT_CODES.SUCCESS) process.exit(result.exitCode);
62
+ */
63
+
64
+ /**
65
+ * Result of a programmatic command run. Exit codes match the CLI contract.
66
+ * @see docs/exit-codes.json
67
+ */
68
+ interface RunResult {
69
+ exitCode: number;
70
+ }
71
+ /**
72
+ * Initialize a new schema project in the current directory.
73
+ */
74
+ declare function init(): Promise<RunResult>;
75
+ /**
76
+ * Generate SQL migration from schema files.
77
+ */
78
+ declare function generate(options?: GenerateOptions): Promise<RunResult>;
79
+ /**
80
+ * Compare two schema versions and generate migration SQL (optionally against live DB).
81
+ */
82
+ declare function diff(options?: DiffOptions): Promise<RunResult>;
83
+ /**
84
+ * Check live database drift against state.
85
+ */
86
+ declare function doctor(options?: DoctorOptions): Promise<RunResult>;
87
+ /**
88
+ * Validate schema and optionally check for destructive changes or live drift.
89
+ */
90
+ declare function validate(options?: ValidateOptions): Promise<RunResult>;
91
+ /**
92
+ * Extract normalized live schema from PostgreSQL.
93
+ */
94
+ declare function introspect(options?: IntrospectOptions): Promise<RunResult>;
95
+ /**
96
+ * Import schema from SQL migrations.
97
+ * @param inputPath - Path to .sql file or migrations directory
98
+ */
99
+ declare function importSchema(inputPath: string, options?: ImportOptions): Promise<RunResult>;
100
+
101
+ export { type DiffOptions, type DoctorOptions, EXIT_CODES, type GenerateOptions, type ImportOptions, type IntrospectOptions, type RunResult, type ValidateOptions, diff, doctor, generate, importSchema, init, introspect, validate };