@insforge/cli 0.1.53-dev.0 → 0.1.53-dev.1
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/dist/index.js +166 -77
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2678,7 +2678,7 @@ import { join as join8 } from "path";
|
|
|
2678
2678
|
// src/lib/migrations.ts
|
|
2679
2679
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
2680
2680
|
import { join as join7 } from "path";
|
|
2681
|
-
var MIGRATION_FILENAME_REGEX = /^(
|
|
2681
|
+
var MIGRATION_FILENAME_REGEX = /^(\d{14})_([a-z0-9-]+)\.sql$/;
|
|
2682
2682
|
function parseMigrationFilename(filename) {
|
|
2683
2683
|
const match = MIGRATION_FILENAME_REGEX.exec(filename);
|
|
2684
2684
|
if (!match) {
|
|
@@ -2686,10 +2686,32 @@ function parseMigrationFilename(filename) {
|
|
|
2686
2686
|
}
|
|
2687
2687
|
return {
|
|
2688
2688
|
filename,
|
|
2689
|
-
|
|
2689
|
+
version: match[1],
|
|
2690
2690
|
name: match[2]
|
|
2691
2691
|
};
|
|
2692
2692
|
}
|
|
2693
|
+
function compareMigrationVersions(left, right) {
|
|
2694
|
+
return left.localeCompare(right);
|
|
2695
|
+
}
|
|
2696
|
+
function formatMigrationVersion(date) {
|
|
2697
|
+
const year = String(date.getUTCFullYear());
|
|
2698
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
2699
|
+
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
2700
|
+
const hour = String(date.getUTCHours()).padStart(2, "0");
|
|
2701
|
+
const minute = String(date.getUTCMinutes()).padStart(2, "0");
|
|
2702
|
+
const second = String(date.getUTCSeconds()).padStart(2, "0");
|
|
2703
|
+
return `${year}${month}${day}${hour}${minute}${second}`;
|
|
2704
|
+
}
|
|
2705
|
+
function incrementMigrationVersion(version) {
|
|
2706
|
+
const year = Number(version.slice(0, 4));
|
|
2707
|
+
const month = Number(version.slice(4, 6)) - 1;
|
|
2708
|
+
const day = Number(version.slice(6, 8));
|
|
2709
|
+
const hour = Number(version.slice(8, 10));
|
|
2710
|
+
const minute = Number(version.slice(10, 12));
|
|
2711
|
+
const second = Number(version.slice(12, 14));
|
|
2712
|
+
const nextTimestamp = Date.UTC(year, month, day, hour, minute, second + 1);
|
|
2713
|
+
return formatMigrationVersion(new Date(nextTimestamp));
|
|
2714
|
+
}
|
|
2693
2715
|
function getMigrationsDir(cwd = process.cwd()) {
|
|
2694
2716
|
return join7(cwd, ".insforge", "migrations");
|
|
2695
2717
|
}
|
|
@@ -2712,82 +2734,85 @@ function parseStrictLocalMigrations(filenames) {
|
|
|
2712
2734
|
const parsedMigration = parseMigrationFilename(filename);
|
|
2713
2735
|
if (!parsedMigration) {
|
|
2714
2736
|
throw new CLIError(
|
|
2715
|
-
`Invalid migration filename: ${filename}. Expected <
|
|
2737
|
+
`Invalid migration filename: ${filename}. Expected <migration_version>_<migration-name>.sql.`
|
|
2716
2738
|
);
|
|
2717
2739
|
}
|
|
2718
2740
|
return parsedMigration;
|
|
2719
2741
|
});
|
|
2720
|
-
|
|
2721
|
-
return migrations.sort((left, right) => left.
|
|
2742
|
+
assertNoDuplicateMigrationVersions(migrations);
|
|
2743
|
+
return migrations.sort((left, right) => compareMigrationVersions(left.version, right.version));
|
|
2722
2744
|
}
|
|
2723
|
-
function
|
|
2745
|
+
function assertNoDuplicateMigrationVersions(migrations) {
|
|
2724
2746
|
const seen = /* @__PURE__ */ new Set();
|
|
2725
2747
|
for (const migration of migrations) {
|
|
2726
|
-
if (seen.has(migration.
|
|
2727
|
-
throw new CLIError(`Duplicate local migration
|
|
2748
|
+
if (seen.has(migration.version)) {
|
|
2749
|
+
throw new CLIError(`Duplicate local migration version found: ${migration.version}`);
|
|
2728
2750
|
}
|
|
2729
|
-
seen.add(migration.
|
|
2751
|
+
seen.add(migration.version);
|
|
2730
2752
|
}
|
|
2731
2753
|
}
|
|
2732
|
-
function
|
|
2733
|
-
const orderedMigrations = [...migrations].sort(
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2754
|
+
function getNextLocalMigrationVersion(migrations, latestRemoteVersion, now = /* @__PURE__ */ new Date()) {
|
|
2755
|
+
const orderedMigrations = [...migrations].sort(
|
|
2756
|
+
(left, right) => compareMigrationVersions(left.version, right.version)
|
|
2757
|
+
);
|
|
2758
|
+
assertNoDuplicateMigrationVersions(orderedMigrations);
|
|
2759
|
+
const latestKnownVersion = orderedMigrations.reduce(
|
|
2760
|
+
(latestVersion, migration) => {
|
|
2761
|
+
if (!latestVersion || compareMigrationVersions(migration.version, latestVersion) > 0) {
|
|
2762
|
+
return migration.version;
|
|
2763
|
+
}
|
|
2764
|
+
return latestVersion;
|
|
2765
|
+
},
|
|
2766
|
+
latestRemoteVersion
|
|
2767
|
+
);
|
|
2768
|
+
const currentVersion = formatMigrationVersion(now);
|
|
2769
|
+
if (!latestKnownVersion || compareMigrationVersions(currentVersion, latestKnownVersion) > 0) {
|
|
2770
|
+
return currentVersion;
|
|
2746
2771
|
}
|
|
2747
|
-
return
|
|
2772
|
+
return incrementMigrationVersion(latestKnownVersion);
|
|
2748
2773
|
}
|
|
2749
2774
|
function formatMigrationSql(statements) {
|
|
2750
2775
|
return statements.map((statement) => statement.trim().replace(/;\s*$/u, "")).filter(Boolean).join(";\n\n").concat(statements.length > 0 ? ";\n" : "");
|
|
2751
2776
|
}
|
|
2752
|
-
function
|
|
2777
|
+
function findLocalMigrationByVersion(version, filenames) {
|
|
2753
2778
|
const matches = filenames.map((filename) => parseMigrationFilename(filename)).filter(
|
|
2754
|
-
(migration) => migration !== null && migration.
|
|
2779
|
+
(migration) => migration !== null && migration.version === version
|
|
2755
2780
|
);
|
|
2756
2781
|
if (matches.length === 0) {
|
|
2757
|
-
throw new CLIError(`Local migration for
|
|
2782
|
+
throw new CLIError(`Local migration for version ${version} not found.`);
|
|
2758
2783
|
}
|
|
2759
2784
|
if (matches.length > 1) {
|
|
2760
2785
|
throw new CLIError(
|
|
2761
|
-
`Multiple local migration files found for
|
|
2786
|
+
`Multiple local migration files found for version ${version}.`
|
|
2762
2787
|
);
|
|
2763
2788
|
}
|
|
2764
2789
|
return matches[0];
|
|
2765
2790
|
}
|
|
2766
2791
|
function resolveMigrationTarget(target, filenames) {
|
|
2767
|
-
if (
|
|
2768
|
-
return
|
|
2792
|
+
if (/^\d{14}$/u.test(target)) {
|
|
2793
|
+
return findLocalMigrationByVersion(target, filenames);
|
|
2769
2794
|
}
|
|
2770
2795
|
const parsedTarget = parseMigrationFilename(target);
|
|
2771
2796
|
if (!parsedTarget) {
|
|
2772
2797
|
throw new CLIError(
|
|
2773
|
-
"Migration file names must match <
|
|
2798
|
+
"Migration file names must match <migration_version>_<migration-name>.sql."
|
|
2774
2799
|
);
|
|
2775
2800
|
}
|
|
2776
2801
|
if (!filenames.includes(target)) {
|
|
2777
2802
|
throw new CLIError(`Local migration file not found: ${target}`);
|
|
2778
2803
|
}
|
|
2779
|
-
return
|
|
2804
|
+
return findLocalMigrationByVersion(parsedTarget.version, filenames);
|
|
2780
2805
|
}
|
|
2781
2806
|
|
|
2782
2807
|
// src/commands/db/migrations.ts
|
|
2783
|
-
function
|
|
2808
|
+
function getLatestRemoteVersion(migrations) {
|
|
2784
2809
|
return migrations.reduce(
|
|
2785
|
-
(
|
|
2786
|
-
|
|
2810
|
+
(latestVersion, migration) => !latestVersion || compareMigrationVersions(migration.version, latestVersion) > 0 ? migration.version : latestVersion,
|
|
2811
|
+
null
|
|
2787
2812
|
);
|
|
2788
2813
|
}
|
|
2789
|
-
function buildMigrationFilename(
|
|
2790
|
-
return `${
|
|
2814
|
+
function buildMigrationFilename(version, name) {
|
|
2815
|
+
return `${version}_${name}.sql`;
|
|
2791
2816
|
}
|
|
2792
2817
|
function formatCreatedAt(createdAt) {
|
|
2793
2818
|
const date = new Date(createdAt);
|
|
@@ -2803,6 +2828,24 @@ function assertValidMigrationName(name) {
|
|
|
2803
2828
|
throw new CLIError("Migration name must use lowercase letters, numbers, and hyphens only.");
|
|
2804
2829
|
}
|
|
2805
2830
|
}
|
|
2831
|
+
async function applyMigration(targetMigration, sql) {
|
|
2832
|
+
const body = {
|
|
2833
|
+
version: targetMigration.version,
|
|
2834
|
+
name: targetMigration.name,
|
|
2835
|
+
sql
|
|
2836
|
+
};
|
|
2837
|
+
const res = await ossFetch("/api/database/migrations", {
|
|
2838
|
+
method: "POST",
|
|
2839
|
+
body: JSON.stringify(body)
|
|
2840
|
+
});
|
|
2841
|
+
const createdMigration = await res.json();
|
|
2842
|
+
if (createdMigration.version !== targetMigration.version) {
|
|
2843
|
+
throw new CLIError(
|
|
2844
|
+
`Applied migration version mismatch. Expected ${targetMigration.version}, received ${createdMigration.version}.`
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
return createdMigration;
|
|
2848
|
+
}
|
|
2806
2849
|
function registerDbMigrationsCommand(dbCmd2) {
|
|
2807
2850
|
const migrationsCmd = dbCmd2.command("migrations").description("Manage database migration files");
|
|
2808
2851
|
migrationsCmd.command("list").description("List applied remote database migrations").action(async (_opts, cmd) => {
|
|
@@ -2816,9 +2859,9 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
2816
2859
|
console.log("No database migrations found.");
|
|
2817
2860
|
} else {
|
|
2818
2861
|
outputTable(
|
|
2819
|
-
["
|
|
2862
|
+
["Version", "Name", "Created At"],
|
|
2820
2863
|
migrations.map((migration) => [
|
|
2821
|
-
|
|
2864
|
+
migration.version,
|
|
2822
2865
|
migration.name,
|
|
2823
2866
|
formatCreatedAt(migration.createdAt)
|
|
2824
2867
|
])
|
|
@@ -2839,10 +2882,10 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
2839
2882
|
const createdFiles = [];
|
|
2840
2883
|
const skippedFiles = [];
|
|
2841
2884
|
for (const migration of [...migrations].sort(
|
|
2842
|
-
(left, right) => left.
|
|
2885
|
+
(left, right) => compareMigrationVersions(left.version, right.version)
|
|
2843
2886
|
)) {
|
|
2844
2887
|
const filename = buildMigrationFilename(
|
|
2845
|
-
migration.
|
|
2888
|
+
migration.version,
|
|
2846
2889
|
migration.name
|
|
2847
2890
|
);
|
|
2848
2891
|
const filePath = join8(migrationsDir, filename);
|
|
@@ -2879,13 +2922,13 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
2879
2922
|
await requireAuth();
|
|
2880
2923
|
assertValidMigrationName(migrationName);
|
|
2881
2924
|
const migrations = await fetchRemoteMigrations();
|
|
2882
|
-
const
|
|
2925
|
+
const latestRemoteVersion = getLatestRemoteVersion(migrations);
|
|
2883
2926
|
const localMigrations = parseStrictLocalMigrations(listLocalMigrationFilenames());
|
|
2884
|
-
const
|
|
2927
|
+
const nextVersion = getNextLocalMigrationVersion(
|
|
2885
2928
|
localMigrations,
|
|
2886
|
-
|
|
2929
|
+
latestRemoteVersion
|
|
2887
2930
|
);
|
|
2888
|
-
const filename = buildMigrationFilename(
|
|
2931
|
+
const filename = buildMigrationFilename(nextVersion, migrationName);
|
|
2889
2932
|
const migrationsDir = ensureMigrationsDir();
|
|
2890
2933
|
const filePath = join8(migrationsDir, filename);
|
|
2891
2934
|
if (existsSync4(filePath)) {
|
|
@@ -2893,7 +2936,7 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
2893
2936
|
}
|
|
2894
2937
|
writeFileSync3(filePath, "");
|
|
2895
2938
|
if (json) {
|
|
2896
|
-
outputJson({ filename, path: filePath,
|
|
2939
|
+
outputJson({ filename, path: filePath, version: nextVersion });
|
|
2897
2940
|
} else {
|
|
2898
2941
|
outputSuccess(`Created migration file ${filename}`);
|
|
2899
2942
|
}
|
|
@@ -2903,50 +2946,96 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
2903
2946
|
handleError(err, json);
|
|
2904
2947
|
}
|
|
2905
2948
|
});
|
|
2906
|
-
migrationsCmd.command("up
|
|
2949
|
+
migrationsCmd.command("up [target]").description("Apply one or more local migration files").option("--all", "Apply all pending local migration files").option("--to <version-or-filename>", "Apply pending local migrations up to a version or file").action(async (target, options, cmd) => {
|
|
2907
2950
|
const { json } = getRootOpts(cmd);
|
|
2908
2951
|
try {
|
|
2909
2952
|
await requireAuth();
|
|
2910
2953
|
const migrations = await fetchRemoteMigrations();
|
|
2911
|
-
const
|
|
2954
|
+
const latestRemoteVersion = getLatestRemoteVersion(migrations);
|
|
2912
2955
|
const filenames = listLocalMigrationFilenames();
|
|
2913
|
-
const
|
|
2914
|
-
if (
|
|
2956
|
+
const requestedModes = [Boolean(target), Boolean(options.all), Boolean(options.to)].filter(Boolean);
|
|
2957
|
+
if (requestedModes.length !== 1) {
|
|
2915
2958
|
throw new CLIError(
|
|
2916
|
-
`
|
|
2959
|
+
"Use exactly one apply mode: `up <target>`, `up --to <version-or-filename>`, or `up --all`."
|
|
2917
2960
|
);
|
|
2918
2961
|
}
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2962
|
+
const applySingleTarget = async (targetMigrationFilenameOrVersion) => {
|
|
2963
|
+
const targetMigration = resolveMigrationTarget(targetMigrationFilenameOrVersion, filenames);
|
|
2964
|
+
const validLocalMigrations = filenames.map((filename) => parseMigrationFilename(filename)).filter((migration) => migration !== null).sort((left, right) => compareMigrationVersions(left.version, right.version));
|
|
2965
|
+
if (latestRemoteVersion && compareMigrationVersions(targetMigration.version, latestRemoteVersion) <= 0) {
|
|
2966
|
+
throw new CLIError(
|
|
2967
|
+
`Migration ${targetMigration.filename} is already applied remotely.`
|
|
2968
|
+
);
|
|
2969
|
+
}
|
|
2970
|
+
const earlierPendingMigration = validLocalMigrations.find(
|
|
2971
|
+
(migration) => migration.version !== targetMigration.version && (!latestRemoteVersion || compareMigrationVersions(migration.version, latestRemoteVersion) > 0) && compareMigrationVersions(migration.version, targetMigration.version) < 0
|
|
2922
2972
|
);
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2973
|
+
if (earlierPendingMigration) {
|
|
2974
|
+
throw new CLIError(
|
|
2975
|
+
`Migration ${targetMigration.filename} is not the next pending local migration. Apply ${earlierPendingMigration.filename} first.`
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
const filePath = join8(getMigrationsDir(), targetMigration.filename);
|
|
2979
|
+
if (!existsSync4(filePath)) {
|
|
2980
|
+
throw new CLIError(`Local migration file not found: ${targetMigration.filename}`);
|
|
2981
|
+
}
|
|
2982
|
+
const sql = readFileSync4(filePath, "utf-8");
|
|
2983
|
+
if (!sql.trim()) {
|
|
2984
|
+
throw new CLIError(`Migration file is empty: ${targetMigration.filename}`);
|
|
2985
|
+
}
|
|
2986
|
+
return applyMigration(targetMigration, sql);
|
|
2935
2987
|
};
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
}
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
`Applied migration sequence mismatch. Expected ${targetMigration.sequenceNumber}, received ${createdMigration.sequenceNumber}.`
|
|
2988
|
+
let appliedMigrations = [];
|
|
2989
|
+
if (target) {
|
|
2990
|
+
appliedMigrations = [await applySingleTarget(target)];
|
|
2991
|
+
} else {
|
|
2992
|
+
const localMigrations = parseStrictLocalMigrations(filenames);
|
|
2993
|
+
const pendingMigrations = localMigrations.filter(
|
|
2994
|
+
(migration) => !latestRemoteVersion || compareMigrationVersions(migration.version, latestRemoteVersion) > 0
|
|
2944
2995
|
);
|
|
2996
|
+
if (pendingMigrations.length === 0) {
|
|
2997
|
+
if (json) {
|
|
2998
|
+
outputJson({ appliedMigrations: [] });
|
|
2999
|
+
} else {
|
|
3000
|
+
outputSuccess("No pending local migrations to apply.");
|
|
3001
|
+
}
|
|
3002
|
+
await reportCliUsage("cli.db.migrations.up", true);
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
let migrationsToApply = pendingMigrations;
|
|
3006
|
+
if (options.to) {
|
|
3007
|
+
const targetVersion = /^\d{14}$/u.test(options.to) ? options.to : resolveMigrationTarget(options.to, filenames).version;
|
|
3008
|
+
if (latestRemoteVersion && compareMigrationVersions(targetVersion, latestRemoteVersion) <= 0) {
|
|
3009
|
+
throw new CLIError(`Migration ${options.to} is already applied remotely.`);
|
|
3010
|
+
}
|
|
3011
|
+
migrationsToApply = pendingMigrations.filter(
|
|
3012
|
+
(migration) => compareMigrationVersions(migration.version, targetVersion) <= 0
|
|
3013
|
+
);
|
|
3014
|
+
if (migrationsToApply.length === 0 || migrationsToApply[migrationsToApply.length - 1]?.version !== targetVersion) {
|
|
3015
|
+
throw new CLIError(
|
|
3016
|
+
`Pending local migration not found for target ${options.to}.`
|
|
3017
|
+
);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
for (const migration of migrationsToApply) {
|
|
3021
|
+
const filePath = join8(getMigrationsDir(), migration.filename);
|
|
3022
|
+
if (!existsSync4(filePath)) {
|
|
3023
|
+
throw new CLIError(`Local migration file not found: ${migration.filename}`);
|
|
3024
|
+
}
|
|
3025
|
+
const sql = readFileSync4(filePath, "utf-8");
|
|
3026
|
+
if (!sql.trim()) {
|
|
3027
|
+
throw new CLIError(`Migration file is empty: ${migration.filename}`);
|
|
3028
|
+
}
|
|
3029
|
+
appliedMigrations.push(await applyMigration(migration, sql));
|
|
3030
|
+
}
|
|
2945
3031
|
}
|
|
2946
3032
|
if (json) {
|
|
2947
|
-
outputJson(
|
|
3033
|
+
outputJson({ appliedMigrations });
|
|
2948
3034
|
} else {
|
|
2949
|
-
outputSuccess(`Applied
|
|
3035
|
+
outputSuccess(`Applied ${appliedMigrations.length} migration file(s).`);
|
|
3036
|
+
for (const migration of appliedMigrations) {
|
|
3037
|
+
console.log(`- ${buildMigrationFilename(migration.version, migration.name)}`);
|
|
3038
|
+
}
|
|
2950
3039
|
}
|
|
2951
3040
|
await reportCliUsage("cli.db.migrations.up", true);
|
|
2952
3041
|
} catch (err) {
|
|
@@ -5366,7 +5455,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
5366
5455
|
const s = !json ? clack10.spinner() : null;
|
|
5367
5456
|
s?.start("Collecting diagnostic data...");
|
|
5368
5457
|
const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
|
|
5369
|
-
const cliVersion = "0.1.53-dev.
|
|
5458
|
+
const cliVersion = "0.1.53-dev.1";
|
|
5370
5459
|
s?.stop("Data collected");
|
|
5371
5460
|
if (!json) {
|
|
5372
5461
|
console.log(`
|