@biffo/cli 0.48.0 → 0.49.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/core.version +1 -1
- package/dist/index.js +90 -4
- package/package.json +1 -1
package/core.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.49.1
|
package/dist/index.js
CHANGED
|
@@ -1304,20 +1304,50 @@ function chainOrder(migrations) {
|
|
|
1304
1304
|
function reissuedRevisionId(file) {
|
|
1305
1305
|
return `core_${createHash("sha256").update(file).digest("hex").slice(0, 8)}`;
|
|
1306
1306
|
}
|
|
1307
|
+
var CARRIED_FROM_MARKER = "# biffo:carried-from:";
|
|
1308
|
+
var CARRIED_FROM_RE = /^# biffo:carried-from:[ \t]*(\S+)[ \t]*$/m;
|
|
1309
|
+
function parseCarriedFrom(content) {
|
|
1310
|
+
return CARRIED_FROM_RE.exec(content)?.[1] ?? null;
|
|
1311
|
+
}
|
|
1312
|
+
function stampCarriedFrom(content, templateFile) {
|
|
1313
|
+
if (CARRIED_FROM_RE.test(content)) return content;
|
|
1314
|
+
return content.replace(REVISION_RE, (m) => `${CARRIED_FROM_MARKER} ${templateFile}
|
|
1315
|
+
${m}`);
|
|
1316
|
+
}
|
|
1317
|
+
var MODULE_DOCSTRING_RE = /^\s*("""|''')[\s\S]*?\1/;
|
|
1318
|
+
function migrationBodyHash(content) {
|
|
1319
|
+
const normalised = content.replace(MODULE_DOCSTRING_RE, "").split("\n").map((line) => line.trimEnd()).filter(
|
|
1320
|
+
(line) => !REVISION_RE.test(line) && !DOWN_REVISION_RE.test(line) && !line.trimStart().startsWith("#") && line !== ""
|
|
1321
|
+
).join("\n");
|
|
1322
|
+
return createHash("sha256").update(normalised).digest("hex");
|
|
1323
|
+
}
|
|
1324
|
+
function migrationSlug(file) {
|
|
1325
|
+
return file.replace(/\.py$/, "").replace(/^[0-9a-f]+_/i, "");
|
|
1326
|
+
}
|
|
1307
1327
|
function planMigrationCarry(options) {
|
|
1308
1328
|
const templateVersions = join5(options.templateDir, MIGRATIONS_VERSIONS_DIR);
|
|
1309
1329
|
const instanceVersions = join5(options.instanceDir, MIGRATIONS_VERSIONS_DIR);
|
|
1310
1330
|
const template = readMigrations(templateVersions);
|
|
1311
1331
|
const instance = readMigrations(instanceVersions);
|
|
1312
1332
|
const instanceHead = validateChain(instance, `${MIGRATIONS_VERSIONS_DIR} (instance)`);
|
|
1313
|
-
const instanceFiles = new Set(instance.map((m) => m.file));
|
|
1314
1333
|
const usedRevisions = new Set(instance.map((m) => m.revision));
|
|
1334
|
+
const identity = indexInstanceMigrations(instance);
|
|
1335
|
+
const templateBodyCounts = /* @__PURE__ */ new Map();
|
|
1336
|
+
for (const m of template) {
|
|
1337
|
+
const body = migrationBodyHash(m.content);
|
|
1338
|
+
templateBodyCounts.set(body, (templateBodyCounts.get(body) ?? 0) + 1);
|
|
1339
|
+
}
|
|
1315
1340
|
const entries = [];
|
|
1316
1341
|
const skipped = [];
|
|
1342
|
+
const recognised = [];
|
|
1317
1343
|
let head = instanceHead;
|
|
1318
1344
|
for (const m of chainOrder(template)) {
|
|
1319
|
-
|
|
1345
|
+
const already = alreadyCarried(m, identity, templateBodyCounts);
|
|
1346
|
+
if (already) {
|
|
1320
1347
|
skipped.push(m.file);
|
|
1348
|
+
if (already.how !== "filename") {
|
|
1349
|
+
recognised.push({ file: m.file, instanceFile: already.instance.file, how: already.how });
|
|
1350
|
+
}
|
|
1321
1351
|
continue;
|
|
1322
1352
|
}
|
|
1323
1353
|
let revision = m.revision;
|
|
@@ -1337,7 +1367,9 @@ function planMigrationCarry(options) {
|
|
|
1337
1367
|
file: m.file,
|
|
1338
1368
|
revision,
|
|
1339
1369
|
downRevision: head,
|
|
1340
|
-
|
|
1370
|
+
// Stamped before re-chaining so the instance's copy records which template
|
|
1371
|
+
// migration it is, whatever it is later renamed or renumbered to.
|
|
1372
|
+
content: rechainMigration(stampCarriedFrom(m.content, m.file), revision, head)
|
|
1341
1373
|
};
|
|
1342
1374
|
if (reissuedFrom !== void 0) entry.reissuedFrom = reissuedFrom;
|
|
1343
1375
|
entries.push(entry);
|
|
@@ -1356,7 +1388,56 @@ function planMigrationCarry(options) {
|
|
|
1356
1388
|
],
|
|
1357
1389
|
`${MIGRATIONS_VERSIONS_DIR} (after carry)`
|
|
1358
1390
|
);
|
|
1359
|
-
return { entries, instanceHead, skipped };
|
|
1391
|
+
return { entries, instanceHead, skipped, recognised };
|
|
1392
|
+
}
|
|
1393
|
+
function indexInstanceMigrations(instance) {
|
|
1394
|
+
const index = {
|
|
1395
|
+
byCarriedFrom: /* @__PURE__ */ new Map(),
|
|
1396
|
+
byFile: /* @__PURE__ */ new Map(),
|
|
1397
|
+
byBody: /* @__PURE__ */ new Map(),
|
|
1398
|
+
bySlug: /* @__PURE__ */ new Map()
|
|
1399
|
+
};
|
|
1400
|
+
for (const m of instance) {
|
|
1401
|
+
index.byFile.set(m.file, m);
|
|
1402
|
+
const from = parseCarriedFrom(m.content);
|
|
1403
|
+
if (from !== null) index.byCarriedFrom.set(from, m);
|
|
1404
|
+
const body = migrationBodyHash(m.content);
|
|
1405
|
+
index.byBody.set(body, [...index.byBody.get(body) ?? [], m]);
|
|
1406
|
+
const slug = migrationSlug(m.file);
|
|
1407
|
+
index.bySlug.set(slug, [...index.bySlug.get(slug) ?? [], m]);
|
|
1408
|
+
}
|
|
1409
|
+
return index;
|
|
1410
|
+
}
|
|
1411
|
+
function alreadyCarried(m, index, templateBodyCounts) {
|
|
1412
|
+
const byProvenance = index.byCarriedFrom.get(m.file);
|
|
1413
|
+
if (byProvenance) return { instance: byProvenance, how: "provenance" };
|
|
1414
|
+
const byFile = index.byFile.get(m.file);
|
|
1415
|
+
if (byFile) return { instance: byFile, how: "filename" };
|
|
1416
|
+
const body = migrationBodyHash(m.content);
|
|
1417
|
+
const bodyMatches = index.byBody.get(body) ?? [];
|
|
1418
|
+
if (bodyMatches.length === 1 && templateBodyCounts.get(body) === 1) {
|
|
1419
|
+
return { instance: bodyMatches[0], how: "body" };
|
|
1420
|
+
}
|
|
1421
|
+
const slugMatches = (index.bySlug.get(migrationSlug(m.file)) ?? []).filter(
|
|
1422
|
+
// A file already claimed as a copy of some OTHER template migration is not
|
|
1423
|
+
// an ambiguous match for this one.
|
|
1424
|
+
(candidate) => parseCarriedFrom(candidate.content) === null
|
|
1425
|
+
);
|
|
1426
|
+
if (slugMatches.length > 0) {
|
|
1427
|
+
const names = slugMatches.map((c) => c.file).join(", ");
|
|
1428
|
+
throw new Error(
|
|
1429
|
+
`Cannot carry ${m.file}: the instance has ${names}, which describes the same migration but whose contents differ, and which carries no provenance marker. Refusing to guess.
|
|
1430
|
+
|
|
1431
|
+
If it IS this migration (carried before provenance was recorded, then renamed or edited), add this line above its 'revision' assignment and re-run:
|
|
1432
|
+
|
|
1433
|
+
${CARRIED_FROM_MARKER} ${m.file}
|
|
1434
|
+
|
|
1435
|
+
If it is unrelated, rename it so the descriptions differ.
|
|
1436
|
+
|
|
1437
|
+
Carrying it blindly would re-issue an already-applied migration and run its DDL against a database that already has those objects (#366).`
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1440
|
+
return null;
|
|
1360
1441
|
}
|
|
1361
1442
|
function applyMigrationCarry(instanceDir, plan) {
|
|
1362
1443
|
const written = [];
|
|
@@ -1798,6 +1879,11 @@ var STATUS_COLOR = {
|
|
|
1798
1879
|
"keep-ours": chalk4.dim
|
|
1799
1880
|
};
|
|
1800
1881
|
function printMigrationCarry(migrations) {
|
|
1882
|
+
for (const r of migrations.recognised) {
|
|
1883
|
+
console.log(
|
|
1884
|
+
` ${chalk4.dim("already carried".padEnd(15))} ${r.file} ` + chalk4.dim(`\u2192 this instance calls it ${r.instanceFile} (matched by ${r.how})`)
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1801
1887
|
for (const e of migrations.entries) {
|
|
1802
1888
|
const suffix = e.reissuedFrom ? chalk4.dim(
|
|
1803
1889
|
` (revision ${e.revision}, re-issued from ${e.reissuedFrom}; revises ${e.downRevision ?? "base"})`
|