@supacloud/cli 0.12.1 → 0.12.2
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 +73 -16
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6455,13 +6455,47 @@ class HttpTransport {
|
|
|
6455
6455
|
}
|
|
6456
6456
|
|
|
6457
6457
|
// src/shared/tools/database-tools.ts
|
|
6458
|
+
import { createHash } from "node:crypto";
|
|
6458
6459
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
6459
6460
|
import { basename, join } from "node:path";
|
|
6460
|
-
|
|
6461
|
+
var MAX_MIGRATION_VERSION = 9223372036854775807n;
|
|
6462
|
+
var FALLBACK_MIGRATION_VERSION_BASE = 8000000000000000000n;
|
|
6463
|
+
var FALLBACK_MIGRATION_VERSION_RANGE = 1000000000000000000n;
|
|
6464
|
+
var FALLBACK_MIGRATION_VERSION_LIMIT = FALLBACK_MIGRATION_VERSION_BASE + FALLBACK_MIGRATION_VERSION_RANGE;
|
|
6465
|
+
function migrationVersionFromFilename(file) {
|
|
6461
6466
|
const match = basename(file).match(/^(\d{8,20})[_-]/);
|
|
6462
|
-
if (match)
|
|
6463
|
-
|
|
6464
|
-
|
|
6467
|
+
if (match) {
|
|
6468
|
+
const version = BigInt(match[1]);
|
|
6469
|
+
if (version < 1n || version > MAX_MIGRATION_VERSION) {
|
|
6470
|
+
throw new Error(`Invalid migration version '${match[1]}' in ${basename(file)}: expected 1..${MAX_MIGRATION_VERSION}`);
|
|
6471
|
+
}
|
|
6472
|
+
if (version >= FALLBACK_MIGRATION_VERSION_BASE && version < FALLBACK_MIGRATION_VERSION_LIMIT) {
|
|
6473
|
+
throw new Error(`Invalid migration version '${match[1]}' in ${basename(file)}: version range ${FALLBACK_MIGRATION_VERSION_BASE}..${FALLBACK_MIGRATION_VERSION_LIMIT - 1n} is reserved for non-timestamp migrations`);
|
|
6474
|
+
}
|
|
6475
|
+
return version.toString();
|
|
6476
|
+
}
|
|
6477
|
+
const digest = createHash("sha256").update(basename(file)).digest("hex");
|
|
6478
|
+
const offset = BigInt(`0x${digest}`) % FALLBACK_MIGRATION_VERSION_RANGE;
|
|
6479
|
+
return (FALLBACK_MIGRATION_VERSION_BASE + offset).toString();
|
|
6480
|
+
}
|
|
6481
|
+
function sortMigrationFiles(migrations) {
|
|
6482
|
+
const sorted = [...migrations].sort((a, b) => {
|
|
6483
|
+
const versionA = BigInt(a.version);
|
|
6484
|
+
const versionB = BigInt(b.version);
|
|
6485
|
+
if (versionA < versionB)
|
|
6486
|
+
return -1;
|
|
6487
|
+
if (versionA > versionB)
|
|
6488
|
+
return 1;
|
|
6489
|
+
return a.file.localeCompare(b.file);
|
|
6490
|
+
});
|
|
6491
|
+
for (let i = 1;i < sorted.length; i += 1) {
|
|
6492
|
+
const previous = sorted[i - 1];
|
|
6493
|
+
const current = sorted[i];
|
|
6494
|
+
if (previous.version === current.version && (previous.file !== current.file || previous.name !== current.name)) {
|
|
6495
|
+
throw new Error(`Migration version collision for ${current.version}: ${previous.file} and ${current.file}`);
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
return sorted;
|
|
6465
6499
|
}
|
|
6466
6500
|
function appliedMigrationKeys(data) {
|
|
6467
6501
|
const rows = Array.isArray(data) ? data : data?.rows || [];
|
|
@@ -6477,6 +6511,24 @@ function appliedMigrationKeys(data) {
|
|
|
6477
6511
|
}
|
|
6478
6512
|
return keys;
|
|
6479
6513
|
}
|
|
6514
|
+
function migrationRows(data) {
|
|
6515
|
+
const rows = Array.isArray(data) ? data : data?.rows || [];
|
|
6516
|
+
return Array.isArray(rows) ? rows.filter((row) => Boolean(row && typeof row === "object")) : [];
|
|
6517
|
+
}
|
|
6518
|
+
function baselineMigrationKey(version, name) {
|
|
6519
|
+
return `${version}\x00${name}`;
|
|
6520
|
+
}
|
|
6521
|
+
function baselineMigrationKeys(data) {
|
|
6522
|
+
const keys = new Set;
|
|
6523
|
+
for (const row of migrationRows(data)) {
|
|
6524
|
+
if (row.version == null || row.name == null || !Array.isArray(row.statements))
|
|
6525
|
+
continue;
|
|
6526
|
+
if (row.statements.length !== 1 || row.statements[0] !== `baseline:${String(row.name)}`)
|
|
6527
|
+
continue;
|
|
6528
|
+
keys.add(baselineMigrationKey(String(row.version), String(row.name)));
|
|
6529
|
+
}
|
|
6530
|
+
return keys;
|
|
6531
|
+
}
|
|
6480
6532
|
function isAlreadyAppliedMigrationResponse(response) {
|
|
6481
6533
|
if (response.status !== 409 || !response.data || typeof response.data !== "object")
|
|
6482
6534
|
return false;
|
|
@@ -6695,18 +6747,18 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
6695
6747
|
text = `No .sql migration files found in ${dir}`;
|
|
6696
6748
|
break;
|
|
6697
6749
|
}
|
|
6698
|
-
const migrationFiles = files.map((file
|
|
6750
|
+
const migrationFiles = sortMigrationFiles(files.map((file) => ({
|
|
6699
6751
|
file,
|
|
6700
6752
|
name: basename(file, ".sql"),
|
|
6701
|
-
version: migrationVersionFromFilename(file
|
|
6702
|
-
}));
|
|
6753
|
+
version: migrationVersionFromFilename(file)
|
|
6754
|
+
})));
|
|
6755
|
+
const migrationsResult = await http.get(`/v1/projects/${ref}/database/migrations`);
|
|
6756
|
+
if (!migrationsResult.ok) {
|
|
6757
|
+
text = `❌ Failed to load applied migrations (${migrationsResult.status}): ${JSON.stringify(migrationsResult.data)}`;
|
|
6758
|
+
break;
|
|
6759
|
+
}
|
|
6703
6760
|
if (args.dry_run) {
|
|
6704
|
-
const
|
|
6705
|
-
if (!r.ok) {
|
|
6706
|
-
text = `❌ Failed to load applied migrations (${r.status}): ${JSON.stringify(r.data)}`;
|
|
6707
|
-
break;
|
|
6708
|
-
}
|
|
6709
|
-
const appliedKeys = appliedMigrationKeys(r.data);
|
|
6761
|
+
const appliedKeys = appliedMigrationKeys(migrationsResult.data);
|
|
6710
6762
|
const pending = migrationFiles.filter(({ name, version }) => !appliedKeys.has(name) && !appliedKeys.has(String(version)));
|
|
6711
6763
|
const alreadyApplied = migrationFiles.filter(({ name, version }) => appliedKeys.has(name) || appliedKeys.has(String(version)));
|
|
6712
6764
|
const pendingWithSql = pending.map((migration) => ({
|
|
@@ -6735,7 +6787,12 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
6735
6787
|
}
|
|
6736
6788
|
const applied = [];
|
|
6737
6789
|
const skipped = [];
|
|
6790
|
+
const baselineKeys = baselineMigrationKeys(migrationsResult.data);
|
|
6738
6791
|
for (const { file, name, version } of migrationFiles) {
|
|
6792
|
+
if (baselineKeys.has(baselineMigrationKey(version, name))) {
|
|
6793
|
+
skipped.push(file);
|
|
6794
|
+
continue;
|
|
6795
|
+
}
|
|
6739
6796
|
const sql = readFileSync2(join(dir, file), "utf-8");
|
|
6740
6797
|
const r = await http.post(`/v1/projects/${ref}/database/migrations`, { name, sql, version });
|
|
6741
6798
|
if (r.ok) {
|
|
@@ -6774,11 +6831,11 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
6774
6831
|
text = `No .sql migration files found in ${dir}`;
|
|
6775
6832
|
break;
|
|
6776
6833
|
}
|
|
6777
|
-
const migrationFiles = files.map((file
|
|
6834
|
+
const migrationFiles = sortMigrationFiles(files.map((file) => ({
|
|
6778
6835
|
file,
|
|
6779
6836
|
name: basename(file, ".sql"),
|
|
6780
|
-
version: migrationVersionFromFilename(file
|
|
6781
|
-
}));
|
|
6837
|
+
version: migrationVersionFromFilename(file)
|
|
6838
|
+
})));
|
|
6782
6839
|
const r = await http.get(`/v1/projects/${ref}/database/migrations`);
|
|
6783
6840
|
if (!r.ok) {
|
|
6784
6841
|
text = `❌ Failed to load applied migrations (${r.status}): ${JSON.stringify(r.data)}`;
|