@supacloud/cli 0.21.1 → 0.21.3

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.
Files changed (2) hide show
  1. package/dist/index.js +83 -83
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7046,82 +7046,92 @@ function sortMigrationFiles(migrations) {
7046
7046
  }
7047
7047
  return sorted;
7048
7048
  }
7049
- function appliedMigrationKeys(data) {
7050
- const rows = Array.isArray(data) ? data : data?.rows || [];
7051
- const keys = new Set;
7052
- for (const row of rows) {
7053
- if (!row || typeof row !== "object")
7054
- continue;
7055
- const migration = row;
7056
- if (migration.version != null)
7057
- keys.add(String(migration.version));
7058
- if (migration.name != null)
7059
- keys.add(String(migration.name));
7060
- }
7061
- return keys;
7062
- }
7063
7049
  function migrationRows(data) {
7064
- const rows = Array.isArray(data) ? data : data?.rows || [];
7065
- return Array.isArray(rows) ? rows.filter((row) => Boolean(row && typeof row === "object")) : [];
7050
+ if (Array.isArray(data))
7051
+ return data;
7052
+ if (data && typeof data === "object" && Array.isArray(data.rows)) {
7053
+ return data.rows;
7054
+ }
7055
+ throw new Error("Invalid remote migration inventory");
7066
7056
  }
7067
- function migrationIdentityKey(version, name) {
7068
- return `${version}\x00${name}`;
7057
+ function migrationIdentity(row) {
7058
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
7059
+ throw new Error("Invalid remote migration identity");
7060
+ }
7061
+ const migration = row;
7062
+ if (!isMigrationInventoryVersion(migration.version) || !isMigrationInventoryName(migration.name)) {
7063
+ throw new Error("Invalid remote migration identity");
7064
+ }
7065
+ return { version: migration.version, name: migration.name, statements: migration.statements };
7069
7066
  }
7070
7067
  function migrationIdentities(data) {
7071
- return migrationRows(data).flatMap((row) => row.version == null || row.name == null ? [] : [{ version: String(row.version), name: String(row.name) }]);
7072
- }
7073
- function identityConflicts(local, remote) {
7074
- const reusesVersionOrName = local.version === remote.version || local.name === remote.name;
7075
- const exactlyMatches = local.version === remote.version && local.name === remote.name;
7076
- return reusesVersionOrName && !exactlyMatches;
7077
- }
7078
- function migrationIdentityConflicts(data, migrationFiles) {
7079
- const remoteMigrations = migrationIdentities(data);
7080
- return migrationFiles.flatMap((localMigration) => remoteMigrations.filter((remoteMigration) => identityConflicts(localMigration, remoteMigration)).map((remoteMigration) => ({ file: localMigration.file, local: localMigration, remote: remoteMigration })));
7081
- }
7082
- function assertNoMigrationIdentityConflicts(data, migrationFiles) {
7083
- const conflicts = migrationIdentityConflicts(data, migrationFiles);
7084
- if (!conflicts.length)
7085
- return;
7086
- throw new Error([
7087
- "Migration identity conflicts:",
7088
- ...conflicts.map(({ file, local, remote }) => `- ${file} (${local.version}) conflicts with remote ${remote.name} (${remote.version})`)
7089
- ].join(`
7090
- `));
7068
+ const identities = migrationRows(data).map(migrationIdentity);
7069
+ const versions = new Set;
7070
+ const names = new Set;
7071
+ for (const identity of identities) {
7072
+ if (versions.has(identity.version) || identity.name !== null && names.has(identity.name)) {
7073
+ throw new Error("Invalid remote migration inventory");
7074
+ }
7075
+ versions.add(identity.version);
7076
+ if (identity.name !== null)
7077
+ names.add(identity.name);
7078
+ }
7079
+ return identities;
7091
7080
  }
