@insforge/cli 0.1.53 → 0.1.55

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
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync7 } from "fs";
5
- import { join as join10, dirname } from "path";
4
+ import { readFileSync as readFileSync8 } from "fs";
5
+ import { join as join12, dirname } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { Command } from "commander";
8
8
  import * as clack11 from "@clack/prompts";
@@ -1123,7 +1123,7 @@ async function reportCliUsage(toolName, success, maxRetries = 1, explicitConfig)
1123
1123
 
1124
1124
  // src/lib/analytics.ts
1125
1125
  import { PostHog } from "posthog-node";
1126
- var POSTHOG_API_KEY = "phc_ueV1ii62wdBTkH7E70ugyeqHIHu8dFDdjs0qq3TZhJz";
1126
+ var POSTHOG_API_KEY = "";
1127
1127
  var POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
1128
1128
  var client = null;
1129
1129
  function getClient() {
@@ -1211,6 +1211,9 @@ ${err.nextActions}`;
1211
1211
  if (res.status === 404 && path5.startsWith("/api/compute")) {
1212
1212
  message = "Compute services are not available on this backend.\nSelf-hosted: upgrade your InsForge instance. Cloud: contact your InsForge admin to enable compute.";
1213
1213
  }
1214
+ if (res.status === 404 && path5 === "/api/database/migrations") {
1215
+ message = "Database migrations are not available on this backend.\nSelf-hosted: upgrade your InsForge instance. Cloud: contact your InsForge admin about database migration support.";
1216
+ }
1214
1217
  throw new CLIError(message);
1215
1218
  }
1216
1219
  return res;
@@ -2230,7 +2233,7 @@ function registerProjectLinkCommand(program2) {
2230
2233
  if (err instanceof CLIError && (err.exitCode === 5 || err.exitCode === 4 || err.message.includes("not found"))) {
2231
2234
  const identity = creds.user?.email ?? creds.user?.name ?? "unknown user";
2232
2235
  throw new CLIError(
2233
- `You're logged in as ${identity}, and you don't have access to project ${projectId}. Check that the project ID is correct and belongs to one of your organizations.`,
2236
+ `No access to project ${projectId} as ${identity}. Double-check the project ID, or run \`npx @insforge/cli logout\` to switch accounts.`,
2234
2237
  5,
2235
2238
  "PERMISSION_DENIED"
2236
2239
  );
@@ -2671,6 +2674,451 @@ function registerDbImportCommand(dbCmd2) {
2671
2674
  });
2672
2675
  }
2673
2676
 
