@xubylele/schema-forge 1.10.1 → 1.11.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 +18 -2
- package/dist/api.d.ts +5 -0
- package/dist/api.js +25 -1
- package/dist/cli.js +133 -76
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -210,12 +210,13 @@ Creates the necessary directory structure and configuration files.
|
|
|
210
210
|
Generate SQL migration from schema changes.
|
|
211
211
|
|
|
212
212
|
```bash
|
|
213
|
-
schema-forge generate [--name "migration description"] [--safe] [--force]
|
|
213
|
+
schema-forge generate [--name "migration description"] [--migration-format hyphen|underscore] [--safe] [--force]
|
|
214
214
|
```
|
|
215
215
|
|
|
216
216
|
**Options:**
|
|
217
217
|
|
|
218
218
|
- `--name` - Optional name for the migration (default: "migration")
|
|
219
|
+
- `--migration-format <format>` - Migration file name: `hyphen` (e.g. `20250315120000-add-users.sql`) or `underscore` (e.g. `20250315120000_add_users.sql`, Supabase CLI style). Overrides `migrationFileNameFormat` in config.
|
|
219
220
|
- `--safe` - Block execution if destructive operations are detected (exits with error)
|
|
220
221
|
- `--force` - Bypass safety checks and proceed with destructive operations (shows warning)
|
|
221
222
|
|
|
@@ -269,6 +270,20 @@ Behavior:
|
|
|
269
270
|
- Ignores unsupported SQL safely and prints warnings
|
|
270
271
|
- Writes a normalized SchemaForge DSL schema file
|
|
271
272
|
|
|
273
|
+
### `schema-forge config`
|
|
274
|
+
|
|
275
|
+
Update `schemaforge/config.json` settings without editing the file.
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
schema-forge config migration-format <format>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Subcommands:**
|
|
282
|
+
|
|
283
|
+
- **`migration-format <format>`** – Set migration file name format: `hyphen` (e.g. `20250315120000-add-users.sql`) or `underscore` (e.g. `20250315120000_add_users.sql`, Supabase CLI style). Writes `migrationFileNameFormat` to config.
|
|
284
|
+
|
|
285
|
+
Requires an initialized project. Other config keys are left unchanged.
|
|
286
|
+
|
|
272
287
|
### `schema-forge validate`
|
|
273
288
|
|
|
274
289
|
Detect destructive or risky schema changes before generating/applying migrations.
|
|
@@ -626,7 +641,8 @@ The `schemaforge/config.json` file contains project configuration. The `provider
|
|
|
626
641
|
```
|
|
627
642
|
|
|
628
643
|
- **postgres**: `provider: "postgres"`, `outputDir: "migrations"`.
|
|
629
|
-
- **supabase**: `provider: "supabase"`, `outputDir: "supabase/migrations"`.
|
|
644
|
+
- **supabase**: `provider: "supabase"`, `outputDir: "supabase/migrations"`. Init also sets `migrationFileNameFormat: "hyphen"` so migrations are named `timestamp-name.sql` (Supabase CLI uses `timestamp_name.sql` by default; you can set `migrationFileNameFormat: "underscore"` in config or use `--migration-format underscore` to match).
|
|
645
|
+
- **migrationFileNameFormat** (optional): `"hyphen"` (default) or `"underscore"`. Controls generated migration file names: `YYYYMMDDHHmmss-name.sql` vs `YYYYMMDDHHmmss_name.sql`. You can set it via **`schema-forge config migration-format <hyphen|underscore>`** instead of editing the file.
|
|
630
646
|
|
|
631
647
|
## Supported Databases
|
|
632
648
|
|
package/dist/api.d.ts
CHANGED
|
@@ -11,10 +11,15 @@ interface DoctorOptions {
|
|
|
11
11
|
schema?: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/** Migration file name format: hyphen (timestamp-name.sql) or underscore (timestamp_name.sql, Supabase CLI style). */
|
|
15
|
+
type MigrationFileNameFormat = 'hyphen' | 'underscore';
|
|
16
|
+
|
|
14
17
|
interface GenerateOptions {
|
|
15
18
|
name?: string;
|
|
16
19
|
safe?: boolean;
|
|
17
20
|
force?: boolean;
|
|
21
|
+
/** Override migration file name format: hyphen (timestamp-name.sql) or underscore (timestamp_name.sql). */
|
|
22
|
+
migrationFormat?: MigrationFileNameFormat;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
interface ImportOptions {
|
package/dist/api.js
CHANGED
|
@@ -3241,6 +3241,10 @@ function nowTimestamp2() {
|
|
|
3241
3241
|
function slugifyName2(name) {
|
|
3242
3242
|
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "migration";
|
|
3243
3243
|
}
|
|
3244
|
+
function migrationFileName(timestamp, slug, format = "hyphen") {
|
|
3245
|
+
const sep = format === "underscore" ? "_" : "-";
|
|
3246
|
+
return `${timestamp}${sep}${slug}.sql`;
|
|
3247
|
+
}
|
|
3244
3248
|
|
|
3245
3249
|
// src/commands/generate.ts
|
|
3246
3250
|
var REQUIRED_CONFIG_FIELDS = [
|
|
@@ -3248,6 +3252,19 @@ var REQUIRED_CONFIG_FIELDS = [
|
|
|
3248
3252
|
"stateFile",
|
|
3249
3253
|
"outputDir"
|
|
3250
3254
|
];
|
|
3255
|
+
var MIGRATION_FORMATS = ["hyphen", "underscore"];
|
|
3256
|
+
function resolveMigrationFormat(cliFormat, configFormat) {
|
|
3257
|
+
if (cliFormat !== void 0 && cliFormat !== "") {
|
|
3258
|
+
const normalized = cliFormat.trim().toLowerCase();
|
|
3259
|
+
if (MIGRATION_FORMATS.includes(normalized)) {
|
|
3260
|
+
return normalized;
|
|
3261
|
+
}
|
|
3262
|
+
throw new Error(
|
|
3263
|
+
`Invalid --migration-format "${cliFormat}". Allowed: ${MIGRATION_FORMATS.join(", ")}.`
|
|
3264
|
+
);
|
|
3265
|
+
}
|
|
3266
|
+
return configFormat ?? "hyphen";
|
|
3267
|
+
}
|
|
3251
3268
|
function resolveConfigPath3(root, targetPath) {
|
|
3252
3269
|
return import_path9.default.isAbsolute(targetPath) ? targetPath : import_path9.default.join(root, targetPath);
|
|
3253
3270
|
}
|
|
@@ -3328,7 +3345,11 @@ Remove --safe flag or modify schema to avoid destructive changes.`
|
|
|
3328
3345
|
const sql = await generateSql2(diff2, provider, config.sql);
|
|
3329
3346
|
const timestamp = nowTimestamp2();
|
|
3330
3347
|
const slug = slugifyName2(options.name ?? "migration");
|
|
3331
|
-
const
|
|
3348
|
+
const format = resolveMigrationFormat(
|
|
3349
|
+
options.migrationFormat,
|
|
3350
|
+
config.migrationFileNameFormat
|
|
3351
|
+
);
|
|
3352
|
+
const fileName = migrationFileName(timestamp, slug, format);
|
|
3332
3353
|
await ensureDir(outputDir);
|
|
3333
3354
|
const migrationPath = import_path9.default.join(outputDir, fileName);
|
|
3334
3355
|
await writeTextFile(migrationPath, sql + "\n");
|
|
@@ -3464,6 +3485,9 @@ table users {
|
|
|
3464
3485
|
timestampDefault: "now()"
|
|
3465
3486
|
}
|
|
3466
3487
|
};
|
|
3488
|
+
if (provider === "supabase") {
|
|
3489
|
+
config.migrationFileNameFormat = "hyphen";
|
|
3490
|
+
}
|
|
3467
3491
|
await writeJsonFile(configPath, config);
|
|
3468
3492
|
success(`Created ${configPath}`);
|
|
3469
3493
|
const state = {
|
package/dist/cli.js
CHANGED
|
@@ -1130,14 +1130,14 @@ var init_validate = __esm({
|
|
|
1130
1130
|
// node_modules/@xubylele/schema-forge-core/dist/core/fs.js
|
|
1131
1131
|
async function ensureDir2(dirPath) {
|
|
1132
1132
|
try {
|
|
1133
|
-
await
|
|
1133
|
+
await import_fs3.promises.mkdir(dirPath, { recursive: true });
|
|
1134
1134
|
} catch (error2) {
|
|
1135
1135
|
throw new Error(`Failed to create directory ${dirPath}: ${error2}`);
|
|
1136
1136
|
}
|
|
1137
1137
|
}
|
|
1138
1138
|
async function fileExists2(filePath) {
|
|
1139
1139
|
try {
|
|
1140
|
-
await
|
|
1140
|
+
await import_fs3.promises.access(filePath);
|
|
1141
1141
|
return true;
|
|
1142
1142
|
} catch {
|
|
1143
1143
|
return false;
|
|
@@ -1145,7 +1145,7 @@ async function fileExists2(filePath) {
|
|
|
1145
1145
|
}
|
|
1146
1146
|
async function readTextFile2(filePath) {
|
|
1147
1147
|
try {
|
|
1148
|
-
return await
|
|
1148
|
+
return await import_fs3.promises.readFile(filePath, "utf-8");
|
|
1149
1149
|
} catch (error2) {
|
|
1150
1150
|
throw new Error(`Failed to read file ${filePath}: ${error2}`);
|
|
1151
1151
|
}
|
|
@@ -1154,7 +1154,7 @@ async function writeTextFile2(filePath, content) {
|
|
|
1154
1154
|
try {
|
|
1155
1155
|
const dir = import_path3.default.dirname(filePath);
|
|
1156
1156
|
await ensureDir2(dir);
|
|
1157
|
-
await
|
|
1157
|
+
await import_fs3.promises.writeFile(filePath, content, "utf-8");
|
|
1158
1158
|
} catch (error2) {
|
|
1159
1159
|
throw new Error(`Failed to write file ${filePath}: ${error2}`);
|
|
1160
1160
|
}
|
|
@@ -1182,7 +1182,7 @@ async function writeJsonFile2(filePath, data) {
|
|
|
1182
1182
|
async function findFiles(dirPath, pattern) {
|
|
1183
1183
|
const results = [];
|
|
1184
1184
|
try {
|
|
1185
|
-
const items = await
|
|
1185
|
+
const items = await import_fs3.promises.readdir(dirPath, { withFileTypes: true });
|
|
1186
1186
|
for (const item of items) {
|
|
1187
1187
|
const fullPath = import_path3.default.join(dirPath, item.name);
|
|
1188
1188
|
if (item.isDirectory()) {
|
|
@@ -1197,11 +1197,11 @@ async function findFiles(dirPath, pattern) {
|
|
|
1197
1197
|
}
|
|
1198
1198
|
return results;
|
|
1199
1199
|
}
|
|
1200
|
-
var
|
|
1200
|
+
var import_fs3, import_path3;
|
|
1201
1201
|
var init_fs = __esm({
|
|
1202
1202
|
"node_modules/@xubylele/schema-forge-core/dist/core/fs.js"() {
|
|
1203
1203
|
"use strict";
|
|
1204
|
-
|
|
1204
|
+
import_fs3 = require("fs");
|
|
1205
1205
|
import_path3 = __toESM(require("path"), 1);
|
|
1206
1206
|
}
|
|
1207
1207
|
});
|
|
@@ -2204,7 +2204,7 @@ var init_schema_to_dsl = __esm({
|
|
|
2204
2204
|
|
|
2205
2205
|
// node_modules/@xubylele/schema-forge-core/dist/core/sql/load-migrations.js
|
|
2206
2206
|
async function loadMigrationSqlInput(inputPath) {
|
|
2207
|
-
const stats = await
|
|
2207
|
+
const stats = await import_fs5.promises.stat(inputPath);
|
|
2208
2208
|
if (stats.isFile()) {
|
|
2209
2209
|
if (!inputPath.toLowerCase().endsWith(".sql")) {
|
|
2210
2210
|
throw new Error(`Input file must be a .sql file: ${inputPath}`);
|
|
@@ -2225,11 +2225,11 @@ async function loadMigrationSqlInput(inputPath) {
|
|
|
2225
2225
|
}
|
|
2226
2226
|
return result;
|
|
2227
2227
|
}
|
|
2228
|
-
var
|
|
2228
|
+
var import_fs5, import_path5;
|
|
2229
2229
|
var init_load_migrations = __esm({
|
|
2230
2230
|
"node_modules/@xubylele/schema-forge-core/dist/core/sql/load-migrations.js"() {
|
|
2231
2231
|
"use strict";
|
|
2232
|
-
|
|
2232
|
+
import_fs5 = require("fs");
|
|
2233
2233
|
import_path5 = __toESM(require("path"), 1);
|
|
2234
2234
|
init_fs();
|
|
2235
2235
|
}
|
|
@@ -2733,7 +2733,7 @@ var import_commander8 = require("commander");
|
|
|
2733
2733
|
// package.json
|
|
2734
2734
|
var package_default = {
|
|
2735
2735
|
name: "@xubylele/schema-forge",
|
|
2736
|
-
version: "1.
|
|
2736
|
+
version: "1.11.0",
|
|
2737
2737
|
description: "Universal migration generator from schema DSL",
|
|
2738
2738
|
main: "dist/cli.js",
|
|
2739
2739
|
type: "commonjs",
|
|
@@ -2802,10 +2802,6 @@ var package_default = {
|
|
|
2802
2802
|
}
|
|
2803
2803
|
};
|
|
2804
2804
|
|
|
2805
|
-
// src/commands/diff.ts
|
|
2806
|
-
var import_commander = require("commander");
|
|
2807
|
-
var import_path7 = __toESM(require("path"));
|
|
2808
|
-
|
|
2809
2805
|
// src/core/fs.ts
|
|
2810
2806
|
var import_fs = require("fs");
|
|
2811
2807
|
var import_path = __toESM(require("path"));
|
|
@@ -2884,6 +2880,95 @@ function getStatePath(root, config) {
|
|
|
2884
2880
|
return import_path2.default.join(schemaForgeDir, fileName);
|
|
2885
2881
|
}
|
|
2886
2882
|
|
|
2883
|
+
// src/utils/exitCodes.ts
|
|
2884
|
+
var EXIT_CODES = {
|
|
2885
|
+
/** Successful operation */
|
|
2886
|
+
SUCCESS: 0,
|
|
2887
|
+
/** Validation error (invalid DSL syntax, config errors, missing files, etc.) */
|
|
2888
|
+
VALIDATION_ERROR: 1,
|
|
2889
|
+
/** Drift detected - Reserved for future use when comparing actual DB state vs schema */
|
|
2890
|
+
DRIFT_DETECTED: 2,
|
|
2891
|
+
/** Destructive operation detected in CI environment without --force */
|
|
2892
|
+
CI_DESTRUCTIVE: 3
|
|
2893
|
+
};
|
|
2894
|
+
function shouldFailCIDestructive(isCIEnvironment, hasDestructiveFindings2, isForceEnabled) {
|
|
2895
|
+
return isCIEnvironment && hasDestructiveFindings2 && !isForceEnabled;
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
// src/utils/output.ts
|
|
2899
|
+
var import_boxen = __toESM(require("boxen"));
|
|
2900
|
+
var import_chalk = require("chalk");
|
|
2901
|
+
var isInteractive = Boolean(process.stdout?.isTTY);
|
|
2902
|
+
var colorsEnabled = isInteractive && process.env.FORCE_COLOR !== "0" && !("NO_COLOR" in process.env);
|
|
2903
|
+
var color = new import_chalk.Chalk({ level: colorsEnabled ? 3 : 0 });
|
|
2904
|
+
var theme = {
|
|
2905
|
+
primary: color.cyanBright,
|
|
2906
|
+
success: color.hex("#00FF88"),
|
|
2907
|
+
warning: color.hex("#FFD166"),
|
|
2908
|
+
error: color.hex("#EF476F"),
|
|
2909
|
+
accent: color.magentaBright
|
|
2910
|
+
};
|
|
2911
|
+
function success(message) {
|
|
2912
|
+
const text = theme.success(`[OK] ${message}`);
|
|
2913
|
+
if (!isInteractive) {
|
|
2914
|
+
console.log(text);
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2917
|
+
try {
|
|
2918
|
+
console.log(
|
|
2919
|
+
(0, import_boxen.default)(text, {
|
|
2920
|
+
padding: 1,
|
|
2921
|
+
borderColor: "cyan",
|
|
2922
|
+
borderStyle: "round"
|
|
2923
|
+
})
|
|
2924
|
+
);
|
|
2925
|
+
} catch {
|
|
2926
|
+
console.log(text);
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
function info(message) {
|
|
2930
|
+
console.log(theme.primary(message));
|
|
2931
|
+
}
|
|
2932
|
+
function warning(message) {
|
|
2933
|
+
console.warn(theme.warning(`[WARN] ${message}`));
|
|
2934
|
+
}
|
|
2935
|
+
function error(message) {
|
|
2936
|
+
console.error(theme.error(`[ERROR] ${message}`));
|
|
2937
|
+
}
|
|
2938
|
+
function forceWarning(message) {
|
|
2939
|
+
console.error(theme.warning(`[FORCE] ${message}`));
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
// src/commands/config.ts
|
|
2943
|
+
var MIGRATION_FORMATS = ["hyphen", "underscore"];
|
|
2944
|
+
async function runConfig(options) {
|
|
2945
|
+
const root = getProjectRoot();
|
|
2946
|
+
const configPath = getConfigPath(root);
|
|
2947
|
+
if (!await fileExists(configPath)) {
|
|
2948
|
+
throw new Error('SchemaForge project not initialized. Run "schema-forge init" first.');
|
|
2949
|
+
}
|
|
2950
|
+
const config = await readJsonFile(configPath, {});
|
|
2951
|
+
if (options.migrationFormat !== void 0) {
|
|
2952
|
+
const format = options.migrationFormat.trim().toLowerCase();
|
|
2953
|
+
if (!MIGRATION_FORMATS.includes(format)) {
|
|
2954
|
+
throw new Error(
|
|
2955
|
+
`Invalid migration format "${options.migrationFormat}". Allowed: ${MIGRATION_FORMATS.join(", ")}.`
|
|
2956
|
+
);
|
|
2957
|
+
}
|
|
2958
|
+
config.migrationFileNameFormat = format;
|
|
2959
|
+
}
|
|
2960
|
+
await writeJsonFile(configPath, config);
|
|
2961
|
+
success(`Updated ${configPath}`);
|
|
2962
|
+
if (options.migrationFormat !== void 0) {
|
|
2963
|
+
success(`migrationFileNameFormat set to "${config.migrationFileNameFormat}" (${config.migrationFileNameFormat === "hyphen" ? "timestamp-name.sql" : "timestamp_name.sql"})`);
|
|
2964
|
+
}
|
|
2965
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
// src/commands/diff.ts
|
|
2969
|
+
var import_commander = require("commander");
|
|
2970
|
+
var import_path7 = __toESM(require("path"));
|
|
2971
|
+
|
|
2887
2972
|
// src/core/provider.ts
|
|
2888
2973
|
var DEFAULT_PROVIDER = "postgres";
|
|
2889
2974
|
function resolveProvider(provider) {
|
|
@@ -3004,65 +3089,6 @@ async function withPostgresQueryExecutor(connectionString, run) {
|
|
|
3004
3089
|
}
|
|
3005
3090
|
}
|
|
3006
3091
|
|
|
3007
|
-
// src/utils/exitCodes.ts
|
|
3008
|
-
var EXIT_CODES = {
|
|
3009
|
-
/** Successful operation */
|
|
3010
|
-
SUCCESS: 0,
|
|
3011
|
-
/** Validation error (invalid DSL syntax, config errors, missing files, etc.) */
|
|
3012
|
-
VALIDATION_ERROR: 1,
|
|
3013
|
-
/** Drift detected - Reserved for future use when comparing actual DB state vs schema */
|
|
3014
|
-
DRIFT_DETECTED: 2,
|
|
3015
|
-
/** Destructive operation detected in CI environment without --force */
|
|
3016
|
-
CI_DESTRUCTIVE: 3
|
|
3017
|
-
};
|
|
3018
|
-
function shouldFailCIDestructive(isCIEnvironment, hasDestructiveFindings2, isForceEnabled) {
|
|
3019
|
-
return isCIEnvironment && hasDestructiveFindings2 && !isForceEnabled;
|
|
3020
|
-
}
|
|
3021
|
-
|
|
3022
|
-
// src/utils/output.ts
|
|
3023
|
-
var import_boxen = __toESM(require("boxen"));
|
|
3024
|
-
var import_chalk = require("chalk");
|
|
3025
|
-
var isInteractive = Boolean(process.stdout?.isTTY);
|
|
3026
|
-
var colorsEnabled = isInteractive && process.env.FORCE_COLOR !== "0" && !("NO_COLOR" in process.env);
|
|
3027
|
-
var color = new import_chalk.Chalk({ level: colorsEnabled ? 3 : 0 });
|
|
3028
|
-
var theme = {
|
|
3029
|
-
primary: color.cyanBright,
|
|
3030
|
-
success: color.hex("#00FF88"),
|
|
3031
|
-
warning: color.hex("#FFD166"),
|
|
3032
|
-
error: color.hex("#EF476F"),
|
|
3033
|
-
accent: color.magentaBright
|
|
3034
|
-
};
|
|
3035
|
-
function success(message) {
|
|
3036
|
-
const text = theme.success(`[OK] ${message}`);
|
|
3037
|
-
if (!isInteractive) {
|
|
3038
|
-
console.log(text);
|
|
3039
|
-
return;
|
|
3040
|
-
}
|
|
3041
|
-
try {
|
|
3042
|
-
console.log(
|
|
3043
|
-
(0, import_boxen.default)(text, {
|
|
3044
|
-
padding: 1,
|
|
3045
|
-
borderColor: "cyan",
|
|
3046
|
-
borderStyle: "round"
|
|
3047
|
-
})
|
|
3048
|
-
);
|
|
3049
|
-
} catch {
|
|
3050
|
-
console.log(text);
|
|
3051
|
-
}
|
|
3052
|
-
}
|
|
3053
|
-
function info(message) {
|
|
3054
|
-
console.log(theme.primary(message));
|
|
3055
|
-
}
|
|
3056
|
-
function warning(message) {
|
|
3057
|
-
console.warn(theme.warning(`[WARN] ${message}`));
|
|
3058
|
-
}
|
|
3059
|
-
function error(message) {
|
|
3060
|
-
console.error(theme.error(`[ERROR] ${message}`));
|
|
3061
|
-
}
|
|
3062
|
-
function forceWarning(message) {
|
|
3063
|
-
console.error(theme.warning(`[FORCE] ${message}`));
|
|
3064
|
-
}
|
|
3065
|
-
|
|
3066
3092
|
// src/utils/prompt.ts
|
|
3067
3093
|
var import_node_readline = __toESM(require("readline"));
|
|
3068
3094
|
function isCI() {
|
|
@@ -3306,6 +3332,10 @@ function nowTimestamp2() {
|
|
|
3306
3332
|
function slugifyName2(name) {
|
|
3307
3333
|
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "migration";
|
|
3308
3334
|
}
|
|
3335
|
+
function migrationFileName(timestamp, slug, format = "hyphen") {
|
|
3336
|
+
const sep = format === "underscore" ? "_" : "-";
|
|
3337
|
+
return `${timestamp}${sep}${slug}.sql`;
|
|
3338
|
+
}
|
|
3309
3339
|
|
|
3310
3340
|
// src/commands/generate.ts
|
|
3311
3341
|
var REQUIRED_CONFIG_FIELDS = [
|
|
@@ -3313,6 +3343,19 @@ var REQUIRED_CONFIG_FIELDS = [
|
|
|
3313
3343
|
"stateFile",
|
|
3314
3344
|
"outputDir"
|
|
3315
3345
|
];
|
|
3346
|
+
var MIGRATION_FORMATS2 = ["hyphen", "underscore"];
|
|
3347
|
+
function resolveMigrationFormat(cliFormat, configFormat) {
|
|
3348
|
+
if (cliFormat !== void 0 && cliFormat !== "") {
|
|
3349
|
+
const normalized = cliFormat.trim().toLowerCase();
|
|
3350
|
+
if (MIGRATION_FORMATS2.includes(normalized)) {
|
|
3351
|
+
return normalized;
|
|
3352
|
+
}
|
|
3353
|
+
throw new Error(
|
|
3354
|
+
`Invalid --migration-format "${cliFormat}". Allowed: ${MIGRATION_FORMATS2.join(", ")}.`
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
return configFormat ?? "hyphen";
|
|
3358
|
+
}
|
|
3316
3359
|
function resolveConfigPath3(root, targetPath) {
|
|
3317
3360
|
return import_path9.default.isAbsolute(targetPath) ? targetPath : import_path9.default.join(root, targetPath);
|
|
3318
3361
|
}
|
|
@@ -3393,7 +3436,11 @@ Remove --safe flag or modify schema to avoid destructive changes.`
|
|
|
3393
3436
|
const sql = await generateSql2(diff, provider, config.sql);
|
|
3394
3437
|
const timestamp = nowTimestamp2();
|
|
3395
3438
|
const slug = slugifyName2(options.name ?? "migration");
|
|
3396
|
-
const
|
|
3439
|
+
const format = resolveMigrationFormat(
|
|
3440
|
+
options.migrationFormat,
|
|
3441
|
+
config.migrationFileNameFormat
|
|
3442
|
+
);
|
|
3443
|
+
const fileName = migrationFileName(timestamp, slug, format);
|
|
3397
3444
|
await ensureDir(outputDir);
|
|
3398
3445
|
const migrationPath = import_path9.default.join(outputDir, fileName);
|
|
3399
3446
|
await writeTextFile(migrationPath, sql + "\n");
|
|
@@ -3529,6 +3576,9 @@ table users {
|
|
|
3529
3576
|
timestampDefault: "now()"
|
|
3530
3577
|
}
|
|
3531
3578
|
};
|
|
3579
|
+
if (provider === "supabase") {
|
|
3580
|
+
config.migrationFileNameFormat = "hyphen";
|
|
3581
|
+
}
|
|
3532
3582
|
await writeJsonFile(configPath, config);
|
|
3533
3583
|
success(`Created ${configPath}`);
|
|
3534
3584
|
const state = {
|
|
@@ -3752,7 +3802,7 @@ program.command("init").description(
|
|
|
3752
3802
|
await handleError(error2);
|
|
3753
3803
|
}
|
|
3754
3804
|
});
|
|
3755
|
-
program.command("generate").description("Generate SQL from schema files. In CI environments (CI=true), exits with code 3 if destructive operations are detected unless --force is used.").option("--name <string>", "Schema name to generate").action(async (options) => {
|
|
3805
|
+
program.command("generate").description("Generate SQL from schema files. In CI environments (CI=true), exits with code 3 if destructive operations are detected unless --force is used.").option("--name <string>", "Schema name to generate").option("--migration-format <format>", "Migration file name: hyphen (timestamp-name.sql) or underscore (timestamp_name.sql, Supabase CLI style)").action(async (options) => {
|
|
3756
3806
|
try {
|
|
3757
3807
|
const globalOptions = program.opts();
|
|
3758
3808
|
validateFlagExclusivity(globalOptions);
|
|
@@ -3791,6 +3841,13 @@ program.command("import").description("Import schema from SQL migrations").argum
|
|
|
3791
3841
|
await handleError(error2);
|
|
3792
3842
|
}
|
|
3793
3843
|
});
|
|
3844
|
+
program.command("config").description("Update schemaforge/config.json settings").command("migration-format <format>").description("Set migration file name format: hyphen (timestamp-name.sql) or underscore (timestamp_name.sql)").action(async (format) => {
|
|
3845
|
+
try {
|
|
3846
|
+
await runConfig({ migrationFormat: format });
|
|
3847
|
+
} catch (error2) {
|
|
3848
|
+
await handleError(error2);
|
|
3849
|
+
}
|
|
3850
|
+
});
|
|
3794
3851
|
program.command("validate").description("Detect destructive or risky schema changes. In CI environments (CI=true), exits with code 3 if destructive operations are detected unless --force is used.").option("--json", "Output structured JSON").option("--url <string>", "PostgreSQL connection URL for live drift validation (defaults to DATABASE_URL)").option("--schema <list>", "Comma-separated schema names to introspect (default: public)").action(async (options) => {
|
|
3795
3852
|
try {
|
|
3796
3853
|
const globalOptions = program.opts();
|