@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 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 = /^([1-9][0-9]*)_([a-z0-9-]+)\.sql$/;
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
- sequenceNumber: Number(match[1]),
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 <sequence_number>_<migration-name>.sql.`
2737
+ `Invalid migration filename: ${filename}. Expected <migration_version>_<migration-name>.sql.`
2716
2738
  );
2717
2739
  }
2718
2740
  return parsedMigration;
2719
2741
  });
2720
- assertNoDuplicateMigrationSequences(migrations);
2721
- return migrations.sort((left, right) => left.sequenceNumber - right.sequenceNumber);
2742
+ assertNoDuplicateMigrationVersions(migrations);
2743
+ return migrations.sort((left, right) => compareMigrationVersions(left.version, right.version));
2722
2744
  }
2723
- function assertNoDuplicateMigrationSequences(migrations) {
2745
+ function assertNoDuplicateMigrationVersions(migrations) {
2724
2746
  const seen = /* @__PURE__ */ new Set();
2725
2747
  for (const migration of migrations) {
2726
- if (seen.has(migration.sequenceNumber)) {
2727
- throw new CLIError(`Duplicate local migration sequence found: ${migration.sequenceNumber}`);
2748
+ if (seen.has(migration.version)) {
2749
+ throw new CLIError(`Duplicate local migration version found: ${migration.version}`);
2728
2750
  }
2729
- seen.add(migration.sequenceNumber);
2751
+ seen.add(migration.version);
2730
2752
  }
2731
2753
  }
2732
- function getNextLocalMigrationSequence(migrations, latestRemoteSequenceNumber) {
2733
- const orderedMigrations = [...migrations].sort((left, right) => left.sequenceNumber - right.sequenceNumber);
2734
- assertNoDuplicateMigrationSequences(orderedMigrations);
2735
- let expectedSequenceNumber = latestRemoteSequenceNumber + 1;
2736
- for (const migration of orderedMigrations) {
2737
- if (migration.sequenceNumber <= latestRemoteSequenceNumber) {
2738
- continue;
2739
- }
2740
- if (migration.sequenceNumber !== expectedSequenceNumber) {
2741
- throw new CLIError(
2742
- `Local pending migrations must be contiguous after remote sequence ${latestRemoteSequenceNumber}.`
2743
- );
2744
- }
2745
- expectedSequenceNumber += 1;
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 expectedSequenceNumber;
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 findLocalMigrationBySequence(sequenceNumber, filenames) {
2777
+ function findLocalMigrationByVersion(version, filenames) {
2753
2778
  const matches = filenames.map((filename) => parseMigrationFilename(filename)).filter(
2754
- (migration) => migration !== null && migration.sequenceNumber === sequenceNumber
2779
+ (migration) => migration !== null && migration.version === version
2755
2780
  );
2756
2781
  if (matches.length === 0) {
2757
- throw new CLIError(`Local migration for sequence ${sequenceNumber} not found.`);
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 sequence ${sequenceNumber}.`
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 (/^[1-9][0-9]*$/u.test(target)) {
2768
- return findLocalMigrationBySequence(Number(target), filenames);
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 <sequence_number>_<migration-name>.sql."
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 findLocalMigrationBySequence(parsedTarget.sequenceNumber, filenames);
2804
+ return findLocalMigrationByVersion(parsedTarget.version, filenames);
2780
2805
  }
2781
2806
 
2782
2807
  // src/commands/db/migrations.ts
2783
- function getLatestRemoteSequenceNumber(migrations) {
2808
+ function getLatestRemoteVersion(migrations) {
2784
2809
  return migrations.reduce(
2785
- (latestSequenceNumber, migration) => Math.max(latestSequenceNumber, migration.sequenceNumber),
2786
- 0
2810
+ (latestVersion, migration) => !latestVersion || compareMigrationVersions(migration.version, latestVersion) > 0 ? migration.version : latestVersion,
2811
+ null
2787
2812
  );
2788
2813
  }
2789
- function buildMigrationFilename(sequenceNumber, name) {
2790
- return `${sequenceNumber}_${name}.sql`;
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
- ["Sequence", "Name", "Created At"],
2862
+ ["Version", "Name", "Created At"],
2820
2863
  migrations.map((migration) => [
2821
- String(migration.sequenceNumber),
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.sequenceNumber - right.sequenceNumber
2885
+ (left, right) => compareMigrationVersions(left.version, right.version)
2843
2886
  )) {
2844
2887
  const filename = buildMigrationFilename(
2845
- migration.sequenceNumber,
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 latestRemoteSequenceNumber = getLatestRemoteSequenceNumber(migrations);
2925
+ const latestRemoteVersion = getLatestRemoteVersion(migrations);
2883
2926
  const localMigrations = parseStrictLocalMigrations(listLocalMigrationFilenames());
2884
- const nextSequenceNumber = getNextLocalMigrationSequence(
2927
+ const nextVersion = getNextLocalMigrationVersion(
2885
2928
  localMigrations,
2886
- latestRemoteSequenceNumber
2929
+ latestRemoteVersion
2887
2930
  );
2888
- const filename = buildMigrationFilename(nextSequenceNumber, migrationName);
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, sequenceNumber: nextSequenceNumber });
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 <target>").description("Apply exactly one local migration file").action(async (target, _opts, cmd) => {
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 latestRemoteSequenceNumber = getLatestRemoteSequenceNumber(migrations);
2954
+ const latestRemoteVersion = getLatestRemoteVersion(migrations);
2912
2955
  const filenames = listLocalMigrationFilenames();
2913
- const targetMigration = resolveMigrationTarget(target, filenames);
2914
- if (targetMigration.sequenceNumber <= latestRemoteSequenceNumber) {
2956
+ const requestedModes = [Boolean(target), Boolean(options.all), Boolean(options.to)].filter(Boolean);
2957
+ if (requestedModes.length !== 1) {
2915
2958
  throw new CLIError(
2916
- `Migration ${targetMigration.filename} is already applied remotely.`
2959
+ "Use exactly one apply mode: `up <target>`, `up --to <version-or-filename>`, or `up --all`."
2917
2960
  );
2918
2961
  }
2919
- if (targetMigration.sequenceNumber !== latestRemoteSequenceNumber + 1) {
2920
- throw new CLIError(
2921
- `Migration ${targetMigration.filename} is not the next remote sequence. Expected ${latestRemoteSequenceNumber + 1}.`
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
- const filePath = join8(getMigrationsDir(), targetMigration.filename);
2925
- if (!existsSync4(filePath)) {
2926
- throw new CLIError(`Local migration file not found: ${targetMigration.filename}`);
2927
- }
2928
- const sql = readFileSync4(filePath, "utf-8");
2929
- if (!sql.trim()) {
2930
- throw new CLIError(`Migration file is empty: ${targetMigration.filename}`);
2931
- }
2932
- const body = {
2933
- name: targetMigration.name,
2934
- sql
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
- const res = await ossFetch("/api/database/migrations", {
2937
- method: "POST",
2938
- body: JSON.stringify(body)
2939
- });
2940
- const createdMigration = await res.json();
2941
- if (createdMigration.sequenceNumber !== targetMigration.sequenceNumber) {
2942
- throw new CLIError(
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(createdMigration);
3033
+ outputJson({ appliedMigrations });
2948
3034
  } else {
2949
- outputSuccess(`Applied migration ${targetMigration.filename}`);
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.0";
5458
+ const cliVersion = "0.1.53-dev.1";
5370
5459
  s?.stop("Data collected");
5371
5460
  if (!json) {
5372
5461
  console.log(`