2677
+ // src/commands/db/migrations.ts
2678
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2679
+ import { join as join8 } from "path";
2680
+
2681
+ // src/lib/migrations.ts
2682
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync } from "fs";
2683
+ import { join as join7 } from "path";
2684
+ var MIGRATION_VERSION_REGEX = /^\d{14}$/u;
2685
+ var MIGRATION_FILENAME_REGEX = /^(\d{14})_([a-z0-9-]+)\.sql$/u;
2686
+ function assertValidMigrationVersion(version) {
2687
+ if (!MIGRATION_VERSION_REGEX.test(version)) {
2688
+ throw new CLIError(`Invalid migration version: ${version}. Expected YYYYMMDDHHmmss.`);
2689
+ }
2690
+ }
2691
+ function parseMigrationFilename(filename) {
2692
+ const match = MIGRATION_FILENAME_REGEX.exec(filename);
2693
+ if (!match) {
2694
+ return null;
2695
+ }
2696
+ return {
2697
+ filename,
2698
+ version: match[1],
2699
+ name: match[2]
2700
+ };
2701
+ }
2702
+ function compareMigrationVersions(left, right) {
2703
+ return left.localeCompare(right);
2704
+ }
2705
+ function getRemoteMigrationVersionStatus(version, appliedVersions, latestRemoteVersion) {
2706
+ if (appliedVersions.has(version)) {
2707
+ return "already-applied";
2708
+ }
2709
+ if (latestRemoteVersion && compareMigrationVersions(version, latestRemoteVersion) < 0) {
2710
+ return "older-than-head";
2711
+ }
2712
+ return "pending";
2713
+ }
2714
+ function formatMigrationVersion(date) {
2715
+ const year = String(date.getUTCFullYear());
2716
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
2717
+ const day = String(date.getUTCDate()).padStart(2, "0");
2718
+ const hour = String(date.getUTCHours()).padStart(2, "0");
2719
+ const minute = String(date.getUTCMinutes()).padStart(2, "0");
2720
+ const second = String(date.getUTCSeconds()).padStart(2, "0");
2721
+ return `${year}${month}${day}${hour}${minute}${second}`;
2722
+ }
2723
+ function incrementMigrationVersion(version) {
2724
+ const year = Number(version.slice(0, 4));
2725
+ const month = Number(version.slice(4, 6)) - 1;
2726
+ const day = Number(version.slice(6, 8));
2727
+ const hour = Number(version.slice(8, 10));
2728
+ const minute = Number(version.slice(10, 12));
2729
+ const second = Number(version.slice(12, 14));
2730
+ const nextTimestamp = Date.UTC(year, month, day, hour, minute, second + 1);
2731
+ return formatMigrationVersion(new Date(nextTimestamp));
2732
+ }
2733
+ function getMigrationsDir(cwd = process.cwd()) {
2734
+ return join7(cwd, "migrations");
2735
+ }
2736
+ function ensureMigrationsDir(cwd = process.cwd()) {
2737
+ const migrationsDir = getMigrationsDir(cwd);
2738
+ if (!existsSync3(migrationsDir)) {
2739
+ mkdirSync2(migrationsDir, { recursive: true });
2740
+ }
2741
+ return migrationsDir;
2742
+ }
2743
+ function listLocalMigrationFilenames(cwd = process.cwd()) {
2744
+ const migrationsDir = getMigrationsDir(cwd);
2745
+ if (!existsSync3(migrationsDir)) {
2746
+ return [];
2747
+ }
2748
+ return readdirSync(migrationsDir).sort((left, right) => left.localeCompare(right));
2749
+ }
2750
+ function parseStrictLocalMigrations(filenames) {
2751
+ const migrations = filenames.map((filename) => {
2752
+ const parsedMigration = parseMigrationFilename(filename);
2753
+ if (!parsedMigration) {
2754
+ throw new CLIError(
2755
+ `Invalid migration filename: ${filename}. Expected <migration_version>_<migration-name>.sql.`
2756
+ );
2757
+ }
2758
+ return parsedMigration;
2759
+ });
2760
+ assertNoDuplicateMigrationVersions(migrations);
2761
+ return migrations.sort((left, right) => compareMigrationVersions(left.version, right.version));
2762
+ }
2763
+ function assertNoDuplicateMigrationVersions(migrations) {
2764
+ const seen = /* @__PURE__ */ new Set();
2765
+ for (const migration of migrations) {
2766
+ if (seen.has(migration.version)) {
2767
+ throw new CLIError(`Duplicate local migration version found: ${migration.version}`);
2768
+ }
2769
+ seen.add(migration.version);
2770
+ }
2771
+ }
2772
+ function getNextLocalMigrationVersion(migrations, latestRemoteVersion, now = /* @__PURE__ */ new Date()) {
2773
+ const orderedMigrations = [...migrations].sort(
2774
+ (left, right) => compareMigrationVersions(left.version, right.version)
2775
+ );
2776
+ assertNoDuplicateMigrationVersions(orderedMigrations);
2777
+ const latestKnownVersion = orderedMigrations.reduce(
2778
+ (latestVersion, migration) => {
2779
+ if (!latestVersion || compareMigrationVersions(migration.version, latestVersion) > 0) {
2780
+ return migration.version;
2781
+ }
2782
+ return latestVersion;
2783
+ },
2784
+ latestRemoteVersion
2785
+ );
2786
+ const currentVersion = formatMigrationVersion(now);
2787
+ if (!latestKnownVersion || compareMigrationVersions(currentVersion, latestKnownVersion) > 0) {
2788
+ return currentVersion;
2789
+ }
2790
+ return incrementMigrationVersion(latestKnownVersion);
2791
+ }
2792
+ function formatMigrationSql(statements) {
2793
+ const normalizedStatements = statements.map((statement) => statement.trim().replace(/;\s*$/u, "")).filter(Boolean);
2794
+ return normalizedStatements.join(";\n\n").concat(normalizedStatements.length > 0 ? ";\n" : "");
2795
+ }
2796
+ function findOlderThanHeadLocalMigrations(migrations, appliedVersions, latestRemoteVersion) {
2797
+ return migrations.filter(
2798
+ (migration) => getRemoteMigrationVersionStatus(
2799
+ migration.version,
2800
+ appliedVersions,
2801
+ latestRemoteVersion
2802
+ ) === "older-than-head"
2803
+ );
2804
+ }
2805
+ function findLocalMigrationByVersion(version, filenames) {
2806
+ const matches = filenames.map((filename) => parseMigrationFilename(filename)).filter(
2807
+ (migration) => migration !== null && migration.version === version
2808
+ );
2809
+ if (matches.length === 0) {
2810
+ throw new CLIError(`Local migration for version ${version} not found.`);
2811
+ }
2812
+ if (matches.length > 1) {
2813
+ throw new CLIError(
2814
+ `Multiple local migration files found for version ${version}.`
2815
+ );
2816
+ }
2817
+ return matches[0];
2818
+ }
2819
+ function resolveMigrationTarget(target, filenames) {
2820
+ if (/^\d{14}$/u.test(target)) {
2821
+ return findLocalMigrationByVersion(target, filenames);
2822
+ }
2823
+ const parsedTarget = parseMigrationFilename(target);
2824
+ if (!parsedTarget) {
2825
+ throw new CLIError(
2826
+ "Migration file names must match <migration_version>_<migration-name>.sql."
2827
+ );
2828
+ }
2829
+ if (!filenames.includes(target)) {
2830
+ throw new CLIError(`Local migration file not found: ${target}`);
2831
+ }
2832
+ return parsedTarget;
2833
+ }
2834
+
2835
+ // src/commands/db/migrations.ts
2836
+ function getLatestRemoteVersion(migrations) {
2837
+ return migrations.reduce(
2838
+ (latestVersion, migration) => !latestVersion || compareMigrationVersions(migration.version, latestVersion) > 0 ? migration.version : latestVersion,
2839
+ null
2840
+ );
2841
+ }
2842
+ function buildMigrationFilename(version, name) {
2843
+ return `${version}_${name}.sql`;
2844
+ }
2845
+ function buildOlderThanHeadError(migrationLabel, latestRemoteVersion) {
2846
+ return new CLIError(
2847
+ `Migration ${migrationLabel} is older than the current remote head (${latestRemoteVersion}) and is not applied remotely. Rename it with a newer timestamp, or delete it locally if it is stale.`
2848
+ );
2849
+ }
2850
+ function formatCreatedAt(createdAt) {
2851
+ const date = new Date(createdAt);
2852
+ return Number.isNaN(date.getTime()) ? createdAt : date.toLocaleString();
2853
+ }
2854
+ async function fetchRemoteMigrations() {
2855
+ const res = await ossFetch("/api/database/migrations");
2856
+ const raw = await res.json();
2857
+ const migrations = Array.isArray(raw.migrations) ? raw.migrations : [];
2858
+ for (const migration of migrations) {
2859
+ assertValidMigrationVersion(migration.version);
2860
+ }
2861
+ return migrations;
2862
+ }
2863
+ function assertValidMigrationName(name) {
2864
+ if (!/^[a-z0-9-]+$/u.test(name)) {
2865
+ throw new CLIError("Migration name must use lowercase letters, numbers, and hyphens only.");
2866
+ }
2867
+ }
2868
+ async function applyMigration(targetMigration, sql) {
2869
+ const body = {
2870
+ version: targetMigration.version,
2871
+ name: targetMigration.name,
2872
+ sql
2873
+ };
2874
+ const res = await ossFetch("/api/database/migrations", {
2875
+ method: "POST",
2876
+ body: JSON.stringify(body)
2877
+ });
2878
+ const createdMigration = await res.json();
2879
+ if (createdMigration.version !== targetMigration.version) {
2880
+ throw new CLIError(
2881
+ `Applied migration version mismatch. Expected ${targetMigration.version}, received ${createdMigration.version}.`
2882
+ );
2883
+ }
2884
+ return createdMigration;
2885
+ }
2886
+ function registerDbMigrationsCommand(dbCmd2) {
2887
+ const migrationsCmd = dbCmd2.command("migrations").description("Manage database migration files");
2888
+ migrationsCmd.command("list").description("List applied remote database migrations").action(async (_opts, cmd) => {
2889
+ const { json } = getRootOpts(cmd);
2890
+ try {
2891
+ await requireAuth();
2892
+ const migrations = await fetchRemoteMigrations();
2893
+ if (json) {
2894
+ outputJson({ migrations });
2895
+ } else if (migrations.length === 0) {
2896
+ console.log("No database migrations found.");
2897
+ } else {
2898
+ outputTable(
2899
+ ["Version", "Name", "Created At"],
2900
+ migrations.map((migration) => [
2901
+ migration.version,
2902
+ migration.name,
2903
+ formatCreatedAt(migration.createdAt)
2904
+ ])
2905
+ );
2906
+ }
2907
+ await reportCliUsage("cli.db.migrations.list", true);
2908
+ } catch (err) {
2909
+ await reportCliUsage("cli.db.migrations.list", false);
2910
+ handleError(err, json);
2911
+ }
2912
+ });
2913
+ migrationsCmd.command("fetch").description("Fetch applied remote migrations into migrations/").action(async (_opts, cmd) => {
2914
+ const { json } = getRootOpts(cmd);
2915
+ try {
2916
+ await requireAuth();
2917
+ const migrations = await fetchRemoteMigrations();
2918
+ const migrationsDir = ensureMigrationsDir();
2919
+ const createdFiles = [];
2920
+ const skippedFiles = [];
2921
+ for (const migration of [...migrations].sort(
2922
+ (left, right) => compareMigrationVersions(left.version, right.version)
2923
+ )) {
2924
+ assertValidMigrationName(migration.name);
2925
+ const filename = buildMigrationFilename(
2926
+ migration.version,
2927
+ migration.name
2928
+ );
2929
+ const filePath = join8(migrationsDir, filename);
2930
+ if (existsSync4(filePath)) {
2931
+ skippedFiles.push(filename);
2932
+ continue;
2933
+ }
2934
+ writeFileSync3(filePath, formatMigrationSql(migration.statements));
2935
+ createdFiles.push(filename);
2936
+ }
2937
+ if (json) {
2938
+ outputJson({
2939
+ directory: migrationsDir,
2940
+ totalRemoteMigrations: migrations.length,
2941
+ createdFiles,
2942
+ skippedFiles
2943
+ });
2944
+ } else {
2945
+ outputSuccess(
2946
+ `Fetched ${migrations.length} remote migration(s) into ${migrationsDir}.`
2947
+ );
2948
+ console.log(`Created: ${createdFiles.length}`);
2949
+ console.log(`Skipped: ${skippedFiles.length}`);
2950
+ }
2951
+ await reportCliUsage("cli.db.migrations.fetch", true);
2952
+ } catch (err) {
2953
+ await reportCliUsage("cli.db.migrations.fetch", false);
2954
+ handleError(err, json);
2955
+ }
2956
+ });
2957
+ migrationsCmd.command("new <migration-name>").description("Create a new local migration file").action(async (migrationName, _opts, cmd) => {
2958
+ const { json } = getRootOpts(cmd);
2959
+ try {
2960
+ await requireAuth();
2961
+ assertValidMigrationName(migrationName);
2962
+ const migrations = await fetchRemoteMigrations();
2963
+ const latestRemoteVersion = getLatestRemoteVersion(migrations);
2964
+ const localMigrations = parseStrictLocalMigrations(listLocalMigrationFilenames());
2965
+ const nextVersion = getNextLocalMigrationVersion(
2966
+ localMigrations,
2967
+ latestRemoteVersion
2968
+ );
2969
+ const filename = buildMigrationFilename(nextVersion, migrationName);
2970
+ const migrationsDir = ensureMigrationsDir();
2971
+ const filePath = join8(migrationsDir, filename);
2972
+ try {
2973
+ writeFileSync3(filePath, "", { flag: "wx" });
2974
+ } catch (error) {
2975
+ if (error.code === "EEXIST") {
2976
+ throw new CLIError(`Migration file already exists: ${filename}`);
2977
+ }
2978
+ throw error;
2979
+ }
2980
+ if (json) {
2981
+ outputJson({ filename, path: filePath, version: nextVersion });
2982
+ } else {
2983
+ outputSuccess(`Created migration file ${filename}`);
2984
+ }
2985
+ await reportCliUsage("cli.db.migrations.new", true);
2986
+ } catch (err) {
2987
+ await reportCliUsage("cli.db.migrations.new", false);
2988
+ handleError(err, json);
2989
+ }
2990
+ });
2991
+ 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) => {
2992
+ const { json } = getRootOpts(cmd);
2993
+ try {
2994
+ await requireAuth();
2995
+ const migrations = await fetchRemoteMigrations();
2996
+ const latestRemoteVersion = getLatestRemoteVersion(migrations);
2997
+ const appliedRemoteVersions = new Set(
2998
+ migrations.map((migration) => migration.version)
2999
+ );
3000
+ const filenames = listLocalMigrationFilenames();
3001
+ const requestedModes = [Boolean(target), Boolean(options.all), Boolean(options.to)].filter(Boolean);
3002
+ if (requestedModes.length !== 1) {
3003
+ throw new CLIError(
3004
+ "Use exactly one apply mode: `up <target>`, `up --to <version-or-filename>`, or `up --all`."
3005
+ );
3006
+ }
3007
+ const applySingleTarget = async (targetMigrationFilenameOrVersion) => {
3008
+ const targetMigration = resolveMigrationTarget(targetMigrationFilenameOrVersion, filenames);
3009
+ const validLocalMigrations = filenames.map((filename) => parseMigrationFilename(filename)).filter((migration) => migration !== null).sort((left, right) => compareMigrationVersions(left.version, right.version));
3010
+ const targetRemoteStatus = getRemoteMigrationVersionStatus(
3011
+ targetMigration.version,
3012
+ appliedRemoteVersions,
3013
+ latestRemoteVersion
3014
+ );
3015
+ if (targetRemoteStatus === "already-applied") {
3016
+ throw new CLIError(`Migration ${targetMigration.filename} is already applied remotely.`);
3017
+ }
3018
+ if (targetRemoteStatus === "older-than-head" && latestRemoteVersion) {
3019
+ throw buildOlderThanHeadError(targetMigration.filename, latestRemoteVersion);
3020
+ }
3021
+ const earlierPendingMigration = validLocalMigrations.find(
3022
+ (migration) => migration.version !== targetMigration.version && (!latestRemoteVersion || compareMigrationVersions(migration.version, latestRemoteVersion) > 0) && compareMigrationVersions(migration.version, targetMigration.version) < 0
3023
+ );
3024
+ if (earlierPendingMigration) {
3025
+ throw new CLIError(
3026
+ `Migration ${targetMigration.filename} is not the next pending local migration. Apply ${earlierPendingMigration.filename} first, or fix/delete it locally if it is invalid or no longer needed.`
3027
+ );
3028
+ }
3029
+ const filePath = join8(getMigrationsDir(), targetMigration.filename);
3030
+ if (!existsSync4(filePath)) {
3031
+ throw new CLIError(`Local migration file not found: ${targetMigration.filename}`);
3032
+ }
3033
+ const sql = readFileSync4(filePath, "utf-8");
3034
+ if (!sql.trim()) {
3035
+ throw new CLIError(`Migration file is empty: ${targetMigration.filename}`);
3036
+ }
3037
+ return applyMigration(targetMigration, sql);
3038
+ };
3039
+ let appliedMigrations = [];
3040
+ if (target) {
3041
+ appliedMigrations = [await applySingleTarget(target)];
3042
+ } else {
3043
+ const localMigrations = parseStrictLocalMigrations(filenames);
3044
+ const olderThanHeadMigrations = findOlderThanHeadLocalMigrations(
3045
+ localMigrations,
3046
+ appliedRemoteVersions,
3047
+ latestRemoteVersion
3048
+ );
3049
+ const pendingMigrations = localMigrations.filter(
3050
+ (migration) => getRemoteMigrationVersionStatus(
3051
+ migration.version,
3052
+ appliedRemoteVersions,
3053
+ latestRemoteVersion
3054
+ ) === "pending"
3055
+ );
3056
+ if (olderThanHeadMigrations.length > 0 && latestRemoteVersion) {
3057
+ throw buildOlderThanHeadError(
3058
+ olderThanHeadMigrations[0].filename,
3059
+ latestRemoteVersion
3060
+ );
3061
+ }
3062
+ if (pendingMigrations.length === 0) {
3063
+ if (json) {
3064
+ outputJson({ appliedMigrations: [] });
3065
+ } else {
3066
+ outputSuccess("No pending local migrations to apply.");
3067
+ }
3068
+ await reportCliUsage("cli.db.migrations.up", true);
3069
+ return;
3070
+ }
3071
+ let migrationsToApply = pendingMigrations;
3072
+ if (options.to) {
3073
+ const targetVersion = /^\d{14}$/u.test(options.to) ? options.to : resolveMigrationTarget(options.to, filenames).version;
3074
+ const targetRemoteStatus = getRemoteMigrationVersionStatus(
3075
+ targetVersion,
3076
+ appliedRemoteVersions,
3077
+ latestRemoteVersion
3078
+ );
3079
+ if (targetRemoteStatus === "already-applied") {
3080
+ throw new CLIError(`Migration ${options.to} is already applied remotely.`);
3081
+ }
3082
+ if (targetRemoteStatus === "older-than-head" && latestRemoteVersion) {
3083
+ throw buildOlderThanHeadError(options.to, latestRemoteVersion);
3084
+ }
3085
+ migrationsToApply = pendingMigrations.filter(
3086
+ (migration) => compareMigrationVersions(migration.version, targetVersion) <= 0
3087
+ );
3088
+ if (migrationsToApply.length === 0 || migrationsToApply[migrationsToApply.length - 1]?.version !== targetVersion) {
3089
+ throw new CLIError(
3090
+ `Pending local migration not found for target ${options.to}.`
3091
+ );
3092
+ }
3093
+ }
3094
+ for (const migration of migrationsToApply) {
3095
+ const filePath = join8(getMigrationsDir(), migration.filename);
3096
+ if (!existsSync4(filePath)) {
3097
+ throw new CLIError(`Local migration file not found: ${migration.filename}`);
3098
+ }
3099
+ const sql = readFileSync4(filePath, "utf-8");
3100
+ if (!sql.trim()) {
3101
+ throw new CLIError(`Migration file is empty: ${migration.filename}`);
3102
+ }
3103
+ appliedMigrations.push(await applyMigration(migration, sql));
3104
+ }
3105
+ }
3106
+ if (json) {
3107
+ outputJson({ appliedMigrations });
3108
+ } else {
3109
+ outputSuccess(`Applied ${appliedMigrations.length} migration file(s).`);
3110
+ for (const migration of appliedMigrations) {
3111
+ console.log(`- ${buildMigrationFilename(migration.version, migration.name)}`);
3112
+ }
3113
+ }
3114
+ await reportCliUsage("cli.db.migrations.up", true);
3115
+ } catch (err) {
3116
+ await reportCliUsage("cli.db.migrations.up", false);
3117
+ handleError(err, json);
3118
+ }
3119
+ });
3120
+ }
3121
+
2674
3122
  // src/commands/records/list.ts
