@supacloud/cli 0.21.2 → 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 -91
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7046,89 +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), statements: row.statements }]);
7072
- }
7073
- function isLegacyNameBoundMigration(local, remote, remoteMigrations) {
7074
- return remoteMigrations.filter((candidate) => candidate.name === local.name).length === 1 && local.name === remote.name && local.version !== remote.version && Array.isArray(remote.statements) && remote.statements.length === 1 && typeof remote.statements[0] === "string" && remote.statements[0].trim() === local.sql.trim();
7075
- }
7076
- function identityConflicts(local, remote, remoteMigrations) {
7077
- const reusesVersionOrName = local.version === remote.version || local.name === remote.name;
7078
- const exactlyMatches = local.version === remote.version && local.name === remote.name;
7079
- return reusesVersionOrName && !exactlyMatches && !isLegacyNameBoundMigration(local, remote, remoteMigrations);
7080
- }
7081
- function migrationIdentityConflicts(data, migrationFiles) {
7082
- const remoteMigrations = migrationIdentities(data);
7083
- return migrationFiles.flatMap((localMigration) => remoteMigrations.filter((remoteMigration) => identityConflicts(localMigration, remoteMigration, remoteMigrations)).map((remoteMigration) => ({ file: localMigration.file, local: localMigration, remote: remoteMigration })));
7084
- }
7085
- function legacyNameBoundMigrationKeys(data, migrationFiles) {
7086
- const remoteMigrations = migrationIdentities(data);
7087
- return new Set(migrationFiles.flatMap((localMigration) => remoteMigrations.some((remoteMigration) => isLegacyNameBoundMigration(localMigration, remoteMigration, remoteMigrations)) ? [migrationIdentityKey(localMigration.version, localMigration.name)] : []));
7088
- }
7089
- function assertNoMigrationIdentityConflicts(data, migrationFiles) {
7090
- const conflicts = migrationIdentityConflicts(data, migrationFiles);
7091
- if (!conflicts.length)
7092
- return;
7093
- throw new Error([
7094
- "Migration identity conflicts:",
7095
- ...conflicts.map(({ file, local, remote }) => `- ${file} (${local.version}) conflicts with remote ${remote.name} (${remote.version})`)
7096
- ].join(`
7097
- `));
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;
7098
7080
  }
7099
- function nameBoundMigrationMarkerKeys(data) {
7081
+ function appliedMigrationKeys(data) {
7100
7082
  const keys = new Set;
7101
- for (const row of migrationRows(data)) {
7102
- if (row.version == null || row.name == null || !Array.isArray(row.statements))
7103
- continue;
7104
- if (row.statements.length !== 1)
7105
- continue;
7106
- const name = String(row.name);
7107
- const marker = row.statements[0];
7108
- if (marker !== `baseline:${name}` && marker !== `direct-apply:${name}`)
7109
- continue;
7110
- 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);
7111
7087
  }
7112
7088
  return keys;
7113
7089
  }
7114
- function historicalChecksumMarkerKeys(data, migrationFiles) {
7115
- const filesByKey = new Map(migrationFiles.map((migration) => [migrationIdentityKey(migration.version, migration.name), migration]));
7116
- const keys = new Set;
7117
- for (const row of migrationRows(data)) {
7118
- if (row.version == null || row.name == null || !Array.isArray(row.statements) || row.statements.length !== 1)
7119
- continue;
7120
- const marker = row.statements[0];
7121
- if (typeof marker !== "string" || !/^sha256:[0-9a-f]{64}$/.test(marker))
7122
- continue;
7123
- const key = migrationIdentityKey(String(row.version), String(row.name));
7124
- const migration = filesByKey.get(key);
7125
- if (!migration)
7126
- continue;
7127
- const checksum = createHash("sha256").update(migration.rawBytes).digest("hex");
7128
- if (marker === `sha256:${checksum}`)
7129
- 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);
7130
7133
  }