7092
- function nameBoundMigrationMarkerKeys(data) {
7081
+ function appliedMigrationKeys(data) {
7093
7082
  const keys = new Set;
7094
- for (const row of migrationRows(data)) {
7095
- if (row.version == null || row.name == null || !Array.isArray(row.statements))
7096
- continue;
7097
- if (row.statements.length !== 1)
7098
- continue;
7099
- const name = String(row.name);
7100
- const marker = row.statements[0];
7101
- if (marker !== `baseline:${name}` && marker !== `direct-apply:${name}`)
7102
- continue;
7103
- keys.add(migrationIdentityKey(String(row.version), String(row.name)));
7083
+ for (const migration of migrationIdentities(data)) {
7084
+ keys.add(migration.version);
7085
+ if (migration.name !== null)
7086
+ keys.add(migration.name);
7104
7087
  }
7105
7088
  return keys;
7106
7089
  }
7107
- function historicalChecksumMarkerKeys(data, migrationFiles) {
7108
- const filesByKey = new Map(migrationFiles.map((migration) => [migrationIdentityKey(migration.version, migration.name), migration]));
7109
- const keys = new Set;
7110
- for (const row of migrationRows(data)) {
7111
- if (row.version == null || row.name == null || !Array.isArray(row.statements) || row.statements.length !== 1)
7112
- continue;
7113
- const marker = row.statements[0];
7114
- if (typeof marker !== "string" || !/^sha256:[0-9a-f]{64}$/.test(marker))
7115
- continue;
7116
- const key = migrationIdentityKey(String(row.version), String(row.name));
7117
- const migration = filesByKey.get(key);
7118
- if (!migration)
7119
- continue;
7120
- const checksum = createHash("sha256").update(migration.rawBytes).digest("hex");
7121
- if (marker === `sha256:${checksum}`)
7122
- keys.add(key);
7090
+ function singleRemoteStatement(remote) {
7091
+ return Array.isArray(remote.statements) && remote.statements.length === 1 && typeof remote.statements[0] === "string" ? remote.statements[0] : null;
7092
+ }
7093
+ function exactIdentityIsApplied(local, remote) {
7094
+ const statement = singleRemoteStatement(remote);
7095
+ if (statement === null)
7096
+ return false;
7097
+ if (statement === `baseline:${local.name}` || statement === `direct-apply:${local.name}`)
7098
+ return true;
7099
+ const rawChecksum = createHash("sha256").update(local.rawBytes).digest("hex");
7100
+ return statement === `sha256:${rawChecksum}` || statement.trim() === local.sql.trim();
7101
+ }
7102
+ function legacyIdentityIsApplied(local, remote) {
7103
+ const statement = singleRemoteStatement(remote);
7104
+ return remote.name === local.name && remote.version !== local.version && statement !== null && statement.trim() === local.sql.trim();
7105
+ }
7106
+ function migrationIdentityConflict(local) {
7107
+ return new Error(`Migration identity conflicts:
7108
+ - ${local.file} (${local.version}) conflicts with remote inventory`);
7109
+ }
7110
+ function migrationDisposition(local, remoteMigrations) {
7111
+ const sameVersion = remoteMigrations.filter((remote2) => remote2.version === local.version);
7112
+ const sameName = remoteMigrations.filter((remote2) => remote2.name === local.name);
7113
+ if (sameVersion.length > 1 || sameName.length > 1)
7114
+ throw migrationIdentityConflict(local);
7115
+ if (!sameVersion.length && !sameName.length)
7116
+ return "pending";
7117
+ if (sameVersion[0] && sameName[0] && sameVersion[0] !== sameName[0])
7118
+ throw migrationIdentityConflict(local);
7119
+ const remote = sameVersion[0] ?? sameName[0];
7120
+ const exactIdentity = remote.version === local.version && remote.name === local.name;
7121
+ if (exactIdentity && exactIdentityIsApplied(local, remote))
7122
+ return "applied";
7123
+ if (!sameVersion.length && legacyIdentityIsApplied(local, remote))
7124
+ return "applied";
7125
+ throw migrationIdentityConflict(local);
7126
+ }
7127
+ function migrationPushPlan(data, migrationFiles) {
7128
+ const remoteMigrations = migrationIdentities(data);
7129
+ const plan = { alreadyApplied: [], pending: [] };
7130
+ for (const migration of migrationFiles) {
7131
+ const disposition = migrationDisposition(migration, remoteMigrations);
7132
+ plan[disposition === "applied" ? "alreadyApplied" : "pending"].push(migration);
7123
7133
  }
7124
- return keys;
7134
+ return plan;
7125
7135
  }
7126
7136
  function isAlreadyAppliedMigrationResponse(response) {
7127
7137
  if (response.status !== 409 || !response.data || typeof response.data !== "object")
@@ -7349,14 +7359,13 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
7349
7359
  const migrationFiles = sortMigrationFiles(files.map((file) => readMigrationFile(dir, file)));
7350
7360
  const migrationsResult = await http.get(`/v1/projects/${ref}/database/migrations`);
7351
7361
  if (!migrationsResult.ok) {
7352
- text = `❌ Failed to load applied migrations (${migrationsResult.status}): ${JSON.stringify(migrationsResult.data)}`;
7362
+ text = `❌ Failed to load applied migrations (${migrationsResult.status})`;
7353
7363
  break;
7354
7364
  }
7355
- assertNoMigrationIdentityConflicts(migrationsResult.data, migrationFiles);
7365
+ const migrationPlan = migrationPushPlan(migrationsResult.data, migrationFiles);
7356
7366
  if (args.dry_run) {
7357
- const appliedKeys = appliedMigrationKeys(migrationsResult.data);
7358
- const pending = migrationFiles.filter(({ name, version }) => !appliedKeys.has(name) && !appliedKeys.has(String(version)));
7359
- const alreadyApplied = migrationFiles.filter(({ name, version }) => appliedKeys.has(name) || appliedKeys.has(String(version)));
7367
+ const pending = migrationPlan.pending;
7368
+ const alreadyApplied = migrationPlan.alreadyApplied;
7360
7369
  const pendingWithSql = pending;
7361
7370
  let vectorEnabled = null;
7362
7371
  if (pendingWithSql.some(({ sql }) => sqlReferencesVector(sql) || sqlCreatesVectorExtension(sql))) {
@@ -7379,15 +7388,8 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
7379
7388
  break;
7380
7389
  }
7381
7390
  const applied = [];
7382
- const skipped = [];
7383
- const nameBoundMarkerKeys = nameBoundMigrationMarkerKeys(migrationsResult.data);
7384
- const historicalChecksumKeys = historicalChecksumMarkerKeys(migrationsResult.data, migrationFiles);
7385
- for (const { file, name, version, sql } of migrationFiles) {
7386
- const migrationKey = migrationIdentityKey(version, name);
7387
- if (nameBoundMarkerKeys.has(migrationKey) || historicalChecksumKeys.has(migrationKey)) {
7388
- skipped.push(file);
7389
- continue;
7390
- }
7391
+ const skipped = migrationPlan.alreadyApplied.map(({ file }) => file);
7392
+ for (const { file, name, version, sql } of migrationPlan.pending) {
7391
7393
  const r = await http.post(`/v1/projects/${ref}/database/migrations`, { name, sql, version });
7392
7394
  if (r.ok) {
7393
7395
  applied.push(file);
@@ -7396,8 +7398,6 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
7396
7398
  } else {
7397
7399
  text = [
7398
7400
  `❌ Failed to apply ${file} (${r.status})`,
7399
- JSON.stringify(r.data, null, 2),
7400
- "",
7401
7401
  `Applied before failure: ${applied.length}`,
7402
7402
  `Skipped before failure: ${skipped.length}`
7403
7403
  ].join(`
@@ -12039,7 +12039,7 @@ var MUTATION_TOOL_SCHEMA = {
12039
12039
  // package.json
12040
12040
  var package_default = {
12041
12041
  name: "@supacloud/cli",
12042
- version: "0.21.1",
12042
+ version: "0.21.3",
12043
12043
  description: "Project-scoped CLI for SupaCloud users",
12044
12044
  type: "module",
12045
12045
  main: "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.21.1",
3
+ "version": "0.21.3",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",