2675
3123
  function registerRecordsCommands(recordsCmd2) {
2676
3124
  recordsCmd2.command("list <table>").description("List records from a table").option("--select <columns>", "Columns to select (comma-separated)").option("--filter <filter>", 'Filter expression (e.g. "name=eq.John")').option("--order <order>", 'Order by (e.g. "created_at.desc")').option("--limit <n>", "Limit number of records", parseInt).option("--offset <n>", "Offset for pagination", parseInt).action(async (table, opts, cmd) => {
@@ -2854,21 +3302,21 @@ function registerFunctionsCommands(functionsCmd2) {
2854
3302
  }
2855
3303
 
2856
3304
  // src/commands/functions/deploy.ts
2857
- import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
2858
- import { join as join7 } from "path";
3305
+ import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
3306
+ import { join as join9 } from "path";
2859
3307
  function registerFunctionsDeployCommand(functionsCmd2) {
2860
3308
  functionsCmd2.command("deploy <slug>").description("Deploy an edge function (create or update)").option("--file <path>", "Path to the function source file").option("--name <name>", "Function display name").option("--description <desc>", "Function description").action(async (slug, opts, cmd) => {
2861
3309
  const { json } = getRootOpts(cmd);
2862
3310
  try {
2863
3311
  await requireAuth();
2864
- const filePath = opts.file ?? join7(process.cwd(), "insforge", "functions", slug, "index.ts");
2865
- if (!existsSync3(filePath)) {
3312
+ const filePath = opts.file ?? join9(process.cwd(), "insforge", "functions", slug, "index.ts");
3313
+ if (!existsSync5(filePath)) {
2866
3314
  throw new CLIError(
2867
3315
  `Source file not found: ${filePath}
2868
- Specify --file <path> or create ${join7("insforge", "functions", slug, "index.ts")}`
3316
+ Specify --file <path> or create ${join9("insforge", "functions", slug, "index.ts")}`
2869
3317
  );
2870
3318
  }
2871
- const code = readFileSync4(filePath, "utf-8");
3319
+ const code = readFileSync5(filePath, "utf-8");
2872
3320
  const name = opts.name ?? slug;
2873
3321
  const description = opts.description ?? "";
2874
3322
  let exists = false;
@@ -3055,7 +3503,7 @@ function registerStorageBucketsCommand(storageCmd2) {
3055
3503
  }
3056
3504
 
3057
3505
  // src/commands/storage/upload.ts
3058
- import { readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
3506
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
3059
3507
  import { basename as basename5 } from "path";
3060
3508
  function registerStorageUploadCommand(storageCmd2) {
3061
3509
  storageCmd2.command("upload <file>").description("Upload a file to a storage bucket").requiredOption("--bucket <name>", "Target bucket name").option("--key <objectKey>", "Object key (defaults to filename)").action(async (file, opts, cmd) => {
@@ -3064,10 +3512,10 @@ function registerStorageUploadCommand(storageCmd2) {
3064
3512
  await requireAuth();
3065
3513
  const config = getProjectConfig();
3066
3514
  if (!config) throw new ProjectNotLinkedError();
3067
- if (!existsSync4(file)) {
3515
+ if (!existsSync6(file)) {
3068
3516
  throw new CLIError(`File not found: ${file}`);
3069
3517
  }
3070
- const fileContent = readFileSync5(file);
3518
+ const fileContent = readFileSync6(file);
3071
3519
  const objectKey = opts.key ?? basename5(file);
3072
3520
  const bucketName = opts.bucket;
3073
3521
  const formData = new FormData();
@@ -3098,8 +3546,8 @@ function registerStorageUploadCommand(storageCmd2) {
3098
3546
  }
3099
3547
 
3100
3548
  // src/commands/storage/download.ts
3101
- import { writeFileSync as writeFileSync3 } from "fs";
3102
- import { join as join8, basename as basename6 } from "path";
3549
+ import { writeFileSync as writeFileSync4 } from "fs";
3550
+ import { join as join10, basename as basename6 } from "path";
3103
3551
  function registerStorageDownloadCommand(storageCmd2) {
3104
3552
  storageCmd2.command("download <objectKey>").description("Download a file from a storage bucket").requiredOption("--bucket <name>", "Source bucket name").option("--output <path>", "Output file path (defaults to current directory)").action(async (objectKey, opts, cmd) => {
3105
3553
  const { json } = getRootOpts(cmd);
@@ -3119,8 +3567,8 @@ function registerStorageDownloadCommand(storageCmd2) {
3119
3567
  throw new CLIError(err.error ?? `Download failed: ${res.status}`);
3120
3568
  }
3121
3569
  const buffer = Buffer.from(await res.arrayBuffer());
3122
- const outputPath = opts.output ?? join8(process.cwd(), basename6(objectKey));
3123
- writeFileSync3(outputPath, buffer);
3570
+ const outputPath = opts.output ?? join10(process.cwd(), basename6(objectKey));
3571
+ writeFileSync4(outputPath, buffer);
3124
3572
  if (json) {
3125
3573
  outputJson({ success: true, path: outputPath, size: buffer.length });
3126
3574
  } else {
@@ -4226,13 +4674,13 @@ function registerComputeLogsCommand(computeCmd2) {
4226
4674
  }
4227
4675
 
4228
4676
  // src/commands/compute/deploy.ts
4229
- import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, renameSync } from "fs";
4230
- import { join as join9 } from "path";
4677
+ import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5, unlinkSync as unlinkSync2, renameSync } from "fs";
4678
+ import { join as join11 } from "path";
4231
4679
  import { execSync, spawn } from "child_process";
4232
4680
  function parseFlyToml(dir) {
4233
- const tomlPath = join9(dir, "fly.toml");
4234
- if (!existsSync5(tomlPath)) return {};
4235
- const content = readFileSync6(tomlPath, "utf-8");
4681
+ const tomlPath = join11(dir, "fly.toml");
4682
+ if (!existsSync7(tomlPath)) return {};
4683
+ const content = readFileSync7(tomlPath, "utf-8");
4236
4684
  const config = {};
4237
4685
  const portMatch = content.match(/internal_port\s*=\s*(\d+)/);
4238
4686
  if (portMatch) config.internalPort = Number(portMatch[1]);
@@ -4302,8 +4750,8 @@ function registerComputeDeployCommand(computeCmd2) {
4302
4750
  checkFlyctl();
4303
4751
  const flyToken = getFlyToken();
4304
4752
  const dir = directory ?? process.cwd();
4305
- const dockerfilePath = join9(dir, "Dockerfile");
4306
- if (!existsSync5(dockerfilePath)) {
4753
+ const dockerfilePath = join11(dir, "Dockerfile");
4754
+ if (!existsSync7(dockerfilePath)) {
4307
4755
  throw new CLIError(`No Dockerfile found in ${dir}`);
4308
4756
  }
4309
4757
  const flyTomlDefaults = parseFlyToml(dir);
@@ -4348,15 +4796,15 @@ function registerComputeDeployCommand(computeCmd2) {
4348
4796
  serviceId = service.id;
4349
4797
  flyAppId = service.flyAppId;
4350
4798
  }
4351
- const existingTomlPath = join9(dir, "fly.toml");
4352
- const backupTomlPath = join9(dir, "fly.toml.insforge-backup");
4799
+ const existingTomlPath = join11(dir, "fly.toml");
4800
+ const backupTomlPath = join11(dir, "fly.toml.insforge-backup");
4353
4801
  let hadExistingToml = false;
4354
- if (existsSync5(existingTomlPath)) {
4802
+ if (existsSync7(existingTomlPath)) {
4355
4803
  hadExistingToml = true;
4356
4804
  renameSync(existingTomlPath, backupTomlPath);
4357
4805
  }
4358
4806
  const tomlContent = generateFlyToml(flyAppId, { port, memory, cpu, region });
4359
- writeFileSync4(existingTomlPath, tomlContent);
4807
+ writeFileSync5(existingTomlPath, tomlContent);
4360
4808
  try {
4361
4809
  if (!json) outputInfo("Building and deploying (this may take a few minutes)...");
4362
4810
  await new Promise((resolve4, reject) => {
@@ -5081,7 +5529,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
5081
5529
  const s = !json ? clack10.spinner() : null;
5082
5530
  s?.start("Collecting diagnostic data...");
5083
5531
  const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
5084
- const cliVersion = "0.1.53";
5532
+ const cliVersion = "0.1.55";
5085
5533
  s?.stop("Data collected");
5086
5534
  if (!json) {
5087
5535
  console.log(`
@@ -5310,7 +5758,7 @@ function formatBytesCompact(bytes) {
5310
5758
 
5311
5759
  // src/index.ts
5312
5760
  var __dirname = dirname(fileURLToPath(import.meta.url));
5313
- var pkg = JSON.parse(readFileSync7(join10(__dirname, "../package.json"), "utf-8"));
5761
+ var pkg = JSON.parse(readFileSync8(join12(__dirname, "../package.json"), "utf-8"));
5314
5762
  var INSFORGE_LOGO = `
5315
5763
  \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
5316
5764
  \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
@@ -5344,6 +5792,7 @@ registerDbTriggersCommand(dbCmd);
5344
5792
  registerDbRpcCommand(dbCmd);
5345
5793
  registerDbExportCommand(dbCmd);
5346
5794
  registerDbImportCommand(dbCmd);
5795
+ registerDbMigrationsCommand(dbCmd);
5347
5796
  var recordsCmd = program.command("records", { hidden: true }).description("CRUD operations on table records");
5348
5797
  registerRecordsCommands(recordsCmd);
5349
5798
  registerRecordsCreateCommand(recordsCmd);