7131
- return keys;
7134
+ return plan;
7132
7135
  }
7133
7136
  function isAlreadyAppliedMigrationResponse(response) {
7134
7137
  if (response.status !== 409 || !response.data || typeof response.data !== "object")
@@ -7356,14 +7359,13 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
7356
7359
  const migrationFiles = sortMigrationFiles(files.map((file) => readMigrationFile(dir, file)));
7357
7360
  const migrationsResult = await http.get(`/v1/projects/${ref}/database/migrations`);
7358
7361
  if (!migrationsResult.ok) {
7359
- text = `❌ Failed to load applied migrations (${migrationsResult.status}): ${JSON.stringify(migrationsResult.data)}`;
7362
+ text = `❌ Failed to load applied migrations (${migrationsResult.status})`;
7360
7363
  break;
7361
7364
  }
7362
- assertNoMigrationIdentityConflicts(migrationsResult.data, migrationFiles);
7365
+ const migrationPlan = migrationPushPlan(migrationsResult.data, migrationFiles);
7363
7366
  if (args.dry_run) {
7364
- const appliedKeys = appliedMigrationKeys(migrationsResult.data);
7365
- const pending = migrationFiles.filter(({ name, version }) => !appliedKeys.has(name) && !appliedKeys.has(String(version)));
7366
- const alreadyApplied = migrationFiles.filter(({ name, version }) => appliedKeys.has(name) || appliedKeys.has(String(version)));
7367
+ const pending = migrationPlan.pending;
7368
+ const alreadyApplied = migrationPlan.alreadyApplied;
7367
7369
  const pendingWithSql = pending;
7368
7370
  let vectorEnabled = null;
7369
7371
  if (pendingWithSql.some(({ sql }) => sqlReferencesVector(sql) || sqlCreatesVectorExtension(sql))) {
@@ -7386,16 +7388,8 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
7386
7388
  break;
7387
7389
  }
7388
7390
  const applied = [];
7389
- const skipped = [];
7390
- const nameBoundMarkerKeys = nameBoundMigrationMarkerKeys(migrationsResult.data);
7391
- const historicalChecksumKeys = historicalChecksumMarkerKeys(migrationsResult.data, migrationFiles);
7392
- const legacyNameBoundKeys = legacyNameBoundMigrationKeys(migrationsResult.data, migrationFiles);
7393
- for (const { file, name, version, sql } of migrationFiles) {
7394
- const migrationKey = migrationIdentityKey(version, name);
7395
- if (nameBoundMarkerKeys.has(migrationKey) || historicalChecksumKeys.has(migrationKey) || legacyNameBoundKeys.has(migrationKey)) {
7396
- skipped.push(file);
7397
- continue;
7398
- }
7391
+ const skipped = migrationPlan.alreadyApplied.map(({ file }) => file);
7392
+ for (const { file, name, version, sql } of migrationPlan.pending) {
7399
7393
  const r = await http.post(`/v1/projects/${ref}/database/migrations`, { name, sql, version });
7400
7394
  if (r.ok) {
7401
7395
  applied.push(file);
@@ -7404,8 +7398,6 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
7404
7398
  } else {
7405
7399
  text = [
7406
7400
  `❌ Failed to apply ${file} (${r.status})`,
7407
- JSON.stringify(r.data, null, 2),
7408
- "",
7409
7401
  `Applied before failure: ${applied.length}`,
7410
7402
  `Skipped before failure: ${skipped.length}`
7411
7403
  ].join(`
@@ -12047,7 +12039,7 @@ var MUTATION_TOOL_SCHEMA = {
12047
12039
  // package.json
12048
12040
  var package_default = {
12049
12041
  name: "@supacloud/cli",
12050
- version: "0.21.2",
12042
+ version: "0.21.3",
12051
12043
  description: "Project-scoped CLI for SupaCloud users",
12052
12044
  type: "module",
12053
12045
  main: "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.21.2",
3
+ "version": "0.21.3",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",