superbee 0.4.3-pre.7 → 0.4.3-pre.8
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/superbee.mjs +1029 -869
- package/package.json +1 -1
- package/references/hosted-checkout.md +8 -2
package/dist/superbee.mjs
CHANGED
|
@@ -44,7 +44,7 @@ var __toESM = (mod, isNodeMode, target3) => (target3 = mod != null ? __create(__
|
|
|
44
44
|
var define_SUPERBEE_BUILD_IDENTITY_default;
|
|
45
45
|
var init_define_SUPERBEE_BUILD_IDENTITY = __esm({
|
|
46
46
|
"<define:__SUPERBEE_BUILD_IDENTITY__>"() {
|
|
47
|
-
define_SUPERBEE_BUILD_IDENTITY_default = { schema: "superbee.build-identity.v1", package: { name: "superbee", version: "0.4.3-pre.
|
|
47
|
+
define_SUPERBEE_BUILD_IDENTITY_default = { schema: "superbee.build-identity.v1", package: { name: "superbee", version: "0.4.3-pre.8" }, source: { commit: "9d37b0a82d04092a95ebb2fd95d9faf93964d384", dirty: false }, artifact: { channel: "npm-package" }, compatibility_contracts: { skill: 1, hook: 1, mcp: 1 } };
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
|
|
@@ -27545,7 +27545,7 @@ var init_whole_document_transport = __esm({
|
|
|
27545
27545
|
init_version_transport();
|
|
27546
27546
|
init_read_adapter();
|
|
27547
27547
|
HOSTED_MANAGED_FIELDS = /* @__PURE__ */ new Set(["actor", "superbee_updated_by", "generated", "verified", "timestamp", "okf_version"]);
|
|
27548
|
-
IMPORT_CARRIED_FIELDS = Object.freeze(["generated", "timestamp", "actor", "okf_version"]);
|
|
27548
|
+
IMPORT_CARRIED_FIELDS = Object.freeze(["generated", "timestamp", "actor", "okf_version", "superbee_updated_by", "verified"]);
|
|
27549
27549
|
IMPORT_DROPPED_FIELDS = Object.freeze([...HOSTED_MANAGED_FIELDS].filter((field) => !IMPORT_CARRIED_FIELDS.includes(field)));
|
|
27550
27550
|
FRONTMATTER_KEY_LIMIT = 32;
|
|
27551
27551
|
UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
@@ -28287,12 +28287,37 @@ async function deleteLocal(local, id, options = {}) {
|
|
|
28287
28287
|
}
|
|
28288
28288
|
}
|
|
28289
28289
|
async function retireQuietly(backend, mode, target3) {
|
|
28290
|
+
let failed = false, failure3;
|
|
28290
28291
|
try {
|
|
28291
28292
|
await retireAcknowledgedBody(backend, mode, target3);
|
|
28292
28293
|
} catch (error51) {
|
|
28293
|
-
|
|
28294
|
-
|
|
28294
|
+
failed = true;
|
|
28295
|
+
failure3 = error51;
|
|
28295
28296
|
}
|
|
28297
|
+
try {
|
|
28298
|
+
if (!failed || failure3 instanceof JournalGuardConflict) retirementFailures.get(backend)?.delete(target3);
|
|
28299
|
+
else recordRetirementFailure(backend, target3, failure3);
|
|
28300
|
+
} catch {
|
|
28301
|
+
}
|
|
28302
|
+
}
|
|
28303
|
+
function recordRetirementFailure(backend, target3, error51) {
|
|
28304
|
+
let failures = retirementFailures.get(backend);
|
|
28305
|
+
if (!failures) retirementFailures.set(backend, failures = /* @__PURE__ */ new Map());
|
|
28306
|
+
const previous3 = failures.get(target3);
|
|
28307
|
+
const described = describeRetirementError(error51);
|
|
28308
|
+
failures.set(target3, { target: target3, failures: (previous3?.failures ?? 0) + 1, error: described, since: previous3?.since ?? (/* @__PURE__ */ new Date()).toISOString() });
|
|
28309
|
+
}
|
|
28310
|
+
function describeRetirementError(error51) {
|
|
28311
|
+
let name = "Error", message = "";
|
|
28312
|
+
try {
|
|
28313
|
+
const candidate = error51;
|
|
28314
|
+
if (candidate !== null && typeof candidate === "object") {
|
|
28315
|
+
if (typeof candidate.name === "string" && candidate.name) name = candidate.name;
|
|
28316
|
+
if (typeof candidate.message === "string") message = candidate.message;
|
|
28317
|
+
} else message = String(error51);
|
|
28318
|
+
} catch {
|
|
28319
|
+
}
|
|
28320
|
+
return { name: name.slice(0, 64), message: message.slice(0, RETIREMENT_MESSAGE_CHARS) };
|
|
28296
28321
|
}
|
|
28297
28322
|
function conflictResolutionKey(id) {
|
|
28298
28323
|
return `conflict-resolution:${id}`;
|
|
@@ -29055,7 +29080,7 @@ async function reclaimInFlight(local) {
|
|
|
29055
29080
|
}
|
|
29056
29081
|
return reclaimed;
|
|
29057
29082
|
}
|
|
29058
|
-
var BOOTSTRAP_KEY, SYNC_KEY, PULL_KEY, ACKNOWLEDGED_KEY, EMPTY_REGISTRY, UNSETTLED_STATES, DEFAULT_BATCH_SIZE, DEFAULT_CONCURRENCY, MIN_BOUNDED_DELETIONS, COMPOSE_ATTEMPTS,
|
|
29083
|
+
var BOOTSTRAP_KEY, SYNC_KEY, PULL_KEY, ACKNOWLEDGED_KEY, EMPTY_REGISTRY, UNSETTLED_STATES, DEFAULT_BATCH_SIZE, DEFAULT_CONCURRENCY, MIN_BOUNDED_DELETIONS, COMPOSE_ATTEMPTS, RETIREMENT_MESSAGE_CHARS, retirementFailures, ConflictReviewStaleError, BUSY_REFUSAL_CODES, PULL_MARK_ATTEMPTS;
|
|
29059
29084
|
var init_local_bundle = __esm({
|
|
29060
29085
|
"../browser-local/src/local-bundle.ts"() {
|
|
29061
29086
|
"use strict";
|
|
@@ -29084,7 +29109,8 @@ var init_local_bundle = __esm({
|
|
|
29084
29109
|
DEFAULT_CONCURRENCY = 8;
|
|
29085
29110
|
MIN_BOUNDED_DELETIONS = 8;
|
|
29086
29111
|
COMPOSE_ATTEMPTS = 3;
|
|
29087
|
-
|
|
29112
|
+
RETIREMENT_MESSAGE_CHARS = 512;
|
|
29113
|
+
retirementFailures = /* @__PURE__ */ new WeakMap();
|
|
29088
29114
|
ConflictReviewStaleError = class extends Error {
|
|
29089
29115
|
constructor() {
|
|
29090
29116
|
super("The conflict changed since review. Inspect it again before choosing a resolution.");
|
|
@@ -134372,9 +134398,806 @@ var init_publish_state = __esm({
|
|
|
134372
134398
|
}
|
|
134373
134399
|
});
|
|
134374
134400
|
|
|
134375
|
-
// ../cli/src/commands/
|
|
134401
|
+
// ../cli/src/commands/export.ts
|
|
134402
|
+
import { randomBytes as randomBytes9 } from "node:crypto";
|
|
134403
|
+
import { homedir as homedir39 } from "node:os";
|
|
134404
|
+
import { link as link3, lstat as lstat8, mkdir as mkdir4, open as open4, readdir as readdir8, realpath as realpath11, rename as rename2, rm as rm2, unlink as unlink13 } from "node:fs/promises";
|
|
134376
134405
|
import path54 from "node:path";
|
|
134377
|
-
import {
|
|
134406
|
+
import { parseArgs as parseArgs41 } from "node:util";
|
|
134407
|
+
function exportDeps(partial2) {
|
|
134408
|
+
return {
|
|
134409
|
+
stdout: partial2.stdout ?? ((text5) => void process.stdout.write(text5)),
|
|
134410
|
+
auth: partial2.auth ?? defaultHostedAuthDeps(homedir39()),
|
|
134411
|
+
cwd: partial2.cwd ?? process.cwd(),
|
|
134412
|
+
...partial2.fetch ? { fetch: partial2.fetch } : {}
|
|
134413
|
+
};
|
|
134414
|
+
}
|
|
134415
|
+
async function entryKind(target3) {
|
|
134416
|
+
let info;
|
|
134417
|
+
try {
|
|
134418
|
+
info = await lstat8(target3);
|
|
134419
|
+
} catch (error51) {
|
|
134420
|
+
if (error51.code === "ENOENT") return "absent";
|
|
134421
|
+
throw error51;
|
|
134422
|
+
}
|
|
134423
|
+
if (!info.isDirectory()) return "other";
|
|
134424
|
+
return (await readdir8(target3)).length === 0 ? "empty-dir" : "dir";
|
|
134425
|
+
}
|
|
134426
|
+
function shown(paths) {
|
|
134427
|
+
return [...paths].sort().slice(0, SHOWN);
|
|
134428
|
+
}
|
|
134429
|
+
function refusalCode(body) {
|
|
134430
|
+
const error51 = body?.error;
|
|
134431
|
+
if (typeof error51 === "string") return error51;
|
|
134432
|
+
const code3 = error51?.code;
|
|
134433
|
+
return typeof code3 === "string" ? code3 : void 0;
|
|
134434
|
+
}
|
|
134435
|
+
async function namedClient(source, deps2, resume2) {
|
|
134436
|
+
const { target: target3 } = source;
|
|
134437
|
+
const account = await connectHostedAccount(target3, { ...source.workspace !== null ? { workspace: source.workspace } : {}, resume: resume2, deadlineMs: EXPORT_DEADLINE_MS }, deps2);
|
|
134438
|
+
const { reference } = resolveBundleReference(
|
|
134439
|
+
{ slug: source.slug, bundleId: source.bundleId },
|
|
134440
|
+
account,
|
|
134441
|
+
target3,
|
|
134442
|
+
(named) => `${cliInvocation()} export ${commandToken(named)} --host ${commandToken(hostArgument(target3))} --to <folder>`
|
|
134443
|
+
);
|
|
134444
|
+
return { client: reference.slug === null ? account.client : account.client.within(reference.slug), slug: reference.slug };
|
|
134445
|
+
}
|
|
134446
|
+
async function fetchExport(source, deps2, resume2) {
|
|
134447
|
+
const named = source.binding ? null : await namedClient(source, deps2, resume2);
|
|
134448
|
+
const client = named ? named.client : (await connectCheckout(source.binding, { resume: resume2, deadlineMs: EXPORT_DEADLINE_MS }, deps2)).client;
|
|
134449
|
+
const slug = named ? named.slug : source.binding.workspace_slug ?? null;
|
|
134450
|
+
return fetchExportThrough({ client, slug, source, resume: resume2 });
|
|
134451
|
+
}
|
|
134452
|
+
async function fetchExportThrough(request3) {
|
|
134453
|
+
const { source } = request3;
|
|
134454
|
+
const { bundleId: bundleId2, target: target3 } = source;
|
|
134455
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
134456
|
+
const chain = new ExportPageChain();
|
|
134457
|
+
let bytes = 0;
|
|
134458
|
+
let cursor;
|
|
134459
|
+
let moved = false;
|
|
134460
|
+
for (; ; ) {
|
|
134461
|
+
let page;
|
|
134462
|
+
for (let tries = 1; page === void 0; tries += 1) {
|
|
134463
|
+
const answer2 = await exportAnswer(request3, { bundleId: bundleId2, paged: true, ...cursor !== void 0 ? { cursor } : {} });
|
|
134464
|
+
if (answer2.refused === "invalid_input" && cursor === void 0 && attempt === 1) return verified(await readArchive(await wholeAnswer(request3), source, MAX_EXPORT_BYTES), source);
|
|
134465
|
+
if (answer2.refused === "concurrent_change") {
|
|
134466
|
+
moved = true;
|
|
134467
|
+
break;
|
|
134468
|
+
}
|
|
134469
|
+
try {
|
|
134470
|
+
if (answer2.refused !== void 0) throw await exportRefusal(request3, answer2.refused, answer2.status);
|
|
134471
|
+
page = verified(await readArchive(answer2.ok, source, MAX_EXPORT_BYTES - bytes), source, { paged: true });
|
|
134472
|
+
} catch (error51) {
|
|
134473
|
+
const again = error51 instanceof CliError && (error51.details?.reason === "export_incomplete" || answer2.refused === "backend_unavailable" && error51.code === "TRANSIENT");
|
|
134474
|
+
if (!again || tries >= 2) throw error51;
|
|
134475
|
+
await pause2(PAGE_RETRY_MS);
|
|
134476
|
+
}
|
|
134477
|
+
}
|
|
134478
|
+
if (moved) break;
|
|
134479
|
+
bytes += page.bytes;
|
|
134480
|
+
cursor = joining(source, () => chain.add(page));
|
|
134481
|
+
if (cursor === void 0) return joining(source, () => chain.joined());
|
|
134482
|
+
}
|
|
134483
|
+
if (attempt >= PAGED_EXPORT_ATTEMPTS) {
|
|
134484
|
+
throw new CliError("TRANSIENT", `'${bundleId2}' kept changing on ${target3.origin} while it was exported; nothing was written`, {
|
|
134485
|
+
details: { reason: "export_source_changed", bundle_id: bundleId2, host: target3.origin, retryable: true },
|
|
134486
|
+
help: "retry the same command once the bundle is not being edited"
|
|
134487
|
+
});
|
|
134488
|
+
}
|
|
134489
|
+
const base = PAGED_EXPORT_RESTART_MS[attempt - 1];
|
|
134490
|
+
await pause2(Math.round(base / 2 + Math.random() * base / 2));
|
|
134491
|
+
}
|
|
134492
|
+
}
|
|
134493
|
+
async function exportAnswer({ client, source, resume: resume2 }, input) {
|
|
134494
|
+
let answer2;
|
|
134495
|
+
try {
|
|
134496
|
+
answer2 = await client.carrier.stream(`${client.prefix}/export`, input, client.signal);
|
|
134497
|
+
} catch (error51) {
|
|
134498
|
+
throw hostedFailure(error51, source.target, resume2);
|
|
134499
|
+
}
|
|
134500
|
+
if (!answer2.ok) return { refused: refusalCode(answer2.body) ?? "RUNTIME", status: answer2.status };
|
|
134501
|
+
return { ok: answer2.body };
|
|
134502
|
+
}
|
|
134503
|
+
async function wholeAnswer(request3) {
|
|
134504
|
+
const answer2 = await exportAnswer(request3, { bundleId: request3.source.bundleId });
|
|
134505
|
+
if (answer2.refused !== void 0) throw await exportRefusal(request3, answer2.refused, answer2.status);
|
|
134506
|
+
return answer2.ok;
|
|
134507
|
+
}
|
|
134508
|
+
function exportTooLarge(source) {
|
|
134509
|
+
return new CliError("RUNTIME", `${source.target.origin} answered an export larger than ${MAX_EXPORT_BYTES} bytes`, {
|
|
134510
|
+
details: { reason: "export_too_large", host: source.target.origin, bundle_id: source.bundleId, retryable: false, next: "the person exports it with Export bundle in the Superbee app" },
|
|
134511
|
+
help: `${cliInvocation()} whoami --host ${commandToken(hostArgument(source.target))}`
|
|
134512
|
+
});
|
|
134513
|
+
}
|
|
134514
|
+
async function exportRefusal({ client, slug, source, resume: resume2 }, code3, status2) {
|
|
134515
|
+
const { target: target3, bundleId: bundleId2 } = source;
|
|
134516
|
+
if (code3 === "bundle_not_found") {
|
|
134517
|
+
if (source.binding) return await bundleAbsent(source.binding, client);
|
|
134518
|
+
const text5 = hostedBundleReferenceText({ slug, bundleId: bundleId2 });
|
|
134519
|
+
return new CliError("NOT_FOUND", `no hosted bundle '${text5}' is visible to you on ${target3.origin}`, {
|
|
134520
|
+
details: { bundle_id: bundleId2, ...slug !== null ? { reference: text5 } : {}, host: target3.origin },
|
|
134521
|
+
help: hostedListCommand(target3)
|
|
134522
|
+
});
|
|
134523
|
+
}
|
|
134524
|
+
if (code3 === "result_too_large") {
|
|
134525
|
+
return new CliError("FORBIDDEN", `hosted bundle '${bundleId2}' is larger than an export carries`, {
|
|
134526
|
+
details: { reason: "bundle_too_large", bundle_id: bundleId2, host: target3.origin, next: "the person exports it with Export bundle in the Superbee app" },
|
|
134527
|
+
help: `${cliInvocation()} whoami --host ${commandToken(hostArgument(target3))}`
|
|
134528
|
+
});
|
|
134529
|
+
}
|
|
134530
|
+
if (code3 === "validation_failed") {
|
|
134531
|
+
return new CliError("FORBIDDEN", `hosted bundle '${bundleId2}' holds a path an export cannot carry`, {
|
|
134532
|
+
details: { reason: "unexportable_path", bundle_id: bundleId2, host: target3.origin, next: "the person renames the document or file in the Superbee app, then the same command is retried" },
|
|
134533
|
+
help: `${cliInvocation()} whoami --host ${commandToken(hostArgument(target3))}`
|
|
134534
|
+
});
|
|
134535
|
+
}
|
|
134536
|
+
return hostedFailure(new RemoteError(`hosted export answered ${status2}`, code3, status2), target3, resume2);
|
|
134537
|
+
}
|
|
134538
|
+
async function readArchive(body, source, budget) {
|
|
134539
|
+
const { target: target3, bundleId: bundleId2 } = source;
|
|
134540
|
+
const reader = body.getReader();
|
|
134541
|
+
const chunks = [];
|
|
134542
|
+
let size = 0;
|
|
134543
|
+
try {
|
|
134544
|
+
for (; ; ) {
|
|
134545
|
+
const part = await reader.read();
|
|
134546
|
+
if (part.done) break;
|
|
134547
|
+
size += part.value.byteLength;
|
|
134548
|
+
if (size > budget) throw exportTooLarge(source);
|
|
134549
|
+
chunks.push(part.value);
|
|
134550
|
+
}
|
|
134551
|
+
} catch (error51) {
|
|
134552
|
+
if (error51 instanceof CliError) throw error51;
|
|
134553
|
+
throw new CliError("TRANSIENT", `the export of '${bundleId2}' from ${target3.origin} stopped part way; nothing was written`, {
|
|
134554
|
+
details: { reason: "export_incomplete", bundle_id: bundleId2, host: target3.origin, retryable: true },
|
|
134555
|
+
help: "retry the same command"
|
|
134556
|
+
});
|
|
134557
|
+
} finally {
|
|
134558
|
+
void reader.cancel().catch(() => {
|
|
134559
|
+
});
|
|
134560
|
+
}
|
|
134561
|
+
const archive = new Uint8Array(size);
|
|
134562
|
+
let offset = 0;
|
|
134563
|
+
for (const chunk of chunks) {
|
|
134564
|
+
archive.set(chunk, offset);
|
|
134565
|
+
offset += chunk.byteLength;
|
|
134566
|
+
}
|
|
134567
|
+
return archive;
|
|
134568
|
+
}
|
|
134569
|
+
function verified(archive, source, options = {}) {
|
|
134570
|
+
return refusingArchive(source, () => verifyExport(archive, source.bundleId, options));
|
|
134571
|
+
}
|
|
134572
|
+
function joining(source, join19) {
|
|
134573
|
+
return refusingArchive(source, join19);
|
|
134574
|
+
}
|
|
134575
|
+
function refusingArchive(source, verify) {
|
|
134576
|
+
try {
|
|
134577
|
+
return verify();
|
|
134578
|
+
} catch (error51) {
|
|
134579
|
+
if (!(error51 instanceof ExportArchiveError)) throw error51;
|
|
134580
|
+
if (error51.problem === "incomplete") {
|
|
134581
|
+
throw new CliError("TRANSIENT", `the export of '${source.bundleId}' was not completed by the host; nothing was written`, {
|
|
134582
|
+
details: { reason: "export_incomplete", bundle_id: source.bundleId, host: source.target.origin, retryable: true },
|
|
134583
|
+
help: "retry the same command"
|
|
134584
|
+
});
|
|
134585
|
+
}
|
|
134586
|
+
throw new CliError("RUNTIME", `${source.target.origin} answered an export this CLI refuses (${error51.message}); nothing was written`, {
|
|
134587
|
+
details: { reason: `export_${error51.problem}`, bundle_id: source.bundleId, host: source.target.origin, retryable: false },
|
|
134588
|
+
help: "upgrade Superbee (npm install -g superbee); if it persists, report this bundle and reason"
|
|
134589
|
+
});
|
|
134590
|
+
}
|
|
134591
|
+
}
|
|
134592
|
+
async function writeNewFile(file2, bytes) {
|
|
134593
|
+
const handle = await open4(file2, "wx", 420);
|
|
134594
|
+
try {
|
|
134595
|
+
await handle.writeFile(bytes);
|
|
134596
|
+
await handle.sync();
|
|
134597
|
+
} finally {
|
|
134598
|
+
await handle.close();
|
|
134599
|
+
}
|
|
134600
|
+
}
|
|
134601
|
+
async function writeEntries(root, entries) {
|
|
134602
|
+
for (const entry of entries) {
|
|
134603
|
+
const file2 = path54.join(root, ...entry.path.split("/"));
|
|
134604
|
+
await mkdir4(path54.dirname(file2), { recursive: true });
|
|
134605
|
+
await writeNewFile(file2, entry.bytes);
|
|
134606
|
+
}
|
|
134607
|
+
}
|
|
134608
|
+
function gitRun(dir, args) {
|
|
134609
|
+
let result3;
|
|
134610
|
+
try {
|
|
134611
|
+
result3 = runGit(dir, args);
|
|
134612
|
+
} catch (error51) {
|
|
134613
|
+
const missing2 = error51.code === "GIT_MISSING";
|
|
134614
|
+
throw new CliError(missing2 ? "GIT_MISSING" : "RUNTIME", `git could not run (${error51 instanceof Error ? error51.message : String(error51)})`, {
|
|
134615
|
+
details: { reason: missing2 ? "git_missing" : "git_unavailable" },
|
|
134616
|
+
help: "install git, or export without --git"
|
|
134617
|
+
});
|
|
134618
|
+
}
|
|
134619
|
+
if (result3.status !== 0) {
|
|
134620
|
+
throw new CliError("RUNTIME", `git ${args.find((arg) => !arg.startsWith("-") && !arg.includes("=")) ?? ""} failed: ${result3.stderr.trim().split("\n").at(-1) ?? ""}`, {
|
|
134621
|
+
details: { reason: "git_failed" },
|
|
134622
|
+
help: "fix the git error, then retry the same command"
|
|
134623
|
+
});
|
|
134624
|
+
}
|
|
134625
|
+
return result3.stdout.trim();
|
|
134626
|
+
}
|
|
134627
|
+
function commitExport(dir, message, options) {
|
|
134628
|
+
const quiet = ["-c", "core.hooksPath=/dev/null"];
|
|
134629
|
+
gitRun(dir, [...quiet, "init", "-q"]);
|
|
134630
|
+
gitRun(dir, ["symbolic-ref", "HEAD", `refs/heads/${BOARD_BRANCH}`]);
|
|
134631
|
+
const excluded = ["--", ".", `:(exclude)${IN_PLACE_STAGING}`, `:(exclude)${CHECKOUT_MARKER_DIR}`];
|
|
134632
|
+
gitRun(dir, ["add", "-A", ...options.force ? ["--force"] : [], ...excluded]);
|
|
134633
|
+
gitRun(dir, [...quiet, ...identityFlags(dir), "-c", "commit.gpgsign=false", "commit", "-q", "--no-verify", "--allow-empty", "-m", message]);
|
|
134634
|
+
const committed = { branch: BOARD_BRANCH, commit: gitRun(dir, ["rev-parse", "HEAD"]) };
|
|
134635
|
+
if (options.force) return committed;
|
|
134636
|
+
const ignored = gitRun(dir, ["ls-files", "--others", "--ignored", "--exclude-standard", ...excluded]).split("\n").filter(Boolean);
|
|
134637
|
+
return ignored.length === 0 ? committed : { ...committed, ignored: { count: ignored.length, paths: shown(ignored) } };
|
|
134638
|
+
}
|
|
134639
|
+
function commitMessage(source, exported) {
|
|
134640
|
+
return `Export ${source.bundleId} from ${source.target.origin} at revision ${exported.source.revision}
|
|
134641
|
+
|
|
134642
|
+
superbee export, ${exported.exportedAt}: ${exported.counts.documents} documents, ${exported.counts.reserved} reserved files, ${exported.counts.blobs} blobs. History is not included.
|
|
134643
|
+
`;
|
|
134644
|
+
}
|
|
134645
|
+
function stagingName(target3) {
|
|
134646
|
+
return path54.join(path54.dirname(target3), `.${path54.basename(target3)}.superbee-export-${process.pid}-${randomBytes9(6).toString("hex")}.partial`);
|
|
134647
|
+
}
|
|
134648
|
+
function processAlive(pid) {
|
|
134649
|
+
try {
|
|
134650
|
+
process.kill(pid, 0);
|
|
134651
|
+
return true;
|
|
134652
|
+
} catch (error51) {
|
|
134653
|
+
return error51.code === "EPERM";
|
|
134654
|
+
}
|
|
134655
|
+
}
|
|
134656
|
+
async function removeAbandonedStaging(target3) {
|
|
134657
|
+
const prefix = `.${path54.basename(target3)}.superbee-export-`;
|
|
134658
|
+
let removed = 0;
|
|
134659
|
+
let names;
|
|
134660
|
+
try {
|
|
134661
|
+
names = await readdir8(path54.dirname(target3));
|
|
134662
|
+
} catch {
|
|
134663
|
+
return 0;
|
|
134664
|
+
}
|
|
134665
|
+
for (const name of names) {
|
|
134666
|
+
if (!name.startsWith(prefix)) continue;
|
|
134667
|
+
const match = /^(\d+)-[0-9a-f]{12}\.partial$/.exec(name.slice(prefix.length));
|
|
134668
|
+
if (!match) continue;
|
|
134669
|
+
const age = await lstat8(path54.join(path54.dirname(target3), name)).then((info) => Date.now() - info.mtimeMs, () => 0);
|
|
134670
|
+
if (processAlive(Number(match[1])) && age < ABANDONED_AFTER_MS) continue;
|
|
134671
|
+
await rm2(path54.join(path54.dirname(target3), name), { recursive: true, force: true });
|
|
134672
|
+
removed += 1;
|
|
134673
|
+
}
|
|
134674
|
+
return removed;
|
|
134675
|
+
}
|
|
134676
|
+
async function assertStandalone(target3, home2) {
|
|
134677
|
+
const parent = path54.dirname(target3);
|
|
134678
|
+
const help2 = "pass --to <folder> outside any bundle, board or checkout";
|
|
134679
|
+
const checkout2 = await hostedCheckoutAt(parent, home2);
|
|
134680
|
+
if (checkout2) {
|
|
134681
|
+
throw new CliError("FORBIDDEN", `refusing to export inside the hosted checkout at ${checkout2.path}`, { details: { reason: "inside_checkout", enclosing: checkout2.path }, help: help2 });
|
|
134682
|
+
}
|
|
134683
|
+
const enclosing = await findBundleRoot(parent).catch(() => null);
|
|
134684
|
+
if (enclosing) {
|
|
134685
|
+
throw new CliError("FORBIDDEN", `refusing to export inside the local bundle at ${enclosing}`, { details: { reason: "inside_bundle", enclosing }, help: help2 });
|
|
134686
|
+
}
|
|
134687
|
+
const binding = await resolveProjectBinding(parent).catch(() => null);
|
|
134688
|
+
if (binding) {
|
|
134689
|
+
throw new CliError("FORBIDDEN", `refusing to export inside a project bound to a board (${binding.file})`, { details: { reason: "inside_bound_project", binding: binding.file }, help: help2 });
|
|
134690
|
+
}
|
|
134691
|
+
}
|
|
134692
|
+
async function exportToFolder(source, toArg, git2, deps2, mode, resume2) {
|
|
134693
|
+
const requested = path54.resolve(deps2.cwd, toArg);
|
|
134694
|
+
assertBundleOutsidePrivateState(requested, deps2.auth.home);
|
|
134695
|
+
const found = await entryKind(requested);
|
|
134696
|
+
if (found === "other") throw new CliError("ALREADY_EXISTS", `${requested} exists and is not a folder`, { details: { reason: "not_a_folder", folder: requested }, help: "pass --to <new folder>" });
|
|
134697
|
+
if (found === "dir") throw new CliError("ALREADY_EXISTS", `${requested} is not empty`, { details: { reason: "not_empty", folder: requested }, help: "pass --to <new or empty folder>" });
|
|
134698
|
+
await mkdir4(path54.dirname(requested), { recursive: true });
|
|
134699
|
+
const target3 = path54.join(await realpath11(path54.dirname(requested)), path54.basename(requested));
|
|
134700
|
+
assertBundleOutsidePrivateState(target3, deps2.auth.home);
|
|
134701
|
+
await assertStandalone(target3, deps2.auth.home);
|
|
134702
|
+
const exported = await fetchExport(source, deps2, resume2);
|
|
134703
|
+
const abandoned = await removeAbandonedStaging(target3);
|
|
134704
|
+
const staging = stagingName(target3);
|
|
134705
|
+
await mkdir4(staging);
|
|
134706
|
+
let gitResult = null;
|
|
134707
|
+
try {
|
|
134708
|
+
await writeEntries(staging, exported.entries);
|
|
134709
|
+
if (git2) gitResult = commitExport(staging, commitMessage(source, exported), { force: true });
|
|
134710
|
+
const now = await entryKind(target3);
|
|
134711
|
+
if (now === "dir" || now === "other") throw new CliError("ALREADY_EXISTS", `${target3} is no longer empty`, { details: { reason: "not_empty", folder: target3 }, help: "pass --to <new or empty folder>" });
|
|
134712
|
+
try {
|
|
134713
|
+
await rename2(staging, target3);
|
|
134714
|
+
} catch (error51) {
|
|
134715
|
+
const code3 = error51.code;
|
|
134716
|
+
if (code3 === "ENOTEMPTY" || code3 === "EEXIST" || code3 === "ENOTDIR") {
|
|
134717
|
+
throw new CliError("ALREADY_EXISTS", `${target3} is no longer empty`, { details: { reason: "not_empty", folder: target3 }, help: "pass --to <new or empty folder>" });
|
|
134718
|
+
}
|
|
134719
|
+
throw error51;
|
|
134720
|
+
}
|
|
134721
|
+
} catch (error51) {
|
|
134722
|
+
await rm2(staging, { recursive: true, force: true }).catch(() => {
|
|
134723
|
+
});
|
|
134724
|
+
throw error51;
|
|
134725
|
+
}
|
|
134726
|
+
const home2 = await bundleHomeAt(target3, { home: deps2.auth.home });
|
|
134727
|
+
deps2.stdout(
|
|
134728
|
+
render(
|
|
134729
|
+
{
|
|
134730
|
+
export: "created",
|
|
134731
|
+
folder: target3,
|
|
134732
|
+
home: home2.home,
|
|
134733
|
+
bundle_id: source.bundleId,
|
|
134734
|
+
host: source.target.origin,
|
|
134735
|
+
revision: exported.source.revision,
|
|
134736
|
+
exported_at: exported.exportedAt,
|
|
134737
|
+
okf_edition: exported.source.okfEdition,
|
|
134738
|
+
documents: exported.counts.documents,
|
|
134739
|
+
reserved: exported.counts.reserved,
|
|
134740
|
+
blobs: exported.counts.blobs,
|
|
134741
|
+
bytes: exported.bytes,
|
|
134742
|
+
root_index: exported.entries.some((entry) => entry.path === "index.md"),
|
|
134743
|
+
...gitResult ? { git: gitResult } : {},
|
|
134744
|
+
...abandoned > 0 ? { removed_abandoned_exports: abandoned } : {},
|
|
134745
|
+
hosted: HOSTED_UNCHANGED,
|
|
134746
|
+
history: NO_HISTORY,
|
|
134747
|
+
help: nextSteps(target3, gitResult !== null)
|
|
134748
|
+
},
|
|
134749
|
+
mode
|
|
134750
|
+
)
|
|
134751
|
+
);
|
|
134752
|
+
}
|
|
134753
|
+
function nextSteps(folder, git2) {
|
|
134754
|
+
const steps = [commandFragment`${cliInvocation()} status --dir ${commandToken(folder)}`];
|
|
134755
|
+
if (git2) steps.push(commandFragment`git -C ${commandToken(folder)} remote add origin <url> && ${cliInvocation()} sync --establish --dir ${commandToken(folder)}`);
|
|
134756
|
+
return steps;
|
|
134757
|
+
}
|
|
134758
|
+
function isJournal(value) {
|
|
134759
|
+
const record6 = value;
|
|
134760
|
+
return record6 !== null && typeof record6 === "object" && record6.schema === JOURNAL_SCHEMA && typeof record6.bundle_id === "string" && typeof record6.host === "string" && typeof record6.audience === "string" && Number.isSafeInteger(record6.revision) && typeof record6.exported_at === "string" && typeof record6.git === "boolean" && Array.isArray(record6.adds) && record6.adds.every((entry) => typeof entry === "string") && Number.isSafeInteger(record6.same) && Array.isArray(record6.kept_local) && record6.kept_local.every((entry) => typeof entry === "string") && Array.isArray(record6.deleted_locally) && record6.deleted_locally.every((entry) => typeof entry === "string") && Number.isSafeInteger(record6.unsent_kept);
|
|
134761
|
+
}
|
|
134762
|
+
async function stagingDir(folder) {
|
|
134763
|
+
const staging = path54.join(folder, IN_PLACE_STAGING);
|
|
134764
|
+
try {
|
|
134765
|
+
if (!(await lstat8(staging)).isDirectory()) return null;
|
|
134766
|
+
return await realpath11(staging) === path54.join(await realpath11(folder), IN_PLACE_STAGING) ? staging : null;
|
|
134767
|
+
} catch {
|
|
134768
|
+
return null;
|
|
134769
|
+
}
|
|
134770
|
+
}
|
|
134771
|
+
async function readJournal(folder) {
|
|
134772
|
+
const staging = await stagingDir(folder);
|
|
134773
|
+
if (!staging) return null;
|
|
134774
|
+
let journal;
|
|
134775
|
+
const read = readRegularFileNoFollowSync(path54.join(staging, JOURNAL));
|
|
134776
|
+
if (read.state !== "present" || read.bytes.byteLength > MAX_JOURNAL_BYTES) return null;
|
|
134777
|
+
try {
|
|
134778
|
+
const value = JSON.parse(read.bytes.toString("utf8"));
|
|
134779
|
+
if (!isJournal(value)) return null;
|
|
134780
|
+
journal = value;
|
|
134781
|
+
} catch {
|
|
134782
|
+
return null;
|
|
134783
|
+
}
|
|
134784
|
+
const marker = readCheckoutMarker(folder);
|
|
134785
|
+
if (!marker || marker.bundle_id !== journal.bundle_id || marker.host !== bindingHostArgument({ origin: journal.host, audience: journal.audience })) return null;
|
|
134786
|
+
return journal;
|
|
134787
|
+
}
|
|
134788
|
+
async function sameBytes(file2, bytes) {
|
|
134789
|
+
const read = readRegularFileNoFollowSync(file2);
|
|
134790
|
+
return read.state === "present" && Buffer.compare(read.bytes, Buffer.from(bytes)) === 0;
|
|
134791
|
+
}
|
|
134792
|
+
async function placementOf(folder, entry) {
|
|
134793
|
+
let info;
|
|
134794
|
+
try {
|
|
134795
|
+
info = await lstat8(path54.join(folder, ...entry.path.split("/")));
|
|
134796
|
+
} catch (error51) {
|
|
134797
|
+
const code3 = error51.code;
|
|
134798
|
+
if (code3 === "ENOENT") return "add";
|
|
134799
|
+
if (code3 === "ENOTDIR") return "kept";
|
|
134800
|
+
throw error51;
|
|
134801
|
+
}
|
|
134802
|
+
if (!info.isFile()) return "kept";
|
|
134803
|
+
return await sameBytes(path54.join(folder, ...entry.path.split("/")), entry.bytes) ? "same" : "kept";
|
|
134804
|
+
}
|
|
134805
|
+
async function placeStaged(folder, staging, relative2) {
|
|
134806
|
+
const final = path54.join(folder, ...relative2.split("/"));
|
|
134807
|
+
const staged = path54.join(staging, "files", ...relative2.split("/"));
|
|
134808
|
+
try {
|
|
134809
|
+
if ((await lstat8(staged)).isFile()) {
|
|
134810
|
+
const stagedParent = await realpath11(path54.dirname(staged));
|
|
134811
|
+
if (!stagedParent.startsWith(`${await realpath11(staging)}${path54.sep}`)) return "kept";
|
|
134812
|
+
} else return "kept";
|
|
134813
|
+
} catch (error51) {
|
|
134814
|
+
if (error51.code !== "ENOENT") return "kept";
|
|
134815
|
+
return await lstat8(final).then((info) => info.isFile(), () => false) ? "same" : "kept";
|
|
134816
|
+
}
|
|
134817
|
+
try {
|
|
134818
|
+
await mkdir4(path54.dirname(final), { recursive: true });
|
|
134819
|
+
const parent = await realpath11(path54.dirname(final));
|
|
134820
|
+
if (parent !== folder && !parent.startsWith(`${folder}${path54.sep}`)) return "kept";
|
|
134821
|
+
} catch {
|
|
134822
|
+
return "kept";
|
|
134823
|
+
}
|
|
134824
|
+
let outcome;
|
|
134825
|
+
try {
|
|
134826
|
+
await link3(staged, final);
|
|
134827
|
+
outcome = "added";
|
|
134828
|
+
} catch (error51) {
|
|
134829
|
+
const code3 = error51.code;
|
|
134830
|
+
if (code3 !== "EEXIST") throw error51;
|
|
134831
|
+
const stagedRead = readRegularFileNoFollowSync(staged);
|
|
134832
|
+
outcome = stagedRead.state === "present" && await sameBytes(final, stagedRead.bytes) ? "same" : "kept";
|
|
134833
|
+
}
|
|
134834
|
+
await unlink13(staged).catch(() => {
|
|
134835
|
+
});
|
|
134836
|
+
return outcome;
|
|
134837
|
+
}
|
|
134838
|
+
async function writeJournal(staging, journal) {
|
|
134839
|
+
const temp = path54.join(staging, `.${JOURNAL}.${randomBytes9(6).toString("hex")}.tmp`);
|
|
134840
|
+
await writeNewFile(temp, Buffer.from(`${JSON.stringify(journal)}
|
|
134841
|
+
`, "utf8"));
|
|
134842
|
+
await rename2(temp, path54.join(staging, JOURNAL));
|
|
134843
|
+
}
|
|
134844
|
+
async function finishInPlace(folder, journal) {
|
|
134845
|
+
const staging = await stagingDir(folder);
|
|
134846
|
+
if (!staging) throw new CliError("RUNTIME", `the in-place export's staging folder in ${folder} is gone`, { help: `${cliInvocation()} export --in-place --dir ${commandToken(folder)}` });
|
|
134847
|
+
const added = [];
|
|
134848
|
+
const kept = [...journal.kept_local];
|
|
134849
|
+
let same = journal.same;
|
|
134850
|
+
for (const relative2 of journal.adds) {
|
|
134851
|
+
classifyEntryPath(relative2);
|
|
134852
|
+
const outcome = await placeStaged(folder, staging, relative2);
|
|
134853
|
+
if (outcome === "added") added.push(relative2);
|
|
134854
|
+
else if (outcome === "same") same += 1;
|
|
134855
|
+
else kept.push(relative2);
|
|
134856
|
+
}
|
|
134857
|
+
let git2 = null;
|
|
134858
|
+
if (journal.git) {
|
|
134859
|
+
git2 = await ownCommit(folder) ?? commitExport(folder, `Export ${journal.bundle_id} from ${journal.host} at revision ${journal.revision}
|
|
134860
|
+
|
|
134861
|
+
superbee export --in-place, ${journal.exported_at}. History is not included.
|
|
134862
|
+
`, { force: false });
|
|
134863
|
+
}
|
|
134864
|
+
await rm2(path54.join(staging, "files"), { recursive: true, force: true });
|
|
134865
|
+
await unlink13(path54.join(staging, JOURNAL)).catch(() => {
|
|
134866
|
+
});
|
|
134867
|
+
await rm2(staging, { recursive: true, force: true });
|
|
134868
|
+
const marker = await removeCheckoutMarker(folder, { origin: journal.host, audience: journal.audience, bundle_id: journal.bundle_id }).catch(() => false) ? "removed" : "none";
|
|
134869
|
+
return { added, kept, same, git: git2, marker };
|
|
134870
|
+
}
|
|
134871
|
+
function inPlaceReceipt(folder, journal, finished, homeName, resumed) {
|
|
134872
|
+
return {
|
|
134873
|
+
export: "converted",
|
|
134874
|
+
folder,
|
|
134875
|
+
home: homeName,
|
|
134876
|
+
bundle_id: journal.bundle_id,
|
|
134877
|
+
host: journal.host,
|
|
134878
|
+
revision: journal.revision,
|
|
134879
|
+
exported_at: journal.exported_at,
|
|
134880
|
+
...resumed ? { resumed: true } : {},
|
|
134881
|
+
binding: "removed (the folder is no longer a hosted checkout)",
|
|
134882
|
+
marker: finished.marker,
|
|
134883
|
+
added: finished.added.length,
|
|
134884
|
+
...finished.added.length > 0 ? { added_paths: shown(finished.added) } : {},
|
|
134885
|
+
unchanged: finished.same,
|
|
134886
|
+
kept_local: finished.kept.length,
|
|
134887
|
+
...finished.kept.length > 0 ? { kept_local_paths: shown(finished.kept), kept_local_note: "these files differ from the host's (or block its path); the folder's own were kept" } : {},
|
|
134888
|
+
...journal.deleted_locally.length > 0 ? { deleted_locally: journal.deleted_locally.length, deleted_locally_paths: shown(journal.deleted_locally), deleted_locally_note: "deleted in this folder and not sent; the host's copies were not put back" } : {},
|
|
134889
|
+
...journal.unsent_kept > 0 ? { unsent_kept: journal.unsent_kept, unsent_note: "changes sync had not sent stay in this folder only" } : {},
|
|
134890
|
+
...finished.git ? { git: finished.git } : {},
|
|
134891
|
+
hosted: HOSTED_UNCHANGED,
|
|
134892
|
+
history: NO_HISTORY,
|
|
134893
|
+
help: nextSteps(folder, finished.git !== null)
|
|
134894
|
+
};
|
|
134895
|
+
}
|
|
134896
|
+
async function ownCommit(folder) {
|
|
134897
|
+
const head = runGit(folder, ["rev-parse", "--verify", "--quiet", "HEAD"]);
|
|
134898
|
+
const top = runGit(folder, ["rev-parse", "--show-toplevel"]);
|
|
134899
|
+
const own5 = top.status === 0 && await realpath11(top.stdout.trim()).catch(() => "") === await realpath11(folder).catch(() => folder);
|
|
134900
|
+
if (!own5 || head.status !== 0) return null;
|
|
134901
|
+
return { branch: runGit(folder, ["symbolic-ref", "-q", "--short", "HEAD"]).stdout.trim() || BOARD_BRANCH, commit: head.stdout.trim() };
|
|
134902
|
+
}
|
|
134903
|
+
async function assertNoRepository(folder) {
|
|
134904
|
+
if (await lstat8(path54.join(folder, ".git")).then(() => true, () => false)) {
|
|
134905
|
+
throw new CliError("ALREADY_EXISTS", `${folder} already holds a Git repository`, {
|
|
134906
|
+
details: { reason: "already_git", folder },
|
|
134907
|
+
help: `${cliInvocation()} export --in-place --dir ${commandToken(folder)}`
|
|
134908
|
+
});
|
|
134909
|
+
}
|
|
134910
|
+
}
|
|
134911
|
+
async function exportInPlace(dirArg, git2, keepUnsent, deps2, mode, resume2) {
|
|
134912
|
+
const requested = path54.resolve(deps2.cwd, dirArg ?? ".");
|
|
134913
|
+
assertBundleOutsidePrivateState(requested, deps2.auth.home);
|
|
134914
|
+
const binding = await hostedCheckoutFor(dirArg === void 0 ? [] : ["--dir", dirArg], deps2.auth.home, deps2.cwd);
|
|
134915
|
+
if (!binding) {
|
|
134916
|
+
const folder2 = await realpath11(requested).catch(() => requested);
|
|
134917
|
+
const journal = await readJournal(folder2);
|
|
134918
|
+
if (journal) {
|
|
134919
|
+
const finished = await finishInPlace(folder2, journal);
|
|
134920
|
+
const home4 = await bundleHomeAt(folder2, { home: deps2.auth.home });
|
|
134921
|
+
deps2.stdout(render(inPlaceReceipt(folder2, journal, finished, home4.home, true), mode));
|
|
134922
|
+
return;
|
|
134923
|
+
}
|
|
134924
|
+
if (await entryKind(folder2) === "absent") throw new CliError("NOT_FOUND", `${folder2} does not exist`, { help: `${cliInvocation()} export --in-place --dir <checkout folder>` });
|
|
134925
|
+
const marker = readCheckoutMarker(folder2);
|
|
134926
|
+
if (marker) {
|
|
134927
|
+
const existing = git2 ? await ownCommit(folder2) : null;
|
|
134928
|
+
if (git2 && !existing) await assertNoRepository(folder2);
|
|
134929
|
+
const staging = await stagingDir(folder2);
|
|
134930
|
+
if (staging && !await lstat8(path54.join(staging, JOURNAL)).then(() => true, () => false)) await rm2(staging, { recursive: true, force: true });
|
|
134931
|
+
await removeCheckoutMarker(folder2, { origin: marker.host, audience: `${marker.host}/mcp`, bundle_id: marker.bundle_id });
|
|
134932
|
+
const committed = existing ?? (git2 ? commitExport(folder2, `Export ${marker.bundle_id} (an unbound copy of a hosted checkout)
|
|
134933
|
+
|
|
134934
|
+
superbee export --in-place. Nothing was fetched from the host; history is not included.
|
|
134935
|
+
`, { force: false }) : null);
|
|
134936
|
+
const home4 = await bundleHomeAt(folder2, { home: deps2.auth.home });
|
|
134937
|
+
deps2.stdout(
|
|
134938
|
+
render(
|
|
134939
|
+
{
|
|
134940
|
+
export: "converted",
|
|
134941
|
+
folder: folder2,
|
|
134942
|
+
home: home4.home,
|
|
134943
|
+
bundle_id: marker.bundle_id,
|
|
134944
|
+
from: "an unbound copy of a hosted checkout",
|
|
134945
|
+
marker: "removed",
|
|
134946
|
+
fetched: false,
|
|
134947
|
+
note: `nothing was fetched: the files are kept as they are. To fill in what the copy lacks from the host first, adopt it (${cliInvocation()} checkout --adopt <folder> --host <url>), then export --in-place`,
|
|
134948
|
+
...committed ? { git: committed } : {},
|
|
134949
|
+
hosted: HOSTED_UNCHANGED,
|
|
134950
|
+
history: NO_HISTORY,
|
|
134951
|
+
help: nextSteps(folder2, committed !== null)
|
|
134952
|
+
},
|
|
134953
|
+
mode
|
|
134954
|
+
)
|
|
134955
|
+
);
|
|
134956
|
+
return;
|
|
134957
|
+
}
|
|
134958
|
+
const home3 = await bundleHomeAt(folder2, { home: deps2.auth.home });
|
|
134959
|
+
deps2.stdout(render({ export: "unchanged", folder: folder2, home: home3.home, reason: "not a hosted checkout: nothing to convert" }, mode));
|
|
134960
|
+
return;
|
|
134961
|
+
}
|
|
134962
|
+
const folder = binding.path;
|
|
134963
|
+
if (git2) await assertNoRepository(folder);
|
|
134964
|
+
const status2 = await hostedStatus(binding, deps2.auth.home);
|
|
134965
|
+
if (status2.sync.state === "busy") {
|
|
134966
|
+
throw new CliError("CONFLICT", `another command holds the checkout lock for ${folder}`, { details: { reason: "checkout_busy", folder }, help: "wait for it to finish, then retry the same command" });
|
|
134967
|
+
}
|
|
134968
|
+
const unsentRefusal = (count5, sync3) => new CliError("CONFLICT", `the checkout of '${binding.bundle_id}' at ${folder} has ${count5} change(s) not on the host`, {
|
|
134969
|
+
details: {
|
|
134970
|
+
reason: "unsent_changes",
|
|
134971
|
+
folder,
|
|
134972
|
+
...sync3,
|
|
134973
|
+
keep_them_here_only: commandFragment`${cliInvocation()} export --in-place --dir ${commandToken(folder)} --keep-unsent${git2 ? commandFragment` --git` : commandFragment``}`
|
|
134974
|
+
},
|
|
134975
|
+
help: commandFragment`${cliInvocation()} sync --dir ${commandToken(folder)}`
|
|
134976
|
+
});
|
|
134977
|
+
const early = Number(status2.sync.unsent ?? 0) + Number(status2.sync.conflicts ?? 0) + Number(status2.sync.held_files ?? 0) + Number(status2.sync.held_deletions ?? 0);
|
|
134978
|
+
if (early > 0 && !keepUnsent) throw unsentRefusal(early, status2.sync);
|
|
134979
|
+
const source = { target: resolveHostedTarget(binding.audience), bundleId: binding.bundle_id, slug: binding.workspace_slug ?? null, workspace: null, binding };
|
|
134980
|
+
const exported = await fetchExport(source, deps2, resume2);
|
|
134981
|
+
const result3 = await filesystemPushRoleLocks().request(checkoutLockName(folder), { ifAvailable: true }, async (lock) => {
|
|
134982
|
+
if (!lock) {
|
|
134983
|
+
throw new CliError("CONFLICT", `another command holds the checkout lock for ${folder}`, { details: { reason: "checkout_busy", folder }, help: "wait for it to finish, then retry the same command" });
|
|
134984
|
+
}
|
|
134985
|
+
const current = await bindingForPath(deps2.auth.home, folder);
|
|
134986
|
+
if (!current || current.checkout_id !== binding.checkout_id) {
|
|
134987
|
+
throw new CliError("CONFLICT", `the checkout at ${folder} changed while it was exported`, { details: { reason: "checkout_changed", folder }, help: "retry the same command" });
|
|
134988
|
+
}
|
|
134989
|
+
const store = await FileJournaledBackend.open({ directory: checkoutStoreDir(deps2.auth.home, binding.checkout_id), readOnly: true });
|
|
134990
|
+
let pending;
|
|
134991
|
+
try {
|
|
134992
|
+
const classified = await classifyCheckout(binding, deps2.auth.home, store);
|
|
134993
|
+
pending = /* @__PURE__ */ new Set([...classified.unsent, ...classified.conflicts, ...classified.held.keys(), ...classified.heldDeletions]);
|
|
134994
|
+
} finally {
|
|
134995
|
+
await store.close();
|
|
134996
|
+
}
|
|
134997
|
+
if (pending.size > 0 && !keepUnsent) throw unsentRefusal(pending.size, { unsent_ids: shown([...pending]) });
|
|
134998
|
+
const staging = path54.join(folder, IN_PLACE_STAGING);
|
|
134999
|
+
await rm2(staging, { recursive: true, force: true });
|
|
135000
|
+
await mkdir4(path54.join(staging, "files"), { recursive: true });
|
|
135001
|
+
const adds = [];
|
|
135002
|
+
const kept = [];
|
|
135003
|
+
const deletedLocally = [];
|
|
135004
|
+
let same = 0;
|
|
135005
|
+
for (const entry of exported.entries) {
|
|
135006
|
+
const id = entry.kind === "document" ? entry.path.slice(0, -3) : entry.path;
|
|
135007
|
+
const placement = await placementOf(folder, entry);
|
|
135008
|
+
if (placement === "add" && pending.has(id)) {
|
|
135009
|
+
deletedLocally.push(entry.path);
|
|
135010
|
+
continue;
|
|
135011
|
+
}
|
|
135012
|
+
if (placement === "same") same += 1;
|
|
135013
|
+
else if (placement === "kept") kept.push(entry.path);
|
|
135014
|
+
else {
|
|
135015
|
+
const file2 = path54.join(staging, "files", ...entry.path.split("/"));
|
|
135016
|
+
await mkdir4(path54.dirname(file2), { recursive: true });
|
|
135017
|
+
await writeNewFile(file2, entry.bytes);
|
|
135018
|
+
adds.push(entry.path);
|
|
135019
|
+
}
|
|
135020
|
+
}
|
|
135021
|
+
const journal = {
|
|
135022
|
+
schema: JOURNAL_SCHEMA,
|
|
135023
|
+
bundle_id: binding.bundle_id,
|
|
135024
|
+
host: binding.origin,
|
|
135025
|
+
audience: binding.audience,
|
|
135026
|
+
revision: exported.source.revision,
|
|
135027
|
+
exported_at: exported.exportedAt,
|
|
135028
|
+
git: git2,
|
|
135029
|
+
adds,
|
|
135030
|
+
same,
|
|
135031
|
+
kept_local: kept,
|
|
135032
|
+
deleted_locally: deletedLocally,
|
|
135033
|
+
unsent_kept: pending.size
|
|
135034
|
+
};
|
|
135035
|
+
await writeJournal(staging, journal);
|
|
135036
|
+
await releaseCheckout(deps2.auth.home, binding);
|
|
135037
|
+
return { journal, finished: await finishInPlace(folder, journal) };
|
|
135038
|
+
});
|
|
135039
|
+
const home2 = await bundleHomeAt(folder, { home: deps2.auth.home });
|
|
135040
|
+
deps2.stdout(render(inPlaceReceipt(folder, result3.journal, result3.finished, home2.home, false), mode));
|
|
135041
|
+
}
|
|
135042
|
+
async function exportCommand(argv2, partial2 = {}) {
|
|
135043
|
+
const deps2 = exportDeps(partial2);
|
|
135044
|
+
const { values, positionals } = parseLeafOrUsage(
|
|
135045
|
+
() => parseArgs41({
|
|
135046
|
+
args: argv2,
|
|
135047
|
+
options: {
|
|
135048
|
+
to: { type: "string" },
|
|
135049
|
+
"in-place": { type: "boolean" },
|
|
135050
|
+
dir: { type: "string" },
|
|
135051
|
+
host: { type: "string" },
|
|
135052
|
+
workspace: { type: "string" },
|
|
135053
|
+
git: { type: "boolean" },
|
|
135054
|
+
"keep-unsent": { type: "boolean" },
|
|
135055
|
+
json: { type: "boolean" },
|
|
135056
|
+
help: { type: "boolean", short: "h" }
|
|
135057
|
+
},
|
|
135058
|
+
allowPositionals: true
|
|
135059
|
+
}),
|
|
135060
|
+
CLI_LEAVES.export
|
|
135061
|
+
);
|
|
135062
|
+
if (values.help) {
|
|
135063
|
+
deps2.stdout(renderUsage(EXPORT_USAGE));
|
|
135064
|
+
return;
|
|
135065
|
+
}
|
|
135066
|
+
const mode = resolveMode(values);
|
|
135067
|
+
const usage3 = (message) => new CliError("USAGE", message, { help: `${cliInvocation()} export --help` });
|
|
135068
|
+
const named = positionals[0];
|
|
135069
|
+
const inPlace = values["in-place"] === true;
|
|
135070
|
+
if (inPlace === (values.to !== void 0)) throw usage3("pass exactly one of --to <folder> and --in-place");
|
|
135071
|
+
if (named !== void 0 && values.dir !== void 0) throw usage3("pass a bundle id or --dir <checkout>, not both");
|
|
135072
|
+
if (named === void 0 && (values.host !== void 0 || values.workspace !== void 0)) throw usage3("--host and --workspace go with a bundle id; a checkout uses its own");
|
|
135073
|
+
if (inPlace && named !== void 0) throw usage3("--in-place converts a checkout folder: pass --dir <checkout>, not a bundle id");
|
|
135074
|
+
if (!inPlace && values["keep-unsent"]) throw usage3("--keep-unsent goes with --in-place");
|
|
135075
|
+
const reference = named === void 0 ? void 0 : parseHostedBundleReference(named);
|
|
135076
|
+
if (named !== void 0 && !reference) throw usage3(`'${named}' is not a hosted bundle id or <workspace>/<bundle-id>`);
|
|
135077
|
+
const git2 = values.git === true;
|
|
135078
|
+
const resume2 = commandFragment`${cliInvocation()} export${named !== void 0 ? commandFragment` ${commandToken(named)}` : commandFragment``}${values.dir !== void 0 ? commandFragment` --dir ${commandToken(path54.resolve(deps2.cwd, values.dir))}` : commandFragment``}${values.to !== void 0 ? commandFragment` --to ${commandToken(path54.resolve(deps2.cwd, values.to))}` : commandFragment``}${inPlace ? commandFragment` --in-place` : commandFragment``}${values.host !== void 0 ? commandFragment` --host ${commandToken(values.host)}` : commandFragment``}${values.workspace !== void 0 ? commandFragment` --workspace ${commandToken(values.workspace)}` : commandFragment``}${git2 ? commandFragment` --git` : commandFragment``}${values["keep-unsent"] ? commandFragment` --keep-unsent` : commandFragment``}${values.json ? commandFragment` --json` : commandFragment``}`;
|
|
135079
|
+
if (values.dir !== void 0) assertBundleOutsidePrivateState(path54.resolve(deps2.cwd, values.dir), deps2.auth.home);
|
|
135080
|
+
if (inPlace) {
|
|
135081
|
+
await exportInPlace(values.dir, git2, values["keep-unsent"] === true, deps2, mode, resume2);
|
|
135082
|
+
return;
|
|
135083
|
+
}
|
|
135084
|
+
let source;
|
|
135085
|
+
if (reference) {
|
|
135086
|
+
const named2 = await requireHostedBundleHost(values.host, deps2.auth);
|
|
135087
|
+
source = {
|
|
135088
|
+
target: named2.target,
|
|
135089
|
+
bundleId: reference.bundleId,
|
|
135090
|
+
slug: reference.slug,
|
|
135091
|
+
workspace: workspaceForNamedHost(named2, values.workspace) ?? null,
|
|
135092
|
+
binding: null
|
|
135093
|
+
};
|
|
135094
|
+
} else {
|
|
135095
|
+
const binding = await hostedCheckoutFor(values.dir === void 0 ? [] : ["--dir", values.dir], deps2.auth.home, deps2.cwd);
|
|
135096
|
+
if (!binding) {
|
|
135097
|
+
throw new CliError("NOT_FOUND", `${path54.resolve(deps2.cwd, values.dir ?? ".")} is not a hosted checkout`, {
|
|
135098
|
+
details: { reason: "not_a_checkout" },
|
|
135099
|
+
help: `${cliInvocation()} export <bundle-id> --to ${commandToken(values.to)}`
|
|
135100
|
+
});
|
|
135101
|
+
}
|
|
135102
|
+
source = { target: resolveHostedTarget(binding.audience), bundleId: binding.bundle_id, slug: binding.workspace_slug ?? null, workspace: null, binding };
|
|
135103
|
+
}
|
|
135104
|
+
await exportToFolder(source, values.to, git2, deps2, mode, resume2);
|
|
135105
|
+
}
|
|
135106
|
+
var EXPORT_USAGE, EXPORT_DEADLINE_MS, SHOWN, JOURNAL, JOURNAL_SCHEMA, MAX_JOURNAL_BYTES, NO_HISTORY, HOSTED_UNCHANGED, PAGED_EXPORT_ATTEMPTS, PAGED_EXPORT_RESTART_MS, PAGE_RETRY_MS, pause2, ABANDONED_AFTER_MS;
|
|
135107
|
+
var init_export = __esm({
|
|
135108
|
+
"../cli/src/commands/export.ts"() {
|
|
135109
|
+
"use strict";
|
|
135110
|
+
init_define_SUPERBEE_BUILD_IDENTITY();
|
|
135111
|
+
init_define_SUPERBEE_UPDATE_POLICY();
|
|
135112
|
+
init_src();
|
|
135113
|
+
init_filesystem_push_role();
|
|
135114
|
+
init_src2();
|
|
135115
|
+
init_args();
|
|
135116
|
+
init_bundle2();
|
|
135117
|
+
init_bundle_home();
|
|
135118
|
+
init_command_spec();
|
|
135119
|
+
init_command_text();
|
|
135120
|
+
init_errors4();
|
|
135121
|
+
init_invocation();
|
|
135122
|
+
init_output2();
|
|
135123
|
+
init_private_state_bundle_boundary();
|
|
135124
|
+
init_nofollow_read();
|
|
135125
|
+
init_autopull2();
|
|
135126
|
+
init_session();
|
|
135127
|
+
init_discovery();
|
|
135128
|
+
init_binding();
|
|
135129
|
+
init_client();
|
|
135130
|
+
init_account();
|
|
135131
|
+
init_refusals();
|
|
135132
|
+
init_reference();
|
|
135133
|
+
init_sync2();
|
|
135134
|
+
init_status();
|
|
135135
|
+
init_file_journaled_backend();
|
|
135136
|
+
init_marker();
|
|
135137
|
+
init_export_archive();
|
|
135138
|
+
init_export_archive();
|
|
135139
|
+
EXPORT_USAGE = `superbee export \u2014 copy a hosted bundle out of hosted Superbee
|
|
135140
|
+
|
|
135141
|
+
Usage:
|
|
135142
|
+
superbee export <bundle-id> --to <folder> [--host <url>] [--workspace <id>] [--git] [--json]
|
|
135143
|
+
superbee export [--dir <checkout>] --to <folder> [--git] [--json]
|
|
135144
|
+
superbee export [--dir <checkout>] --in-place [--git] [--keep-unsent] [--json]
|
|
135145
|
+
|
|
135146
|
+
Fetches the hosted bundle's portable export: every document, reserved file (index.md, log.md)
|
|
135147
|
+
and blob at its latest confirmed revision, byte for byte as the host stores them. The archive is
|
|
135148
|
+
verified whole (every entry against the host's digest manifest) before any file is written.
|
|
135149
|
+
Signs in if needed (AUTH_REQUIRED, exit 4, carries the one link to relay and the command to
|
|
135150
|
+
re-run). The hosted bundle is never changed. History is not exported: the folder holds the
|
|
135151
|
+
current revision only.
|
|
135152
|
+
|
|
135153
|
+
--to <folder> writes a new local bundle there. The folder must be new or empty; it appears
|
|
135154
|
+
complete or not at all. With a bundle id the host is --host, else your last sign-in; with
|
|
135155
|
+
--dir (or run inside a checkout) the checkout's own host and bundle are used. A bundle id in more
|
|
135156
|
+
than one of your workspaces is named <workspace>/<bundle-id>, or with --workspace.
|
|
135157
|
+
|
|
135158
|
+
--in-place converts a hosted checkout into a plain local bundle: it adds the files the checkout
|
|
135159
|
+
lacks (blobs, reserved files, documents added on the host), never overwrites a file, then
|
|
135160
|
+
forgets the checkout's binding. It refuses a checkout with changes sync has not sent, conflicts
|
|
135161
|
+
or held files; run 'superbee sync' first, or pass --keep-unsent to keep them in this folder only.
|
|
135162
|
+
If it is interrupted, re-running the same command finishes it.
|
|
135163
|
+
|
|
135164
|
+
--git makes the result a Git board: a repository on branch '${BOARD_BRANCH}' whose first commit is the
|
|
135165
|
+
export. Share it with 'git remote add origin <url>' and 'superbee sync --establish'.
|
|
135166
|
+
|
|
135167
|
+
Options:
|
|
135168
|
+
--to <folder> New or empty folder to export into
|
|
135169
|
+
--in-place Convert the hosted checkout (--dir, else the current folder) into a local bundle
|
|
135170
|
+
--dir <checkout> The hosted checkout to export (default: the current folder)
|
|
135171
|
+
--host <url> Hosted Superbee URL, with a bundle id; default: your last sign-in
|
|
135172
|
+
--workspace <id> With a bundle id: your workspace that holds it, by id or slug
|
|
135173
|
+
--git Initialize a Git repository on branch '${BOARD_BRANCH}' and commit the export
|
|
135174
|
+
--keep-unsent With --in-place: convert even with unsent changes, conflicts or held files
|
|
135175
|
+
--json Emit compact JSON instead of TOON
|
|
135176
|
+
-h, --help Show this help
|
|
135177
|
+
|
|
135178
|
+
Examples:
|
|
135179
|
+
superbee export team.knowledge --to ~/work/team-knowledge --git
|
|
135180
|
+
superbee export --dir ~/work/team --to ~/work/team-copy
|
|
135181
|
+
superbee export --dir ~/work/team --in-place
|
|
135182
|
+
`;
|
|
135183
|
+
EXPORT_DEADLINE_MS = 6e4;
|
|
135184
|
+
SHOWN = 20;
|
|
135185
|
+
JOURNAL = IN_PLACE_JOURNAL;
|
|
135186
|
+
JOURNAL_SCHEMA = 1;
|
|
135187
|
+
MAX_JOURNAL_BYTES = 8 * 1024 * 1024;
|
|
135188
|
+
NO_HISTORY = "not exported: the folder holds the current revision only";
|
|
135189
|
+
HOSTED_UNCHANGED = "unchanged: the hosted bundle still exists and was not modified";
|
|
135190
|
+
PAGED_EXPORT_ATTEMPTS = 3;
|
|
135191
|
+
PAGED_EXPORT_RESTART_MS = [500, 1500];
|
|
135192
|
+
PAGE_RETRY_MS = 1e3;
|
|
135193
|
+
pause2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
135194
|
+
ABANDONED_AFTER_MS = 60 * 60 * 1e3;
|
|
135195
|
+
}
|
|
135196
|
+
});
|
|
135197
|
+
|
|
135198
|
+
// ../cli/src/commands/checkout-adopt.ts
|
|
135199
|
+
import path55 from "node:path";
|
|
135200
|
+
import { lstat as lstat9, realpath as realpath12 } from "node:fs/promises";
|
|
134378
135201
|
function readLeaf(file2) {
|
|
134379
135202
|
const read = readRegularFileNoFollowSync(file2);
|
|
134380
135203
|
if (read.state === "missing") return null;
|
|
@@ -134397,11 +135220,11 @@ async function withCheckoutLock(folder, body) {
|
|
|
134397
135220
|
});
|
|
134398
135221
|
}
|
|
134399
135222
|
async function adopt(folderArg, options, deps2, mode) {
|
|
134400
|
-
const folder =
|
|
135223
|
+
const folder = path55.resolve(deps2.cwd, folderArg);
|
|
134401
135224
|
let canonical2;
|
|
134402
135225
|
try {
|
|
134403
|
-
canonical2 = await
|
|
134404
|
-
if (!(await
|
|
135226
|
+
canonical2 = await realpath12(folder);
|
|
135227
|
+
if (!(await lstat9(canonical2)).isDirectory()) throw new Error("not a folder");
|
|
134405
135228
|
} catch {
|
|
134406
135229
|
throw new CliError("NOT_FOUND", `${folder} is not a folder`, { help: `${cliInvocation()} checkout --adopt <folder> --host <url>` });
|
|
134407
135230
|
}
|
|
@@ -134416,7 +135239,7 @@ async function adopt(folderArg, options, deps2, mode) {
|
|
|
134416
135239
|
}
|
|
134417
135240
|
if (options.workspace !== void 0 && await rebindToWorkspace(canonical2, bound, options, deps2, mode)) return;
|
|
134418
135241
|
const marker2 = await writeCheckoutMarker(canonical2, bound).catch(() => null);
|
|
134419
|
-
deps2.stdout(render({ adopted: "unchanged", ...bindingView(bound), marker: marker2 ? "written" : "present", help:
|
|
135242
|
+
deps2.stdout(render({ adopted: "unchanged", ...bindingView(bound), marker: marker2 ? "written" : "present", help: nextSteps2(canonical2) }, mode));
|
|
134420
135243
|
return;
|
|
134421
135244
|
}
|
|
134422
135245
|
const folderMarker = readCheckoutMarker(canonical2);
|
|
@@ -134456,7 +135279,7 @@ async function adopt(folderArg, options, deps2, mode) {
|
|
|
134456
135279
|
...marker2 ? { marker: "written" } : {},
|
|
134457
135280
|
catalog: relocated ? { relocated: true, label: relocated.label, id: relocated.id } : { relocated: false, note: "no catalog entry named the old folder" },
|
|
134458
135281
|
...options.workspace !== void 0 ? { workspace_flag: "ignored: a moved checkout keeps the workspace it was bound with" } : {},
|
|
134459
|
-
help: [...
|
|
135282
|
+
help: [...nextSteps2(canonical2), `${cliInvocation()} sync --dir ${commandToken(canonical2)}`]
|
|
134460
135283
|
},
|
|
134461
135284
|
mode
|
|
134462
135285
|
)
|
|
@@ -134508,7 +135331,7 @@ async function adopt(folderArg, options, deps2, mode) {
|
|
|
134508
135331
|
const typed = { slug: marker.workspace_slug ?? null, bundleId: bundleId2 };
|
|
134509
135332
|
await assertStandaloneFolder(canonical2);
|
|
134510
135333
|
const resume2 = commandFragment`${cliInvocation()} checkout --adopt ${commandToken(canonical2)} --host ${commandToken(bindingHostArgument(target3))}${options.workspace !== void 0 ? commandFragment` --workspace ${commandToken(options.workspace)}` : commandFragment``}${options.json ? commandFragment` --json` : commandFragment``}`;
|
|
134511
|
-
const connection = await connectHostedBundle(typed, target3, options.workspace, deps2, resume2);
|
|
135334
|
+
const connection = await connectHostedBundle(typed, target3, options.workspace, deps2, resume2, { deadlineMs: EXPORT_DEADLINE_MS });
|
|
134512
135335
|
if (typed.slug === null && connection.reference.slug !== null) {
|
|
134513
135336
|
const found = readBundleListing(await connection.client.bundles()).lookup({ slug: null, bundleId: bundleId2 });
|
|
134514
135337
|
if (found.holders > 1) {
|
|
@@ -134520,8 +135343,12 @@ async function adopt(folderArg, options, deps2, mode) {
|
|
|
134520
135343
|
}
|
|
134521
135344
|
}
|
|
134522
135345
|
const published = await readPublishedExtras(home2, canonical2);
|
|
134523
|
-
const
|
|
134524
|
-
const
|
|
135346
|
+
const host = await adoptedExtras(canonical2, exportRequestFor(connection, target3, bundleId2, resume2));
|
|
135347
|
+
const extras = {
|
|
135348
|
+
...host.extras,
|
|
135349
|
+
...published && published.host === target3.origin && published.bundle_id === bundleId2 ? published.extras : {}
|
|
135350
|
+
};
|
|
135351
|
+
const result3 = await bindWithExtras({ canonical: canonical2, target: target3, bundleId: bundleId2, connection, deps: deps2, resume: resume2, extras }, host);
|
|
134525
135352
|
if (published) await clearPublishedExtras(home2, canonical2);
|
|
134526
135353
|
const cataloged = await registerInCatalog(home2, result3.binding);
|
|
134527
135354
|
const syncHelp = `${cliInvocation()} sync --dir ${commandToken(canonical2)}`;
|
|
@@ -134535,8 +135362,9 @@ async function adopt(folderArg, options, deps2, mode) {
|
|
|
134535
135362
|
...result3.conflicts.length > 0 ? { conflicts: listed(result3.conflicts) } : {},
|
|
134536
135363
|
...result3.localOnly.length > 0 ? { local_only: listed(result3.localOnly), local_only_note: "the next sync sends these as new documents, which re-creates any the host deleted since the copy was made; delete any you do not want first" } : {},
|
|
134537
135364
|
root_index: result3.rootIndex,
|
|
135365
|
+
...result3.otherFiles ? { other_files: result3.otherFiles, warning: "files other than documents were not checked against the host" } : {},
|
|
134538
135366
|
...result3.markerWritten ? { marker: "written" } : {},
|
|
134539
|
-
help: result3.conflicts.length > 0 ? [`${cliInvocation()} sync --dir ${commandToken(canonical2)} --inspect --doc ${commandToken(result3.conflicts[0])}`, syncHelp] : [syncHelp, ...
|
|
135367
|
+
help: result3.conflicts.length > 0 ? [`${cliInvocation()} sync --dir ${commandToken(canonical2)} --inspect --doc ${commandToken(result3.conflicts[0])}`, syncHelp] : [syncHelp, ...nextSteps2(canonical2)]
|
|
134540
135368
|
},
|
|
134541
135369
|
mode
|
|
134542
135370
|
)
|
|
@@ -134551,7 +135379,7 @@ async function rebindToWorkspace(canonical2, bound, options, deps2, mode) {
|
|
|
134551
135379
|
}
|
|
134552
135380
|
const target3 = resolveHostedTarget(options.host);
|
|
134553
135381
|
const resume2 = commandFragment`${cliInvocation()} checkout --adopt ${commandToken(canonical2)} --host ${commandToken(bindingHostArgument(target3))} --workspace ${commandToken(options.workspace)}${options.json ? commandFragment` --json` : commandFragment``}`;
|
|
134554
|
-
const connection = await connectHostedBundle({ slug: null, bundleId: bound.bundle_id }, target3, options.workspace, deps2, resume2);
|
|
135382
|
+
const connection = await connectHostedBundle({ slug: null, bundleId: bound.bundle_id }, target3, options.workspace, deps2, resume2, { deadlineMs: EXPORT_DEADLINE_MS });
|
|
134555
135383
|
const slug = connection.reference.slug;
|
|
134556
135384
|
if (slug === null || slug === (bound.workspace_slug ?? null)) return false;
|
|
134557
135385
|
if (connection.identity.principalId !== bound.principal_id) {
|
|
@@ -134600,7 +135428,8 @@ async function rebindToWorkspace(canonical2, bound, options, deps2, mode) {
|
|
|
134600
135428
|
await releaseCheckout(home2, bound);
|
|
134601
135429
|
return pending;
|
|
134602
135430
|
});
|
|
134603
|
-
const
|
|
135431
|
+
const host = await adoptedExtras(canonical2, exportRequestFor(connection, target3, bound.bundle_id, resume2));
|
|
135432
|
+
const result3 = await bindWithExtras({ canonical: canonical2, target: target3, bundleId: bound.bundle_id, connection, deps: deps2, resume: resume2, extras: host.extras }, host);
|
|
134604
135433
|
deps2.stdout(
|
|
134605
135434
|
render(
|
|
134606
135435
|
{
|
|
@@ -134614,7 +135443,7 @@ async function rebindToWorkspace(canonical2, bound, options, deps2, mode) {
|
|
|
134614
135443
|
documents: { placed: result3.placed.length, matched: result3.matched.length, conflicts: result3.conflicts.length, local_only: result3.localOnly.length },
|
|
134615
135444
|
...result3.conflicts.length > 0 ? { conflicts: listed(result3.conflicts) } : {},
|
|
134616
135445
|
...result3.localOnly.length > 0 ? { local_only: listed(result3.localOnly) } : {},
|
|
134617
|
-
help: [`${cliInvocation()} sync --dir ${commandToken(canonical2)}`, ...
|
|
135446
|
+
help: [`${cliInvocation()} sync --dir ${commandToken(canonical2)}`, ...nextSteps2(canonical2)]
|
|
134618
135447
|
},
|
|
134619
135448
|
mode
|
|
134620
135449
|
)
|
|
@@ -134672,7 +135501,7 @@ async function bindFolderInPlace(input) {
|
|
|
134672
135501
|
const hostIds = /* @__PURE__ */ new Set();
|
|
134673
135502
|
for (const head of heads) {
|
|
134674
135503
|
hostIds.add(head.id);
|
|
134675
|
-
const file2 =
|
|
135504
|
+
const file2 = path55.join(canonical2, pathFromConceptId(head.id));
|
|
134676
135505
|
if (await parentUnsafe(canonical2, file2)) {
|
|
134677
135506
|
conflicts.push(head.id);
|
|
134678
135507
|
continue;
|
|
@@ -134706,12 +135535,12 @@ async function bindFolderInPlace(input) {
|
|
|
134706
135535
|
if (index2) {
|
|
134707
135536
|
const hostRoot2 = Buffer.from(index2.content, "utf8");
|
|
134708
135537
|
root = digestOf(hostRoot2);
|
|
134709
|
-
const current = readLeaf(
|
|
135538
|
+
const current = readLeaf(path55.join(canonical2, ROOT_INDEX));
|
|
134710
135539
|
if (current === "unsafe") rootIndex = "kept (not a plain file; sync holds it)";
|
|
134711
135540
|
else if (current === null) {
|
|
134712
|
-
const placedRoot = (await placeNew(
|
|
135541
|
+
const placedRoot = (await placeNew(path55.join(canonical2, ROOT_INDEX), hostRoot2)).placed;
|
|
134713
135542
|
if (placedRoot) {
|
|
134714
|
-
placedFiles.set(
|
|
135543
|
+
placedFiles.set(path55.join(canonical2, ROOT_INDEX), root);
|
|
134715
135544
|
rootBase = index2.version;
|
|
134716
135545
|
}
|
|
134717
135546
|
rootIndex = placedRoot ? "placed" : "kept";
|
|
@@ -134746,6 +135575,25 @@ async function bindFolderInPlace(input) {
|
|
|
134746
135575
|
}
|
|
134747
135576
|
});
|
|
134748
135577
|
}
|
|
135578
|
+
function exportRequestFor(connection, target3, bundleId2, resume2) {
|
|
135579
|
+
const { client, reference } = connection;
|
|
135580
|
+
return {
|
|
135581
|
+
client: reference.slug === null ? client : client.within(reference.slug),
|
|
135582
|
+
slug: reference.slug,
|
|
135583
|
+
source: { target: target3, bundleId: bundleId2, slug: reference.slug, workspace: null, binding: null },
|
|
135584
|
+
resume: resume2
|
|
135585
|
+
};
|
|
135586
|
+
}
|
|
135587
|
+
async function bindWithExtras(input, host) {
|
|
135588
|
+
try {
|
|
135589
|
+
const result3 = await bindFolderInPlace(input);
|
|
135590
|
+
return host.unplaced === null ? result3 : { ...result3, otherFiles: `not recorded: ${host.unplaced}; files other than documents here that differ from nothing on record read as edits until the next checkout` };
|
|
135591
|
+
} catch (error51) {
|
|
135592
|
+
await removePlaced(input.canonical, host.placed, false).catch(() => {
|
|
135593
|
+
});
|
|
135594
|
+
throw error51;
|
|
135595
|
+
}
|
|
135596
|
+
}
|
|
134749
135597
|
var LISTED;
|
|
134750
135598
|
var init_checkout_adopt = __esm({
|
|
134751
135599
|
"../cli/src/commands/checkout-adopt.ts"() {
|
|
@@ -134773,24 +135621,25 @@ var init_checkout_adopt = __esm({
|
|
|
134773
135621
|
init_sync_scan();
|
|
134774
135622
|
init_checkout_store();
|
|
134775
135623
|
init_checkout();
|
|
135624
|
+
init_export();
|
|
134776
135625
|
LISTED = 20;
|
|
134777
135626
|
}
|
|
134778
135627
|
});
|
|
134779
135628
|
|
|
134780
135629
|
// ../cli/src/commands/checkout.ts
|
|
134781
|
-
import { homedir as
|
|
134782
|
-
import { lstat as
|
|
134783
|
-
import
|
|
134784
|
-
import { parseArgs as
|
|
135630
|
+
import { homedir as homedir40 } from "node:os";
|
|
135631
|
+
import { lstat as lstat10, mkdir as mkdir5, readFile as readFile5, readdir as readdir9, realpath as realpath13, rmdir as rmdir3, stat as stat8, unlink as unlink14 } from "node:fs/promises";
|
|
135632
|
+
import path56 from "node:path";
|
|
135633
|
+
import { parseArgs as parseArgs42 } from "node:util";
|
|
134785
135634
|
function checkoutDeps(partial2) {
|
|
134786
135635
|
return {
|
|
134787
135636
|
stdout: partial2.stdout ?? ((text5) => void process.stdout.write(text5)),
|
|
134788
|
-
auth: partial2.auth ?? defaultHostedAuthDeps(
|
|
135637
|
+
auth: partial2.auth ?? defaultHostedAuthDeps(homedir40()),
|
|
134789
135638
|
cwd: partial2.cwd ?? process.cwd(),
|
|
134790
135639
|
...partial2.fetch ? { fetch: partial2.fetch } : {}
|
|
134791
135640
|
};
|
|
134792
135641
|
}
|
|
134793
|
-
async function
|
|
135642
|
+
async function entryKind2(target3) {
|
|
134794
135643
|
let info;
|
|
134795
135644
|
try {
|
|
134796
135645
|
info = await stat8(target3);
|
|
@@ -134799,7 +135648,7 @@ async function entryKind(target3) {
|
|
|
134799
135648
|
throw error51;
|
|
134800
135649
|
}
|
|
134801
135650
|
if (!info.isDirectory()) return "other";
|
|
134802
|
-
return (await
|
|
135651
|
+
return (await readdir9(target3)).length === 0 ? "empty-dir" : "dir";
|
|
134803
135652
|
}
|
|
134804
135653
|
function bindingView(binding) {
|
|
134805
135654
|
return {
|
|
@@ -134812,11 +135661,11 @@ function bindingView(binding) {
|
|
|
134812
135661
|
folder: binding.path
|
|
134813
135662
|
};
|
|
134814
135663
|
}
|
|
134815
|
-
function
|
|
135664
|
+
function nextSteps2(folder) {
|
|
134816
135665
|
return [`${cliInvocation()} list --dir ${commandToken(folder)}`, `${cliInvocation()} status --dir ${commandToken(folder)}`];
|
|
134817
135666
|
}
|
|
134818
135667
|
async function assertStandaloneFolder(folder) {
|
|
134819
|
-
const parent =
|
|
135668
|
+
const parent = path56.dirname(folder);
|
|
134820
135669
|
const enclosing = await findBundleRoot(parent).catch(() => null);
|
|
134821
135670
|
if (enclosing) {
|
|
134822
135671
|
throw new CliError("FORBIDDEN", `refusing to check out inside the local bundle at ${enclosing}`, {
|
|
@@ -134875,17 +135724,17 @@ function lockFailure2(error51, folder) {
|
|
|
134875
135724
|
return error51;
|
|
134876
135725
|
}
|
|
134877
135726
|
async function canonicalFolder(folder) {
|
|
134878
|
-
const parent =
|
|
134879
|
-
await
|
|
135727
|
+
const parent = path56.dirname(folder);
|
|
135728
|
+
await mkdir5(parent, { recursive: true });
|
|
134880
135729
|
let link4 = false;
|
|
134881
135730
|
try {
|
|
134882
|
-
link4 = (await
|
|
135731
|
+
link4 = (await lstat10(folder)).isSymbolicLink();
|
|
134883
135732
|
} catch (error51) {
|
|
134884
135733
|
if (error51.code !== "ENOENT") throw error51;
|
|
134885
135734
|
}
|
|
134886
|
-
if (!link4) return
|
|
135735
|
+
if (!link4) return path56.join(await realpath13(parent), path56.basename(folder));
|
|
134887
135736
|
try {
|
|
134888
|
-
return await
|
|
135737
|
+
return await realpath13(folder);
|
|
134889
135738
|
} catch {
|
|
134890
135739
|
throw new CliError("USAGE", `the --dir folder ${folder} is a symbolic link to a folder that does not exist`, { help: "pass --dir <the real folder>" });
|
|
134891
135740
|
}
|
|
@@ -134916,25 +135765,25 @@ async function removePlaced(folder, placed, createdFolder) {
|
|
|
134916
135765
|
const dirs = /* @__PURE__ */ new Set();
|
|
134917
135766
|
for (const [file2, digest3] of placed) {
|
|
134918
135767
|
try {
|
|
134919
|
-
if (digestOf(await readFile5(file2)) === digest3) await
|
|
135768
|
+
if (digestOf(await readFile5(file2)) === digest3) await unlink14(file2);
|
|
134920
135769
|
} catch {
|
|
134921
135770
|
}
|
|
134922
|
-
for (let dir =
|
|
135771
|
+
for (let dir = path56.dirname(file2); dir !== folder && dir.startsWith(`${folder}${path56.sep}`); dir = path56.dirname(dir)) dirs.add(dir);
|
|
134923
135772
|
}
|
|
134924
135773
|
for (const dir of [...dirs].sort((a, b) => b.length - a.length)) await rmdir3(dir).catch(() => {
|
|
134925
135774
|
});
|
|
134926
135775
|
if (createdFolder) await rmdir3(folder).catch(() => {
|
|
134927
135776
|
});
|
|
134928
|
-
return await
|
|
135777
|
+
return await entryKind2(folder) !== "dir";
|
|
134929
135778
|
}
|
|
134930
135779
|
async function release(folderArg, deps2, mode) {
|
|
134931
|
-
const folder =
|
|
135780
|
+
const folder = path56.resolve(deps2.cwd, folderArg);
|
|
134932
135781
|
let canonical2;
|
|
134933
135782
|
try {
|
|
134934
|
-
canonical2 = (await
|
|
135783
|
+
canonical2 = (await lstat10(folder)).isSymbolicLink() ? await realpath13(folder) : path56.join(await realpath13(path56.dirname(folder)), path56.basename(folder));
|
|
134935
135784
|
} catch {
|
|
134936
135785
|
try {
|
|
134937
|
-
canonical2 =
|
|
135786
|
+
canonical2 = path56.join(await realpath13(path56.dirname(folder)), path56.basename(folder));
|
|
134938
135787
|
} catch {
|
|
134939
135788
|
canonical2 = folder;
|
|
134940
135789
|
}
|
|
@@ -134971,7 +135820,7 @@ async function release(folderArg, deps2, mode) {
|
|
|
134971
135820
|
}
|
|
134972
135821
|
async function checkoutHost(flag, folder, typed, values, home2, env, fetchImpl) {
|
|
134973
135822
|
if (flag === void 0) {
|
|
134974
|
-
const real = await
|
|
135823
|
+
const real = await realpath13(folder).catch(() => null);
|
|
134975
135824
|
const bound = real ? await bindingForPath(home2, real).catch(() => null) : null;
|
|
134976
135825
|
if (bound) return { target: checkoutTarget(bound), workspace: values.workspace, origin: {} };
|
|
134977
135826
|
}
|
|
@@ -134985,7 +135834,7 @@ async function checkoutHost(flag, folder, typed, values, home2, env, fetchImpl)
|
|
|
134985
135834
|
async function checkout(argv2, partial2 = {}) {
|
|
134986
135835
|
const deps2 = checkoutDeps(partial2);
|
|
134987
135836
|
const { values, positionals } = parseLeafOrUsage(
|
|
134988
|
-
() =>
|
|
135837
|
+
() => parseArgs42({
|
|
134989
135838
|
args: argv2,
|
|
134990
135839
|
options: {
|
|
134991
135840
|
host: { type: "string" },
|
|
@@ -135027,21 +135876,21 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135027
135876
|
throw new CliError("USAGE", `'${positionals[0]}' is not a hosted bundle id or <workspace>/<bundle-id>`, { help: `${cliInvocation()} checkout --help` });
|
|
135028
135877
|
}
|
|
135029
135878
|
const bundleId2 = typed.bundleId;
|
|
135030
|
-
const folder =
|
|
135879
|
+
const folder = path56.resolve(deps2.cwd, values.dir ?? bundleId2);
|
|
135031
135880
|
assertBundleOutsidePrivateState(folder, deps2.auth.home);
|
|
135032
135881
|
const { target: target3, workspace: workspaceFlag2, origin: hostOrigin } = await checkoutHost(values.host, folder, typed, values, deps2.auth.home, deps2.auth.env, deps2.auth.fetch);
|
|
135033
135882
|
const prefix = syncRoutePrefix(target3);
|
|
135034
135883
|
const resume2 = commandFragment`${cliInvocation()} checkout ${commandToken(hostedBundleReferenceText(typed))} --host ${commandToken(hostArgument(target3))} --dir ${commandToken(folder)}${workspaceFlag2 !== void 0 ? commandFragment` --workspace ${commandToken(workspaceFlag2)}` : commandFragment``}${values.json ? commandFragment` --json` : commandFragment``}`;
|
|
135035
|
-
const found = await
|
|
135884
|
+
const found = await entryKind2(folder);
|
|
135036
135885
|
if (found === "other") {
|
|
135037
135886
|
throw new CliError("ALREADY_EXISTS", `${folder} exists and is not a folder`, { help: "pass --dir <new folder>" });
|
|
135038
135887
|
}
|
|
135039
135888
|
if (found === "dir") {
|
|
135040
|
-
const existing = await bindingForPath(deps2.auth.home, await
|
|
135889
|
+
const existing = await bindingForPath(deps2.auth.home, await realpath13(folder));
|
|
135041
135890
|
const sameWorkspace = workspaceFlag2 === void 0 || workspaceFlag2 === existing?.workspace_slug || workspaceFlag2 === existing?.workspace;
|
|
135042
135891
|
const asked = typed.slug === null && sameWorkspace ? { slug: existing?.workspace_slug ?? null, bundleId: bundleId2 } : typed;
|
|
135043
135892
|
if (existing && bindsBundle(existing, target3, asked)) {
|
|
135044
|
-
deps2.stdout(render({ checkout: "unchanged", ...bindingView(existing), help:
|
|
135893
|
+
deps2.stdout(render({ checkout: "unchanged", ...bindingView(existing), help: nextSteps2(existing.path) }, mode));
|
|
135045
135894
|
return;
|
|
135046
135895
|
}
|
|
135047
135896
|
const marker = existing ? null : readCheckoutMarker(folder);
|
|
@@ -135052,7 +135901,7 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135052
135901
|
});
|
|
135053
135902
|
}
|
|
135054
135903
|
await assertStandaloneFolder(folder);
|
|
135055
|
-
const { identity, reader, listed: listed2, workspace, reference, definitionWrites, documentInputBytes } = await connectHostedBundle(typed, target3, workspaceFlag2, deps2, resume2);
|
|
135904
|
+
const { identity, reader, listed: listed2, workspace, reference, client, definitionWrites, documentInputBytes } = await connectHostedBundle(typed, target3, workspaceFlag2, deps2, resume2, { deadlineMs: EXPORT_DEADLINE_MS });
|
|
135056
135905
|
const canonical2 = await canonicalFolder(folder);
|
|
135057
135906
|
let createdFolder = false;
|
|
135058
135907
|
const placed = /* @__PURE__ */ new Map();
|
|
@@ -135064,7 +135913,7 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135064
135913
|
help: "wait for it to finish, then retry the same command"
|
|
135065
135914
|
});
|
|
135066
135915
|
}
|
|
135067
|
-
const kind2 = await
|
|
135916
|
+
const kind2 = await entryKind2(canonical2);
|
|
135068
135917
|
if (kind2 === "dir" || kind2 === "other") {
|
|
135069
135918
|
throw new CliError("ALREADY_EXISTS", `${canonical2} is no longer empty`, { details: { reason: "not_empty", folder: canonical2 }, help: "pass --dir <new or empty folder>" });
|
|
135070
135919
|
}
|
|
@@ -135074,7 +135923,7 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135074
135923
|
await releaseCheckout(deps2.auth.home, replaced);
|
|
135075
135924
|
}
|
|
135076
135925
|
if (kind2 === "absent") {
|
|
135077
|
-
await
|
|
135926
|
+
await mkdir5(canonical2);
|
|
135078
135927
|
createdFolder = true;
|
|
135079
135928
|
}
|
|
135080
135929
|
const identityNow = await folderIdentity(canonical2);
|
|
@@ -135109,9 +135958,12 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135109
135958
|
const stored = await store.readHeads({ project: (head) => head.id });
|
|
135110
135959
|
assertProjectable(stored, bundleId2, target3);
|
|
135111
135960
|
const exported = await exportFresh(store, canonical2, (file2, digest3) => placed.set(file2, digest3));
|
|
135112
|
-
|
|
135961
|
+
const portable = await exportOrReason({ client: reference.slug === null ? client : client.within(reference.slug), slug: reference.slug, source: { target: target3, bundleId: bundleId2, slug: reference.slug, workspace: workspaceFlag2 ?? null, binding: null }, resume: resume2 });
|
|
135962
|
+
const extras = typeof portable === "string" ? NO_EXTRAS : await placeExtras(portable, canonical2, (file2, digest3) => placed.set(file2, digest3));
|
|
135963
|
+
const kept = [...exported.kept, ...extras.kept];
|
|
135964
|
+
if (kept.length > 0) {
|
|
135113
135965
|
throw new CliError("CONFLICT", `files appeared in ${canonical2} during checkout`, {
|
|
135114
|
-
details: { reason: "folder_changed", kept:
|
|
135966
|
+
details: { reason: "folder_changed", kept: kept.slice(0, 20), kept_total: kept.length },
|
|
135115
135967
|
help: "retry the same command once nothing else writes to the folder"
|
|
135116
135968
|
});
|
|
135117
135969
|
}
|
|
@@ -135122,14 +135974,14 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135122
135974
|
if (id !== ROOT_INDEX && version4) files[id] = { digest: digest3, version: version4 };
|
|
135123
135975
|
}
|
|
135124
135976
|
const rootBase = exported.exported[ROOT_INDEX] !== void 0 ? (await store.readReserved("", "index.md"))?.version ?? null : null;
|
|
135125
|
-
await writeProjection(deps2.auth.home, binding.checkout_id, { files, root: exported.exported[ROOT_INDEX] ?? null, rootBase });
|
|
135977
|
+
await writeProjection(deps2.auth.home, binding.checkout_id, { files, root: exported.exported[ROOT_INDEX] ?? null, rootBase, extras: extras.placed });
|
|
135126
135978
|
const markerFile = await writeCheckoutMarker(canonical2, binding);
|
|
135127
135979
|
if (markerFile) placed.set(markerFile, digestOf(checkoutMarkerBytes(binding)));
|
|
135128
135980
|
const ready = { ...binding, state: "ready" };
|
|
135129
135981
|
await writeBinding(deps2.auth.home, ready);
|
|
135130
135982
|
await recordPulled(deps2.auth.home, ready.checkout_id);
|
|
135131
135983
|
await indexCheckoutPath(deps2.auth.home, ready);
|
|
135132
|
-
return { binding: ready, documents: exported.documents, root: exported.root, digest: marker.headsDigest ?? null };
|
|
135984
|
+
return { binding: ready, documents: exported.documents, root: exported.root, unplaced: typeof portable === "string" ? portable : null, reserved: extras.reserved, other: extras.other, digest: marker.headsDigest ?? null };
|
|
135133
135985
|
} catch (error51) {
|
|
135134
135986
|
await store?.close().catch(() => {
|
|
135135
135987
|
});
|
|
@@ -135162,22 +136014,96 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135162
136014
|
catalog: cataloged,
|
|
135163
136015
|
documents: result3.documents,
|
|
135164
136016
|
root_index: result3.root,
|
|
136017
|
+
...result3.unplaced === null ? { reserved_files: result3.reserved, other_files: result3.other } : {
|
|
136018
|
+
other_files: `not placed: ${result3.unplaced}, so reserved files below the root and files other than documents stay on the host; superbee export copies them`,
|
|
136019
|
+
warning: "this checkout lacks the host's files other than documents, and sync never brings them: check out again into a new folder to get them"
|
|
136020
|
+
},
|
|
135165
136021
|
heads_digest: result3.digest,
|
|
135166
136022
|
...replacedBinding ? { replaced_stale_checkout: { bundle_id: replacedBinding.bundle_id, host: replacedBinding.origin } } : {},
|
|
135167
136023
|
mode: "sync",
|
|
135168
136024
|
refused: REFUSED_SUMMARY,
|
|
135169
|
-
help:
|
|
136025
|
+
help: nextSteps2(result3.binding.path)
|
|
135170
136026
|
},
|
|
135171
136027
|
mode
|
|
135172
136028
|
)
|
|
135173
136029
|
);
|
|
135174
136030
|
}
|
|
135175
|
-
async function
|
|
136031
|
+
async function exportOrReason(request3) {
|
|
136032
|
+
try {
|
|
136033
|
+
return await fetchExportThrough(request3);
|
|
136034
|
+
} catch (error51) {
|
|
136035
|
+
if (!(error51 instanceof CliError)) throw error51;
|
|
136036
|
+
if (error51.details?.code === "not_found" && error51.details?.status === 404) return `${request3.source.target.origin} serves no bundle export`;
|
|
136037
|
+
const reason = error51.details?.reason ?? error51.details?.code;
|
|
136038
|
+
return `the bundle export could not be read: ${error51.message.replace(/; nothing was written$/, "")}${typeof reason === "string" ? ` (${reason})` : ""}`;
|
|
136039
|
+
}
|
|
136040
|
+
}
|
|
136041
|
+
async function adoptedExtras(folder, request3) {
|
|
136042
|
+
const portable = await exportOrReason(request3);
|
|
136043
|
+
if (typeof portable === "string") return { extras: {}, placed: /* @__PURE__ */ new Map(), unplaced: portable };
|
|
136044
|
+
const extras = {};
|
|
136045
|
+
const placed = /* @__PURE__ */ new Map();
|
|
136046
|
+
for (const entry of portable.entries) {
|
|
136047
|
+
if (entry.kind === "document" || entry.path === ROOT_INDEX) continue;
|
|
136048
|
+
const file2 = path56.join(folder, ...entry.path.split("/"));
|
|
136049
|
+
if (await parentUnsafe(folder, file2)) continue;
|
|
136050
|
+
const found = readRegularFileNoFollowSync(file2);
|
|
136051
|
+
if (found.state === "missing") {
|
|
136052
|
+
try {
|
|
136053
|
+
await ensureParentInside(folder, file2);
|
|
136054
|
+
} catch (error51) {
|
|
136055
|
+
if (error51 instanceof UnsafePlacementError) continue;
|
|
136056
|
+
throw error51;
|
|
136057
|
+
}
|
|
136058
|
+
if ((await placeNew(file2, entry.bytes)).placed) {
|
|
136059
|
+
extras[entry.path] = digestOf(entry.bytes);
|
|
136060
|
+
placed.set(file2, digestOf(entry.bytes));
|
|
136061
|
+
}
|
|
136062
|
+
continue;
|
|
136063
|
+
}
|
|
136064
|
+
if (found.state === "present" && Buffer.from(entry.bytes).equals(found.bytes)) extras[entry.path] = digestOf(entry.bytes);
|
|
136065
|
+
}
|
|
136066
|
+
return { extras, placed, unplaced: null };
|
|
136067
|
+
}
|
|
136068
|
+
async function placeExtras(exported, folder, onPlaced) {
|
|
136069
|
+
const placed = {};
|
|
136070
|
+
const kept = [];
|
|
136071
|
+
let reserved = 0;
|
|
136072
|
+
let other = 0;
|
|
136073
|
+
for (const entry of exported.entries) {
|
|
136074
|
+
if (entry.kind === "document" || entry.path === ROOT_INDEX) continue;
|
|
136075
|
+
const file2 = path56.join(folder, ...entry.path.split("/"));
|
|
136076
|
+
try {
|
|
136077
|
+
await ensureParentInside(folder, file2);
|
|
136078
|
+
} catch (error51) {
|
|
136079
|
+
if (!(error51 instanceof UnsafePlacementError)) throw error51;
|
|
136080
|
+
kept.push(entry.path);
|
|
136081
|
+
continue;
|
|
136082
|
+
}
|
|
136083
|
+
const outcome = await placeNew(file2, entry.bytes);
|
|
136084
|
+
if (!outcome.placed) {
|
|
136085
|
+
kept.push(entry.path);
|
|
136086
|
+
continue;
|
|
136087
|
+
}
|
|
136088
|
+
const digest3 = digestOf(entry.bytes);
|
|
136089
|
+
onPlaced(file2, digest3);
|
|
136090
|
+
placed[entry.path] = digest3;
|
|
136091
|
+
if (entry.kind === "reserved") reserved += 1;
|
|
136092
|
+
else other += 1;
|
|
136093
|
+
}
|
|
136094
|
+
return { placed, reserved, other, kept };
|
|
136095
|
+
}
|
|
136096
|
+
async function connectHostedBundle(typed, target3, workspaceFlag2, deps2, resume2, options = {}) {
|
|
135176
136097
|
const { bundleId: bundleId2 } = typed;
|
|
135177
136098
|
const command = (named) => `${cliInvocation()} checkout ${commandToken(named)} --host ${commandToken(hostArgument(target3))}`;
|
|
135178
136099
|
const account = await connectHostedAccount(
|
|
135179
136100
|
target3,
|
|
135180
|
-
{
|
|
136101
|
+
{
|
|
136102
|
+
workspace: workspaceFlag2,
|
|
136103
|
+
resume: resume2,
|
|
136104
|
+
otherWorkspace: `${command(hostedBundleReferenceText(typed))} --workspace <id>`,
|
|
136105
|
+
...options.deadlineMs !== void 0 ? { deadlineMs: options.deadlineMs } : {}
|
|
136106
|
+
},
|
|
135181
136107
|
deps2
|
|
135182
136108
|
);
|
|
135183
136109
|
const { client, identity, workspace } = account;
|
|
@@ -135261,7 +136187,7 @@ function assertProjectable(ids, bundleId2, target3) {
|
|
|
135261
136187
|
});
|
|
135262
136188
|
}
|
|
135263
136189
|
}
|
|
135264
|
-
var CHECKOUT_DOCUMENT_LIMIT, CHECKOUT_USAGE, REFUSED_SUMMARY;
|
|
136190
|
+
var CHECKOUT_DOCUMENT_LIMIT, CHECKOUT_USAGE, NO_EXTRAS, REFUSED_SUMMARY;
|
|
135265
136191
|
var init_checkout = __esm({
|
|
135266
136192
|
"../cli/src/commands/checkout.ts"() {
|
|
135267
136193
|
"use strict";
|
|
@@ -135279,6 +136205,7 @@ var init_checkout = __esm({
|
|
|
135279
136205
|
init_invocation();
|
|
135280
136206
|
init_output2();
|
|
135281
136207
|
init_private_state_bundle_boundary();
|
|
136208
|
+
init_nofollow_read();
|
|
135282
136209
|
init_session();
|
|
135283
136210
|
init_account();
|
|
135284
136211
|
init_freshness2();
|
|
@@ -135290,6 +136217,7 @@ var init_checkout = __esm({
|
|
|
135290
136217
|
init_catalog();
|
|
135291
136218
|
init_marker();
|
|
135292
136219
|
init_checkout_adopt();
|
|
136220
|
+
init_export();
|
|
135293
136221
|
init_reference();
|
|
135294
136222
|
CHECKOUT_DOCUMENT_LIMIT = 1e4;
|
|
135295
136223
|
CHECKOUT_USAGE = `superbee checkout \u2014 mirror a hosted bundle into a local folder
|
|
@@ -135305,8 +136233,11 @@ hosted bundle into --dir (default: ./<bundle-id>), which must be new or empty. T
|
|
|
135305
136233
|
else, for a folder that is already a checkout, its own host, else the host of your last sign-in
|
|
135306
136234
|
when it is the only host you are signed in to (never SUPERBEE_HOST alone); signed in to more than
|
|
135307
136235
|
one host, a checkout without --host is refused and names them. The receipt names the host it
|
|
135308
|
-
bound. The folder holds plain bundle files, so every command runs on it with --dir <folder
|
|
135309
|
-
|
|
136236
|
+
bound. The folder holds plain bundle files, so every command runs on it with --dir <folder>: the
|
|
136237
|
+
same files 'superbee export' writes (documents, every index.md and log.md, and other files such as
|
|
136238
|
+
data/*.json), with the bytes the host stores. Sync sends documents only; it leaves the other files
|
|
136239
|
+
be while they are as placed. The link to the host is kept in private state, keyed by the folder's
|
|
136240
|
+
path, never in the folder.
|
|
135310
136241
|
Re-running for the same folder and bundle is a no-op. A checkout whose folder was deleted, or
|
|
135311
136242
|
replaced by a new empty folder, is replaced, unless it holds changes sync has not sent yet; a
|
|
135312
136243
|
checkout emptied in place is refused, because removing every file is a pending edit.
|
|
@@ -135364,6 +136295,7 @@ Examples:
|
|
|
135364
136295
|
superbee checkout --adopt ~/restored/team --host https://mcp.getsuperbee.com
|
|
135365
136296
|
superbee checkout --release ~/work/team
|
|
135366
136297
|
`;
|
|
136298
|
+
NO_EXTRAS = Object.freeze({ placed: {}, reserved: 0, other: 0, kept: [] });
|
|
135367
136299
|
REFUSED_SUMMARY = Object.freeze(
|
|
135368
136300
|
HOSTED_CHECKOUT_REFUSALS.filter((row4) => row4.reason !== "checkout_target").map((row4) => {
|
|
135369
136301
|
const words = row4.words.join(" ");
|
|
@@ -135373,800 +136305,6 @@ Examples:
|
|
|
135373
136305
|
}
|
|
135374
136306
|
});
|
|
135375
136307
|
|
|
135376
|
-
// ../cli/src/commands/export.ts
|
|
135377
|
-
import { randomBytes as randomBytes9 } from "node:crypto";
|
|
135378
|
-
import { homedir as homedir40 } from "node:os";
|
|
135379
|
-
import { link as link3, lstat as lstat10, mkdir as mkdir5, open as open4, readdir as readdir9, realpath as realpath13, rename as rename2, rm as rm2, unlink as unlink14 } from "node:fs/promises";
|
|
135380
|
-
import path56 from "node:path";
|
|
135381
|
-
import { parseArgs as parseArgs42 } from "node:util";
|
|
135382
|
-
function exportDeps(partial2) {
|
|
135383
|
-
return {
|
|
135384
|
-
stdout: partial2.stdout ?? ((text5) => void process.stdout.write(text5)),
|
|
135385
|
-
auth: partial2.auth ?? defaultHostedAuthDeps(homedir40()),
|
|
135386
|
-
cwd: partial2.cwd ?? process.cwd(),
|
|
135387
|
-
...partial2.fetch ? { fetch: partial2.fetch } : {}
|
|
135388
|
-
};
|
|
135389
|
-
}
|
|
135390
|
-
async function entryKind2(target3) {
|
|
135391
|
-
let info;
|
|
135392
|
-
try {
|
|
135393
|
-
info = await lstat10(target3);
|
|
135394
|
-
} catch (error51) {
|
|
135395
|
-
if (error51.code === "ENOENT") return "absent";
|
|
135396
|
-
throw error51;
|
|
135397
|
-
}
|
|
135398
|
-
if (!info.isDirectory()) return "other";
|
|
135399
|
-
return (await readdir9(target3)).length === 0 ? "empty-dir" : "dir";
|
|
135400
|
-
}
|
|
135401
|
-
function shown(paths) {
|
|
135402
|
-
return [...paths].sort().slice(0, SHOWN);
|
|
135403
|
-
}
|
|
135404
|
-
function refusalCode(body) {
|
|
135405
|
-
const error51 = body?.error;
|
|
135406
|
-
if (typeof error51 === "string") return error51;
|
|
135407
|
-
const code3 = error51?.code;
|
|
135408
|
-
return typeof code3 === "string" ? code3 : void 0;
|
|
135409
|
-
}
|
|
135410
|
-
async function namedClient(source, deps2, resume2) {
|
|
135411
|
-
const { target: target3 } = source;
|
|
135412
|
-
const account = await connectHostedAccount(target3, { ...source.workspace !== null ? { workspace: source.workspace } : {}, resume: resume2, deadlineMs: EXPORT_DEADLINE_MS }, deps2);
|
|
135413
|
-
const { reference } = resolveBundleReference(
|
|
135414
|
-
{ slug: source.slug, bundleId: source.bundleId },
|
|
135415
|
-
account,
|
|
135416
|
-
target3,
|
|
135417
|
-
(named) => `${cliInvocation()} export ${commandToken(named)} --host ${commandToken(hostArgument(target3))} --to <folder>`
|
|
135418
|
-
);
|
|
135419
|
-
return { client: reference.slug === null ? account.client : account.client.within(reference.slug), slug: reference.slug };
|
|
135420
|
-
}
|
|
135421
|
-
async function fetchExport(source, deps2, resume2) {
|
|
135422
|
-
const { bundleId: bundleId2, target: target3 } = source;
|
|
135423
|
-
const named = source.binding ? null : await namedClient(source, deps2, resume2);
|
|
135424
|
-
const client = named ? named.client : (await connectCheckout(source.binding, { resume: resume2, deadlineMs: EXPORT_DEADLINE_MS }, deps2)).client;
|
|
135425
|
-
const slug = named ? named.slug : source.binding.workspace_slug ?? null;
|
|
135426
|
-
const request3 = { client, slug, source, resume: resume2 };
|
|
135427
|
-
for (let attempt = 1; ; attempt += 1) {
|
|
135428
|
-
const chain = new ExportPageChain();
|
|
135429
|
-
let bytes = 0;
|
|
135430
|
-
let cursor;
|
|
135431
|
-
let moved = false;
|
|
135432
|
-
for (; ; ) {
|
|
135433
|
-
let page;
|
|
135434
|
-
for (let tries = 1; page === void 0; tries += 1) {
|
|
135435
|
-
const answer2 = await exportAnswer(request3, { bundleId: bundleId2, paged: true, ...cursor !== void 0 ? { cursor } : {} });
|
|
135436
|
-
if (answer2.refused === "invalid_input" && cursor === void 0 && attempt === 1) return verified(await readArchive(await wholeAnswer(request3), source, MAX_EXPORT_BYTES), source);
|
|
135437
|
-
if (answer2.refused === "concurrent_change") {
|
|
135438
|
-
moved = true;
|
|
135439
|
-
break;
|
|
135440
|
-
}
|
|
135441
|
-
try {
|
|
135442
|
-
if (answer2.refused !== void 0) throw await exportRefusal(request3, answer2.refused, answer2.status);
|
|
135443
|
-
page = verified(await readArchive(answer2.ok, source, MAX_EXPORT_BYTES - bytes), source, { paged: true });
|
|
135444
|
-
} catch (error51) {
|
|
135445
|
-
const again = error51 instanceof CliError && (error51.details?.reason === "export_incomplete" || answer2.refused === "backend_unavailable" && error51.code === "TRANSIENT");
|
|
135446
|
-
if (!again || tries >= 2) throw error51;
|
|
135447
|
-
await pause2(PAGE_RETRY_MS);
|
|
135448
|
-
}
|
|
135449
|
-
}
|
|
135450
|
-
if (moved) break;
|
|
135451
|
-
bytes += page.bytes;
|
|
135452
|
-
cursor = joining(source, () => chain.add(page));
|
|
135453
|
-
if (cursor === void 0) return joining(source, () => chain.joined());
|
|
135454
|
-
}
|
|
135455
|
-
if (attempt >= PAGED_EXPORT_ATTEMPTS) {
|
|
135456
|
-
throw new CliError("TRANSIENT", `'${bundleId2}' kept changing on ${target3.origin} while it was exported; nothing was written`, {
|
|
135457
|
-
details: { reason: "export_source_changed", bundle_id: bundleId2, host: target3.origin, retryable: true },
|
|
135458
|
-
help: "retry the same command once the bundle is not being edited"
|
|
135459
|
-
});
|
|
135460
|
-
}
|
|
135461
|
-
const base = PAGED_EXPORT_RESTART_MS[attempt - 1];
|
|
135462
|
-
await pause2(Math.round(base / 2 + Math.random() * base / 2));
|
|
135463
|
-
}
|
|
135464
|
-
}
|
|
135465
|
-
async function exportAnswer({ client, source, resume: resume2 }, input) {
|
|
135466
|
-
let answer2;
|
|
135467
|
-
try {
|
|
135468
|
-
answer2 = await client.carrier.stream(`${client.prefix}/export`, input, client.signal);
|
|
135469
|
-
} catch (error51) {
|
|
135470
|
-
throw hostedFailure(error51, source.target, resume2);
|
|
135471
|
-
}
|
|
135472
|
-
if (!answer2.ok) return { refused: refusalCode(answer2.body) ?? "RUNTIME", status: answer2.status };
|
|
135473
|
-
return { ok: answer2.body };
|
|
135474
|
-
}
|
|
135475
|
-
async function wholeAnswer(request3) {
|
|
135476
|
-
const answer2 = await exportAnswer(request3, { bundleId: request3.source.bundleId });
|
|
135477
|
-
if (answer2.refused !== void 0) throw await exportRefusal(request3, answer2.refused, answer2.status);
|
|
135478
|
-
return answer2.ok;
|
|
135479
|
-
}
|
|
135480
|
-
function exportTooLarge(source) {
|
|
135481
|
-
return new CliError("RUNTIME", `${source.target.origin} answered an export larger than ${MAX_EXPORT_BYTES} bytes`, {
|
|
135482
|
-
details: { reason: "export_too_large", host: source.target.origin, bundle_id: source.bundleId, retryable: false, next: "the person exports it with Export bundle in the Superbee app" },
|
|
135483
|
-
help: `${cliInvocation()} whoami --host ${commandToken(hostArgument(source.target))}`
|
|
135484
|
-
});
|
|
135485
|
-
}
|
|
135486
|
-
async function exportRefusal({ client, slug, source, resume: resume2 }, code3, status2) {
|
|
135487
|
-
const { target: target3, bundleId: bundleId2 } = source;
|
|
135488
|
-
if (code3 === "bundle_not_found") {
|
|
135489
|
-
if (source.binding) return await bundleAbsent(source.binding, client);
|
|
135490
|
-
const text5 = hostedBundleReferenceText({ slug, bundleId: bundleId2 });
|
|
135491
|
-
return new CliError("NOT_FOUND", `no hosted bundle '${text5}' is visible to you on ${target3.origin}`, {
|
|
135492
|
-
details: { bundle_id: bundleId2, ...slug !== null ? { reference: text5 } : {}, host: target3.origin },
|
|
135493
|
-
help: hostedListCommand(target3)
|
|
135494
|
-
});
|
|
135495
|
-
}
|
|
135496
|
-
if (code3 === "result_too_large") {
|
|
135497
|
-
return new CliError("FORBIDDEN", `hosted bundle '${bundleId2}' is larger than an export carries`, {
|
|
135498
|
-
details: { reason: "bundle_too_large", bundle_id: bundleId2, host: target3.origin, next: "the person exports it with Export bundle in the Superbee app" },
|
|
135499
|
-
help: `${cliInvocation()} whoami --host ${commandToken(hostArgument(target3))}`
|
|
135500
|
-
});
|
|
135501
|
-
}
|
|
135502
|
-
if (code3 === "validation_failed") {
|
|
135503
|
-
return new CliError("FORBIDDEN", `hosted bundle '${bundleId2}' holds a path an export cannot carry`, {
|
|
135504
|
-
details: { reason: "unexportable_path", bundle_id: bundleId2, host: target3.origin, next: "the person renames the document or file in the Superbee app, then the same command is retried" },
|
|
135505
|
-
help: `${cliInvocation()} whoami --host ${commandToken(hostArgument(target3))}`
|
|
135506
|
-
});
|
|
135507
|
-
}
|
|
135508
|
-
return hostedFailure(new RemoteError(`hosted export answered ${status2}`, code3, status2), target3, resume2);
|
|
135509
|
-
}
|
|
135510
|
-
async function readArchive(body, source, budget) {
|
|
135511
|
-
const { target: target3, bundleId: bundleId2 } = source;
|
|
135512
|
-
const reader = body.getReader();
|
|
135513
|
-
const chunks = [];
|
|
135514
|
-
let size = 0;
|
|
135515
|
-
try {
|
|
135516
|
-
for (; ; ) {
|
|
135517
|
-
const part = await reader.read();
|
|
135518
|
-
if (part.done) break;
|
|
135519
|
-
size += part.value.byteLength;
|
|
135520
|
-
if (size > budget) throw exportTooLarge(source);
|
|
135521
|
-
chunks.push(part.value);
|
|
135522
|
-
}
|
|
135523
|
-
} catch (error51) {
|
|
135524
|
-
if (error51 instanceof CliError) throw error51;
|
|
135525
|
-
throw new CliError("TRANSIENT", `the export of '${bundleId2}' from ${target3.origin} stopped part way; nothing was written`, {
|
|
135526
|
-
details: { reason: "export_incomplete", bundle_id: bundleId2, host: target3.origin, retryable: true },
|
|
135527
|
-
help: "retry the same command"
|
|
135528
|
-
});
|
|
135529
|
-
} finally {
|
|
135530
|
-
void reader.cancel().catch(() => {
|
|
135531
|
-
});
|
|
135532
|
-
}
|
|
135533
|
-
const archive = new Uint8Array(size);
|
|
135534
|
-
let offset = 0;
|
|
135535
|
-
for (const chunk of chunks) {
|
|
135536
|
-
archive.set(chunk, offset);
|
|
135537
|
-
offset += chunk.byteLength;
|
|
135538
|
-
}
|
|
135539
|
-
return archive;
|
|
135540
|
-
}
|
|
135541
|
-
function verified(archive, source, options = {}) {
|
|
135542
|
-
return refusingArchive(source, () => verifyExport(archive, source.bundleId, options));
|
|
135543
|
-
}
|
|
135544
|
-
function joining(source, join19) {
|
|
135545
|
-
return refusingArchive(source, join19);
|
|
135546
|
-
}
|
|
135547
|
-
function refusingArchive(source, verify) {
|
|
135548
|
-
try {
|
|
135549
|
-
return verify();
|
|
135550
|
-
} catch (error51) {
|
|
135551
|
-
if (!(error51 instanceof ExportArchiveError)) throw error51;
|
|
135552
|
-
if (error51.problem === "incomplete") {
|
|
135553
|
-
throw new CliError("TRANSIENT", `the export of '${source.bundleId}' was not completed by the host; nothing was written`, {
|
|
135554
|
-
details: { reason: "export_incomplete", bundle_id: source.bundleId, host: source.target.origin, retryable: true },
|
|
135555
|
-
help: "retry the same command"
|
|
135556
|
-
});
|
|
135557
|
-
}
|
|
135558
|
-
throw new CliError("RUNTIME", `${source.target.origin} answered an export this CLI refuses (${error51.message}); nothing was written`, {
|
|
135559
|
-
details: { reason: `export_${error51.problem}`, bundle_id: source.bundleId, host: source.target.origin, retryable: false },
|
|
135560
|
-
help: "upgrade Superbee (npm install -g superbee); if it persists, report this bundle and reason"
|
|
135561
|
-
});
|
|
135562
|
-
}
|
|
135563
|
-
}
|
|
135564
|
-
async function writeNewFile(file2, bytes) {
|
|
135565
|
-
const handle = await open4(file2, "wx", 420);
|
|
135566
|
-
try {
|
|
135567
|
-
await handle.writeFile(bytes);
|
|
135568
|
-
await handle.sync();
|
|
135569
|
-
} finally {
|
|
135570
|
-
await handle.close();
|
|
135571
|
-
}
|
|
135572
|
-
}
|
|
135573
|
-
async function writeEntries(root, entries) {
|
|
135574
|
-
for (const entry of entries) {
|
|
135575
|
-
const file2 = path56.join(root, ...entry.path.split("/"));
|
|
135576
|
-
await mkdir5(path56.dirname(file2), { recursive: true });
|
|
135577
|
-
await writeNewFile(file2, entry.bytes);
|
|
135578
|
-
}
|
|
135579
|
-
}
|
|
135580
|
-
function gitRun(dir, args) {
|
|
135581
|
-
let result3;
|
|
135582
|
-
try {
|
|
135583
|
-
result3 = runGit(dir, args);
|
|
135584
|
-
} catch (error51) {
|
|
135585
|
-
const missing2 = error51.code === "GIT_MISSING";
|
|
135586
|
-
throw new CliError(missing2 ? "GIT_MISSING" : "RUNTIME", `git could not run (${error51 instanceof Error ? error51.message : String(error51)})`, {
|
|
135587
|
-
details: { reason: missing2 ? "git_missing" : "git_unavailable" },
|
|
135588
|
-
help: "install git, or export without --git"
|
|
135589
|
-
});
|
|
135590
|
-
}
|
|
135591
|
-
if (result3.status !== 0) {
|
|
135592
|
-
throw new CliError("RUNTIME", `git ${args.find((arg) => !arg.startsWith("-") && !arg.includes("=")) ?? ""} failed: ${result3.stderr.trim().split("\n").at(-1) ?? ""}`, {
|
|
135593
|
-
details: { reason: "git_failed" },
|
|
135594
|
-
help: "fix the git error, then retry the same command"
|
|
135595
|
-
});
|
|
135596
|
-
}
|
|
135597
|
-
return result3.stdout.trim();
|
|
135598
|
-
}
|
|
135599
|
-
function commitExport(dir, message, options) {
|
|
135600
|
-
const quiet = ["-c", "core.hooksPath=/dev/null"];
|
|
135601
|
-
gitRun(dir, [...quiet, "init", "-q"]);
|
|
135602
|
-
gitRun(dir, ["symbolic-ref", "HEAD", `refs/heads/${BOARD_BRANCH}`]);
|
|
135603
|
-
const excluded = ["--", ".", `:(exclude)${IN_PLACE_STAGING}`, `:(exclude)${CHECKOUT_MARKER_DIR}`];
|
|
135604
|
-
gitRun(dir, ["add", "-A", ...options.force ? ["--force"] : [], ...excluded]);
|
|
135605
|
-
gitRun(dir, [...quiet, ...identityFlags(dir), "-c", "commit.gpgsign=false", "commit", "-q", "--no-verify", "--allow-empty", "-m", message]);
|
|
135606
|
-
const committed = { branch: BOARD_BRANCH, commit: gitRun(dir, ["rev-parse", "HEAD"]) };
|
|
135607
|
-
if (options.force) return committed;
|
|
135608
|
-
const ignored = gitRun(dir, ["ls-files", "--others", "--ignored", "--exclude-standard", ...excluded]).split("\n").filter(Boolean);
|
|
135609
|
-
return ignored.length === 0 ? committed : { ...committed, ignored: { count: ignored.length, paths: shown(ignored) } };
|
|
135610
|
-
}
|
|
135611
|
-
function commitMessage(source, exported) {
|
|
135612
|
-
return `Export ${source.bundleId} from ${source.target.origin} at revision ${exported.source.revision}
|
|
135613
|
-
|
|
135614
|
-
superbee export, ${exported.exportedAt}: ${exported.counts.documents} documents, ${exported.counts.reserved} reserved files, ${exported.counts.blobs} blobs. History is not included.
|
|
135615
|
-
`;
|
|
135616
|
-
}
|
|
135617
|
-
function stagingName(target3) {
|
|
135618
|
-
return path56.join(path56.dirname(target3), `.${path56.basename(target3)}.superbee-export-${process.pid}-${randomBytes9(6).toString("hex")}.partial`);
|
|
135619
|
-
}
|
|
135620
|
-
function processAlive(pid) {
|
|
135621
|
-
try {
|
|
135622
|
-
process.kill(pid, 0);
|
|
135623
|
-
return true;
|
|
135624
|
-
} catch (error51) {
|
|
135625
|
-
return error51.code === "EPERM";
|
|
135626
|
-
}
|
|
135627
|
-
}
|
|
135628
|
-
async function removeAbandonedStaging(target3) {
|
|
135629
|
-
const prefix = `.${path56.basename(target3)}.superbee-export-`;
|
|
135630
|
-
let removed = 0;
|
|
135631
|
-
let names;
|
|
135632
|
-
try {
|
|
135633
|
-
names = await readdir9(path56.dirname(target3));
|
|
135634
|
-
} catch {
|
|
135635
|
-
return 0;
|
|
135636
|
-
}
|
|
135637
|
-
for (const name of names) {
|
|
135638
|
-
if (!name.startsWith(prefix)) continue;
|
|
135639
|
-
const match = /^(\d+)-[0-9a-f]{12}\.partial$/.exec(name.slice(prefix.length));
|
|
135640
|
-
if (!match) continue;
|
|
135641
|
-
const age = await lstat10(path56.join(path56.dirname(target3), name)).then((info) => Date.now() - info.mtimeMs, () => 0);
|
|
135642
|
-
if (processAlive(Number(match[1])) && age < ABANDONED_AFTER_MS) continue;
|
|
135643
|
-
await rm2(path56.join(path56.dirname(target3), name), { recursive: true, force: true });
|
|
135644
|
-
removed += 1;
|
|
135645
|
-
}
|
|
135646
|
-
return removed;
|
|
135647
|
-
}
|
|
135648
|
-
async function assertStandalone(target3, home2) {
|
|
135649
|
-
const parent = path56.dirname(target3);
|
|
135650
|
-
const help2 = "pass --to <folder> outside any bundle, board or checkout";
|
|
135651
|
-
const checkout2 = await hostedCheckoutAt(parent, home2);
|
|
135652
|
-
if (checkout2) {
|
|
135653
|
-
throw new CliError("FORBIDDEN", `refusing to export inside the hosted checkout at ${checkout2.path}`, { details: { reason: "inside_checkout", enclosing: checkout2.path }, help: help2 });
|
|
135654
|
-
}
|
|
135655
|
-
const enclosing = await findBundleRoot(parent).catch(() => null);
|
|
135656
|
-
if (enclosing) {
|
|
135657
|
-
throw new CliError("FORBIDDEN", `refusing to export inside the local bundle at ${enclosing}`, { details: { reason: "inside_bundle", enclosing }, help: help2 });
|
|
135658
|
-
}
|
|
135659
|
-
const binding = await resolveProjectBinding(parent).catch(() => null);
|
|
135660
|
-
if (binding) {
|
|
135661
|
-
throw new CliError("FORBIDDEN", `refusing to export inside a project bound to a board (${binding.file})`, { details: { reason: "inside_bound_project", binding: binding.file }, help: help2 });
|
|
135662
|
-
}
|
|
135663
|
-
}
|
|
135664
|
-
async function exportToFolder(source, toArg, git2, deps2, mode, resume2) {
|
|
135665
|
-
const requested = path56.resolve(deps2.cwd, toArg);
|
|
135666
|
-
assertBundleOutsidePrivateState(requested, deps2.auth.home);
|
|
135667
|
-
const found = await entryKind2(requested);
|
|
135668
|
-
if (found === "other") throw new CliError("ALREADY_EXISTS", `${requested} exists and is not a folder`, { details: { reason: "not_a_folder", folder: requested }, help: "pass --to <new folder>" });
|
|
135669
|
-
if (found === "dir") throw new CliError("ALREADY_EXISTS", `${requested} is not empty`, { details: { reason: "not_empty", folder: requested }, help: "pass --to <new or empty folder>" });
|
|
135670
|
-
await mkdir5(path56.dirname(requested), { recursive: true });
|
|
135671
|
-
const target3 = path56.join(await realpath13(path56.dirname(requested)), path56.basename(requested));
|
|
135672
|
-
assertBundleOutsidePrivateState(target3, deps2.auth.home);
|
|
135673
|
-
await assertStandalone(target3, deps2.auth.home);
|
|
135674
|
-
const exported = await fetchExport(source, deps2, resume2);
|
|
135675
|
-
const abandoned = await removeAbandonedStaging(target3);
|
|
135676
|
-
const staging = stagingName(target3);
|
|
135677
|
-
await mkdir5(staging);
|
|
135678
|
-
let gitResult = null;
|
|
135679
|
-
try {
|
|
135680
|
-
await writeEntries(staging, exported.entries);
|
|
135681
|
-
if (git2) gitResult = commitExport(staging, commitMessage(source, exported), { force: true });
|
|
135682
|
-
const now = await entryKind2(target3);
|
|
135683
|
-
if (now === "dir" || now === "other") throw new CliError("ALREADY_EXISTS", `${target3} is no longer empty`, { details: { reason: "not_empty", folder: target3 }, help: "pass --to <new or empty folder>" });
|
|
135684
|
-
try {
|
|
135685
|
-
await rename2(staging, target3);
|
|
135686
|
-
} catch (error51) {
|
|
135687
|
-
const code3 = error51.code;
|
|
135688
|
-
if (code3 === "ENOTEMPTY" || code3 === "EEXIST" || code3 === "ENOTDIR") {
|
|
135689
|
-
throw new CliError("ALREADY_EXISTS", `${target3} is no longer empty`, { details: { reason: "not_empty", folder: target3 }, help: "pass --to <new or empty folder>" });
|
|
135690
|
-
}
|
|
135691
|
-
throw error51;
|
|
135692
|
-
}
|
|
135693
|
-
} catch (error51) {
|
|
135694
|
-
await rm2(staging, { recursive: true, force: true }).catch(() => {
|
|
135695
|
-
});
|
|
135696
|
-
throw error51;
|
|
135697
|
-
}
|
|
135698
|
-
const home2 = await bundleHomeAt(target3, { home: deps2.auth.home });
|
|
135699
|
-
deps2.stdout(
|
|
135700
|
-
render(
|
|
135701
|
-
{
|
|
135702
|
-
export: "created",
|
|
135703
|
-
folder: target3,
|
|
135704
|
-
home: home2.home,
|
|
135705
|
-
bundle_id: source.bundleId,
|
|
135706
|
-
host: source.target.origin,
|
|
135707
|
-
revision: exported.source.revision,
|
|
135708
|
-
exported_at: exported.exportedAt,
|
|
135709
|
-
okf_edition: exported.source.okfEdition,
|
|
135710
|
-
documents: exported.counts.documents,
|
|
135711
|
-
reserved: exported.counts.reserved,
|
|
135712
|
-
blobs: exported.counts.blobs,
|
|
135713
|
-
bytes: exported.bytes,
|
|
135714
|
-
root_index: exported.entries.some((entry) => entry.path === "index.md"),
|
|
135715
|
-
...gitResult ? { git: gitResult } : {},
|
|
135716
|
-
...abandoned > 0 ? { removed_abandoned_exports: abandoned } : {},
|
|
135717
|
-
hosted: HOSTED_UNCHANGED,
|
|
135718
|
-
history: NO_HISTORY,
|
|
135719
|
-
help: nextSteps2(target3, gitResult !== null)
|
|
135720
|
-
},
|
|
135721
|
-
mode
|
|
135722
|
-
)
|
|
135723
|
-
);
|
|
135724
|
-
}
|
|
135725
|
-
function nextSteps2(folder, git2) {
|
|
135726
|
-
const steps = [commandFragment`${cliInvocation()} status --dir ${commandToken(folder)}`];
|
|
135727
|
-
if (git2) steps.push(commandFragment`git -C ${commandToken(folder)} remote add origin <url> && ${cliInvocation()} sync --establish --dir ${commandToken(folder)}`);
|
|
135728
|
-
return steps;
|
|
135729
|
-
}
|
|
135730
|
-
function isJournal(value) {
|
|
135731
|
-
const record6 = value;
|
|
135732
|
-
return record6 !== null && typeof record6 === "object" && record6.schema === JOURNAL_SCHEMA && typeof record6.bundle_id === "string" && typeof record6.host === "string" && typeof record6.audience === "string" && Number.isSafeInteger(record6.revision) && typeof record6.exported_at === "string" && typeof record6.git === "boolean" && Array.isArray(record6.adds) && record6.adds.every((entry) => typeof entry === "string") && Number.isSafeInteger(record6.same) && Array.isArray(record6.kept_local) && record6.kept_local.every((entry) => typeof entry === "string") && Array.isArray(record6.deleted_locally) && record6.deleted_locally.every((entry) => typeof entry === "string") && Number.isSafeInteger(record6.unsent_kept);
|
|
135733
|
-
}
|
|
135734
|
-
async function stagingDir(folder) {
|
|
135735
|
-
const staging = path56.join(folder, IN_PLACE_STAGING);
|
|
135736
|
-
try {
|
|
135737
|
-
if (!(await lstat10(staging)).isDirectory()) return null;
|
|
135738
|
-
return await realpath13(staging) === path56.join(await realpath13(folder), IN_PLACE_STAGING) ? staging : null;
|
|
135739
|
-
} catch {
|
|
135740
|
-
return null;
|
|
135741
|
-
}
|
|
135742
|
-
}
|
|
135743
|
-
async function readJournal(folder) {
|
|
135744
|
-
const staging = await stagingDir(folder);
|
|
135745
|
-
if (!staging) return null;
|
|
135746
|
-
let journal;
|
|
135747
|
-
const read = readRegularFileNoFollowSync(path56.join(staging, JOURNAL));
|
|
135748
|
-
if (read.state !== "present" || read.bytes.byteLength > MAX_JOURNAL_BYTES) return null;
|
|
135749
|
-
try {
|
|
135750
|
-
const value = JSON.parse(read.bytes.toString("utf8"));
|
|
135751
|
-
if (!isJournal(value)) return null;
|
|
135752
|
-
journal = value;
|
|
135753
|
-
} catch {
|
|
135754
|
-
return null;
|
|
135755
|
-
}
|
|
135756
|
-
const marker = readCheckoutMarker(folder);
|
|
135757
|
-
if (!marker || marker.bundle_id !== journal.bundle_id || marker.host !== bindingHostArgument({ origin: journal.host, audience: journal.audience })) return null;
|
|
135758
|
-
return journal;
|
|
135759
|
-
}
|
|
135760
|
-
async function sameBytes(file2, bytes) {
|
|
135761
|
-
const read = readRegularFileNoFollowSync(file2);
|
|
135762
|
-
return read.state === "present" && Buffer.compare(read.bytes, Buffer.from(bytes)) === 0;
|
|
135763
|
-
}
|
|
135764
|
-
async function placementOf(folder, entry) {
|
|
135765
|
-
let info;
|
|
135766
|
-
try {
|
|
135767
|
-
info = await lstat10(path56.join(folder, ...entry.path.split("/")));
|
|
135768
|
-
} catch (error51) {
|
|
135769
|
-
const code3 = error51.code;
|
|
135770
|
-
if (code3 === "ENOENT") return "add";
|
|
135771
|
-
if (code3 === "ENOTDIR") return "kept";
|
|
135772
|
-
throw error51;
|
|
135773
|
-
}
|
|
135774
|
-
if (!info.isFile()) return "kept";
|
|
135775
|
-
return await sameBytes(path56.join(folder, ...entry.path.split("/")), entry.bytes) ? "same" : "kept";
|
|
135776
|
-
}
|
|
135777
|
-
async function placeStaged(folder, staging, relative2) {
|
|
135778
|
-
const final = path56.join(folder, ...relative2.split("/"));
|
|
135779
|
-
const staged = path56.join(staging, "files", ...relative2.split("/"));
|
|
135780
|
-
try {
|
|
135781
|
-
if ((await lstat10(staged)).isFile()) {
|
|
135782
|
-
const stagedParent = await realpath13(path56.dirname(staged));
|
|
135783
|
-
if (!stagedParent.startsWith(`${await realpath13(staging)}${path56.sep}`)) return "kept";
|
|
135784
|
-
} else return "kept";
|
|
135785
|
-
} catch (error51) {
|
|
135786
|
-
if (error51.code !== "ENOENT") return "kept";
|
|
135787
|
-
return await lstat10(final).then((info) => info.isFile(), () => false) ? "same" : "kept";
|
|
135788
|
-
}
|
|
135789
|
-
try {
|
|
135790
|
-
await mkdir5(path56.dirname(final), { recursive: true });
|
|
135791
|
-
const parent = await realpath13(path56.dirname(final));
|
|
135792
|
-
if (parent !== folder && !parent.startsWith(`${folder}${path56.sep}`)) return "kept";
|
|
135793
|
-
} catch {
|
|
135794
|
-
return "kept";
|
|
135795
|
-
}
|
|
135796
|
-
let outcome;
|
|
135797
|
-
try {
|
|
135798
|
-
await link3(staged, final);
|
|
135799
|
-
outcome = "added";
|
|
135800
|
-
} catch (error51) {
|
|
135801
|
-
const code3 = error51.code;
|
|
135802
|
-
if (code3 !== "EEXIST") throw error51;
|
|
135803
|
-
const stagedRead = readRegularFileNoFollowSync(staged);
|
|
135804
|
-
outcome = stagedRead.state === "present" && await sameBytes(final, stagedRead.bytes) ? "same" : "kept";
|
|
135805
|
-
}
|
|
135806
|
-
await unlink14(staged).catch(() => {
|
|
135807
|
-
});
|
|
135808
|
-
return outcome;
|
|
135809
|
-
}
|
|
135810
|
-
async function writeJournal(staging, journal) {
|
|
135811
|
-
const temp = path56.join(staging, `.${JOURNAL}.${randomBytes9(6).toString("hex")}.tmp`);
|
|
135812
|
-
await writeNewFile(temp, Buffer.from(`${JSON.stringify(journal)}
|
|
135813
|
-
`, "utf8"));
|
|
135814
|
-
await rename2(temp, path56.join(staging, JOURNAL));
|
|
135815
|
-
}
|
|
135816
|
-
async function finishInPlace(folder, journal) {
|
|
135817
|
-
const staging = await stagingDir(folder);
|
|
135818
|
-
if (!staging) throw new CliError("RUNTIME", `the in-place export's staging folder in ${folder} is gone`, { help: `${cliInvocation()} export --in-place --dir ${commandToken(folder)}` });
|
|
135819
|
-
const added = [];
|
|
135820
|
-
const kept = [...journal.kept_local];
|
|
135821
|
-
let same = journal.same;
|
|
135822
|
-
for (const relative2 of journal.adds) {
|
|
135823
|
-
classifyEntryPath(relative2);
|
|
135824
|
-
const outcome = await placeStaged(folder, staging, relative2);
|
|
135825
|
-
if (outcome === "added") added.push(relative2);
|
|
135826
|
-
else if (outcome === "same") same += 1;
|
|
135827
|
-
else kept.push(relative2);
|
|
135828
|
-
}
|
|
135829
|
-
let git2 = null;
|
|
135830
|
-
if (journal.git) {
|
|
135831
|
-
git2 = await ownCommit(folder) ?? commitExport(folder, `Export ${journal.bundle_id} from ${journal.host} at revision ${journal.revision}
|
|
135832
|
-
|
|
135833
|
-
superbee export --in-place, ${journal.exported_at}. History is not included.
|
|
135834
|
-
`, { force: false });
|
|
135835
|
-
}
|
|
135836
|
-
await rm2(path56.join(staging, "files"), { recursive: true, force: true });
|
|
135837
|
-
await unlink14(path56.join(staging, JOURNAL)).catch(() => {
|
|
135838
|
-
});
|
|
135839
|
-
await rm2(staging, { recursive: true, force: true });
|
|
135840
|
-
const marker = await removeCheckoutMarker(folder, { origin: journal.host, audience: journal.audience, bundle_id: journal.bundle_id }).catch(() => false) ? "removed" : "none";
|
|
135841
|
-
return { added, kept, same, git: git2, marker };
|
|
135842
|
-
}
|
|
135843
|
-
function inPlaceReceipt(folder, journal, finished, homeName, resumed) {
|
|
135844
|
-
return {
|
|
135845
|
-
export: "converted",
|
|
135846
|
-
folder,
|
|
135847
|
-
home: homeName,
|
|
135848
|
-
bundle_id: journal.bundle_id,
|
|
135849
|
-
host: journal.host,
|
|
135850
|
-
revision: journal.revision,
|
|
135851
|
-
exported_at: journal.exported_at,
|
|
135852
|
-
...resumed ? { resumed: true } : {},
|
|
135853
|
-
binding: "removed (the folder is no longer a hosted checkout)",
|
|
135854
|
-
marker: finished.marker,
|
|
135855
|
-
added: finished.added.length,
|
|
135856
|
-
...finished.added.length > 0 ? { added_paths: shown(finished.added) } : {},
|
|
135857
|
-
unchanged: finished.same,
|
|
135858
|
-
kept_local: finished.kept.length,
|
|
135859
|
-
...finished.kept.length > 0 ? { kept_local_paths: shown(finished.kept), kept_local_note: "these files differ from the host's (or block its path); the folder's own were kept" } : {},
|
|
135860
|
-
...journal.deleted_locally.length > 0 ? { deleted_locally: journal.deleted_locally.length, deleted_locally_paths: shown(journal.deleted_locally), deleted_locally_note: "deleted in this folder and not sent; the host's copies were not put back" } : {},
|
|
135861
|
-
...journal.unsent_kept > 0 ? { unsent_kept: journal.unsent_kept, unsent_note: "changes sync had not sent stay in this folder only" } : {},
|
|
135862
|
-
...finished.git ? { git: finished.git } : {},
|
|
135863
|
-
hosted: HOSTED_UNCHANGED,
|
|
135864
|
-
history: NO_HISTORY,
|
|
135865
|
-
help: nextSteps2(folder, finished.git !== null)
|
|
135866
|
-
};
|
|
135867
|
-
}
|
|
135868
|
-
async function ownCommit(folder) {
|
|
135869
|
-
const head = runGit(folder, ["rev-parse", "--verify", "--quiet", "HEAD"]);
|
|
135870
|
-
const top = runGit(folder, ["rev-parse", "--show-toplevel"]);
|
|
135871
|
-
const own5 = top.status === 0 && await realpath13(top.stdout.trim()).catch(() => "") === await realpath13(folder).catch(() => folder);
|
|
135872
|
-
if (!own5 || head.status !== 0) return null;
|
|
135873
|
-
return { branch: runGit(folder, ["symbolic-ref", "-q", "--short", "HEAD"]).stdout.trim() || BOARD_BRANCH, commit: head.stdout.trim() };
|
|
135874
|
-
}
|
|
135875
|
-
async function assertNoRepository(folder) {
|
|
135876
|
-
if (await lstat10(path56.join(folder, ".git")).then(() => true, () => false)) {
|
|
135877
|
-
throw new CliError("ALREADY_EXISTS", `${folder} already holds a Git repository`, {
|
|
135878
|
-
details: { reason: "already_git", folder },
|
|
135879
|
-
help: `${cliInvocation()} export --in-place --dir ${commandToken(folder)}`
|
|
135880
|
-
});
|
|
135881
|
-
}
|
|
135882
|
-
}
|
|
135883
|
-
async function exportInPlace(dirArg, git2, keepUnsent, deps2, mode, resume2) {
|
|
135884
|
-
const requested = path56.resolve(deps2.cwd, dirArg ?? ".");
|
|
135885
|
-
assertBundleOutsidePrivateState(requested, deps2.auth.home);
|
|
135886
|
-
const binding = await hostedCheckoutFor(dirArg === void 0 ? [] : ["--dir", dirArg], deps2.auth.home, deps2.cwd);
|
|
135887
|
-
if (!binding) {
|
|
135888
|
-
const folder2 = await realpath13(requested).catch(() => requested);
|
|
135889
|
-
const journal = await readJournal(folder2);
|
|
135890
|
-
if (journal) {
|
|
135891
|
-
const finished = await finishInPlace(folder2, journal);
|
|
135892
|
-
const home4 = await bundleHomeAt(folder2, { home: deps2.auth.home });
|
|
135893
|
-
deps2.stdout(render(inPlaceReceipt(folder2, journal, finished, home4.home, true), mode));
|
|
135894
|
-
return;
|
|
135895
|
-
}
|
|
135896
|
-
if (await entryKind2(folder2) === "absent") throw new CliError("NOT_FOUND", `${folder2} does not exist`, { help: `${cliInvocation()} export --in-place --dir <checkout folder>` });
|
|
135897
|
-
const marker = readCheckoutMarker(folder2);
|
|
135898
|
-
if (marker) {
|
|
135899
|
-
const existing = git2 ? await ownCommit(folder2) : null;
|
|
135900
|
-
if (git2 && !existing) await assertNoRepository(folder2);
|
|
135901
|
-
const staging = await stagingDir(folder2);
|
|
135902
|
-
if (staging && !await lstat10(path56.join(staging, JOURNAL)).then(() => true, () => false)) await rm2(staging, { recursive: true, force: true });
|
|
135903
|
-
await removeCheckoutMarker(folder2, { origin: marker.host, audience: `${marker.host}/mcp`, bundle_id: marker.bundle_id });
|
|
135904
|
-
const committed = existing ?? (git2 ? commitExport(folder2, `Export ${marker.bundle_id} (an unbound copy of a hosted checkout)
|
|
135905
|
-
|
|
135906
|
-
superbee export --in-place. Nothing was fetched from the host; history is not included.
|
|
135907
|
-
`, { force: false }) : null);
|
|
135908
|
-
const home4 = await bundleHomeAt(folder2, { home: deps2.auth.home });
|
|
135909
|
-
deps2.stdout(
|
|
135910
|
-
render(
|
|
135911
|
-
{
|
|
135912
|
-
export: "converted",
|
|
135913
|
-
folder: folder2,
|
|
135914
|
-
home: home4.home,
|
|
135915
|
-
bundle_id: marker.bundle_id,
|
|
135916
|
-
from: "an unbound copy of a hosted checkout",
|
|
135917
|
-
marker: "removed",
|
|
135918
|
-
fetched: false,
|
|
135919
|
-
note: `nothing was fetched: the files are kept as they are. To fill in what the copy lacks from the host first, adopt it (${cliInvocation()} checkout --adopt <folder> --host <url>), then export --in-place`,
|
|
135920
|
-
...committed ? { git: committed } : {},
|
|
135921
|
-
hosted: HOSTED_UNCHANGED,
|
|
135922
|
-
history: NO_HISTORY,
|
|
135923
|
-
help: nextSteps2(folder2, committed !== null)
|
|
135924
|
-
},
|
|
135925
|
-
mode
|
|
135926
|
-
)
|
|
135927
|
-
);
|
|
135928
|
-
return;
|
|
135929
|
-
}
|
|
135930
|
-
const home3 = await bundleHomeAt(folder2, { home: deps2.auth.home });
|
|
135931
|
-
deps2.stdout(render({ export: "unchanged", folder: folder2, home: home3.home, reason: "not a hosted checkout: nothing to convert" }, mode));
|
|
135932
|
-
return;
|
|
135933
|
-
}
|
|
135934
|
-
const folder = binding.path;
|
|
135935
|
-
if (git2) await assertNoRepository(folder);
|
|
135936
|
-
const status2 = await hostedStatus(binding, deps2.auth.home);
|
|
135937
|
-
if (status2.sync.state === "busy") {
|
|
135938
|
-
throw new CliError("CONFLICT", `another command holds the checkout lock for ${folder}`, { details: { reason: "checkout_busy", folder }, help: "wait for it to finish, then retry the same command" });
|
|
135939
|
-
}
|
|
135940
|
-
const unsentRefusal = (count5, sync3) => new CliError("CONFLICT", `the checkout of '${binding.bundle_id}' at ${folder} has ${count5} change(s) not on the host`, {
|
|
135941
|
-
details: {
|
|
135942
|
-
reason: "unsent_changes",
|
|
135943
|
-
folder,
|
|
135944
|
-
...sync3,
|
|
135945
|
-
keep_them_here_only: commandFragment`${cliInvocation()} export --in-place --dir ${commandToken(folder)} --keep-unsent${git2 ? commandFragment` --git` : commandFragment``}`
|
|
135946
|
-
},
|
|
135947
|
-
help: commandFragment`${cliInvocation()} sync --dir ${commandToken(folder)}`
|
|
135948
|
-
});
|
|
135949
|
-
const early = Number(status2.sync.unsent ?? 0) + Number(status2.sync.conflicts ?? 0) + Number(status2.sync.held_files ?? 0) + Number(status2.sync.held_deletions ?? 0);
|
|
135950
|
-
if (early > 0 && !keepUnsent) throw unsentRefusal(early, status2.sync);
|
|
135951
|
-
const source = { target: resolveHostedTarget(binding.audience), bundleId: binding.bundle_id, slug: binding.workspace_slug ?? null, workspace: null, binding };
|
|
135952
|
-
const exported = await fetchExport(source, deps2, resume2);
|
|
135953
|
-
const result3 = await filesystemPushRoleLocks().request(checkoutLockName(folder), { ifAvailable: true }, async (lock) => {
|
|
135954
|
-
if (!lock) {
|
|
135955
|
-
throw new CliError("CONFLICT", `another command holds the checkout lock for ${folder}`, { details: { reason: "checkout_busy", folder }, help: "wait for it to finish, then retry the same command" });
|
|
135956
|
-
}
|
|
135957
|
-
const current = await bindingForPath(deps2.auth.home, folder);
|
|
135958
|
-
if (!current || current.checkout_id !== binding.checkout_id) {
|
|
135959
|
-
throw new CliError("CONFLICT", `the checkout at ${folder} changed while it was exported`, { details: { reason: "checkout_changed", folder }, help: "retry the same command" });
|
|
135960
|
-
}
|
|
135961
|
-
const store = await FileJournaledBackend.open({ directory: checkoutStoreDir(deps2.auth.home, binding.checkout_id), readOnly: true });
|
|
135962
|
-
let pending;
|
|
135963
|
-
try {
|
|
135964
|
-
const classified = await classifyCheckout(binding, deps2.auth.home, store);
|
|
135965
|
-
pending = /* @__PURE__ */ new Set([...classified.unsent, ...classified.conflicts, ...classified.held.keys(), ...classified.heldDeletions]);
|
|
135966
|
-
} finally {
|
|
135967
|
-
await store.close();
|
|
135968
|
-
}
|
|
135969
|
-
if (pending.size > 0 && !keepUnsent) throw unsentRefusal(pending.size, { unsent_ids: shown([...pending]) });
|
|
135970
|
-
const staging = path56.join(folder, IN_PLACE_STAGING);
|
|
135971
|
-
await rm2(staging, { recursive: true, force: true });
|
|
135972
|
-
await mkdir5(path56.join(staging, "files"), { recursive: true });
|
|
135973
|
-
const adds = [];
|
|
135974
|
-
const kept = [];
|
|
135975
|
-
const deletedLocally = [];
|
|
135976
|
-
let same = 0;
|
|
135977
|
-
for (const entry of exported.entries) {
|
|
135978
|
-
const id = entry.kind === "document" ? entry.path.slice(0, -3) : entry.path;
|
|
135979
|
-
const placement = await placementOf(folder, entry);
|
|
135980
|
-
if (placement === "add" && pending.has(id)) {
|
|
135981
|
-
deletedLocally.push(entry.path);
|
|
135982
|
-
continue;
|
|
135983
|
-
}
|
|
135984
|
-
if (placement === "same") same += 1;
|
|
135985
|
-
else if (placement === "kept") kept.push(entry.path);
|
|
135986
|
-
else {
|
|
135987
|
-
const file2 = path56.join(staging, "files", ...entry.path.split("/"));
|
|
135988
|
-
await mkdir5(path56.dirname(file2), { recursive: true });
|
|
135989
|
-
await writeNewFile(file2, entry.bytes);
|
|
135990
|
-
adds.push(entry.path);
|
|
135991
|
-
}
|
|
135992
|
-
}
|
|
135993
|
-
const journal = {
|
|
135994
|
-
schema: JOURNAL_SCHEMA,
|
|
135995
|
-
bundle_id: binding.bundle_id,
|
|
135996
|
-
host: binding.origin,
|
|
135997
|
-
audience: binding.audience,
|
|
135998
|
-
revision: exported.source.revision,
|
|
135999
|
-
exported_at: exported.exportedAt,
|
|
136000
|
-
git: git2,
|
|
136001
|
-
adds,
|
|
136002
|
-
same,
|
|
136003
|
-
kept_local: kept,
|
|
136004
|
-
deleted_locally: deletedLocally,
|
|
136005
|
-
unsent_kept: pending.size
|
|
136006
|
-
};
|
|
136007
|
-
await writeJournal(staging, journal);
|
|
136008
|
-
await releaseCheckout(deps2.auth.home, binding);
|
|
136009
|
-
return { journal, finished: await finishInPlace(folder, journal) };
|
|
136010
|
-
});
|
|
136011
|
-
const home2 = await bundleHomeAt(folder, { home: deps2.auth.home });
|
|
136012
|
-
deps2.stdout(render(inPlaceReceipt(folder, result3.journal, result3.finished, home2.home, false), mode));
|
|
136013
|
-
}
|
|
136014
|
-
async function exportCommand(argv2, partial2 = {}) {
|
|
136015
|
-
const deps2 = exportDeps(partial2);
|
|
136016
|
-
const { values, positionals } = parseLeafOrUsage(
|
|
136017
|
-
() => parseArgs42({
|
|
136018
|
-
args: argv2,
|
|
136019
|
-
options: {
|
|
136020
|
-
to: { type: "string" },
|
|
136021
|
-
"in-place": { type: "boolean" },
|
|
136022
|
-
dir: { type: "string" },
|
|
136023
|
-
host: { type: "string" },
|
|
136024
|
-
workspace: { type: "string" },
|
|
136025
|
-
git: { type: "boolean" },
|
|
136026
|
-
"keep-unsent": { type: "boolean" },
|
|
136027
|
-
json: { type: "boolean" },
|
|
136028
|
-
help: { type: "boolean", short: "h" }
|
|
136029
|
-
},
|
|
136030
|
-
allowPositionals: true
|
|
136031
|
-
}),
|
|
136032
|
-
CLI_LEAVES.export
|
|
136033
|
-
);
|
|
136034
|
-
if (values.help) {
|
|
136035
|
-
deps2.stdout(renderUsage(EXPORT_USAGE));
|
|
136036
|
-
return;
|
|
136037
|
-
}
|
|
136038
|
-
const mode = resolveMode(values);
|
|
136039
|
-
const usage3 = (message) => new CliError("USAGE", message, { help: `${cliInvocation()} export --help` });
|
|
136040
|
-
const named = positionals[0];
|
|
136041
|
-
const inPlace = values["in-place"] === true;
|
|
136042
|
-
if (inPlace === (values.to !== void 0)) throw usage3("pass exactly one of --to <folder> and --in-place");
|
|
136043
|
-
if (named !== void 0 && values.dir !== void 0) throw usage3("pass a bundle id or --dir <checkout>, not both");
|
|
136044
|
-
if (named === void 0 && (values.host !== void 0 || values.workspace !== void 0)) throw usage3("--host and --workspace go with a bundle id; a checkout uses its own");
|
|
136045
|
-
if (inPlace && named !== void 0) throw usage3("--in-place converts a checkout folder: pass --dir <checkout>, not a bundle id");
|
|
136046
|
-
if (!inPlace && values["keep-unsent"]) throw usage3("--keep-unsent goes with --in-place");
|
|
136047
|
-
const reference = named === void 0 ? void 0 : parseHostedBundleReference(named);
|
|
136048
|
-
if (named !== void 0 && !reference) throw usage3(`'${named}' is not a hosted bundle id or <workspace>/<bundle-id>`);
|
|
136049
|
-
const git2 = values.git === true;
|
|
136050
|
-
const resume2 = commandFragment`${cliInvocation()} export${named !== void 0 ? commandFragment` ${commandToken(named)}` : commandFragment``}${values.dir !== void 0 ? commandFragment` --dir ${commandToken(path56.resolve(deps2.cwd, values.dir))}` : commandFragment``}${values.to !== void 0 ? commandFragment` --to ${commandToken(path56.resolve(deps2.cwd, values.to))}` : commandFragment``}${inPlace ? commandFragment` --in-place` : commandFragment``}${values.host !== void 0 ? commandFragment` --host ${commandToken(values.host)}` : commandFragment``}${values.workspace !== void 0 ? commandFragment` --workspace ${commandToken(values.workspace)}` : commandFragment``}${git2 ? commandFragment` --git` : commandFragment``}${values["keep-unsent"] ? commandFragment` --keep-unsent` : commandFragment``}${values.json ? commandFragment` --json` : commandFragment``}`;
|
|
136051
|
-
if (values.dir !== void 0) assertBundleOutsidePrivateState(path56.resolve(deps2.cwd, values.dir), deps2.auth.home);
|
|
136052
|
-
if (inPlace) {
|
|
136053
|
-
await exportInPlace(values.dir, git2, values["keep-unsent"] === true, deps2, mode, resume2);
|
|
136054
|
-
return;
|
|
136055
|
-
}
|
|
136056
|
-
let source;
|
|
136057
|
-
if (reference) {
|
|
136058
|
-
const named2 = await requireHostedBundleHost(values.host, deps2.auth);
|
|
136059
|
-
source = {
|
|
136060
|
-
target: named2.target,
|
|
136061
|
-
bundleId: reference.bundleId,
|
|
136062
|
-
slug: reference.slug,
|
|
136063
|
-
workspace: workspaceForNamedHost(named2, values.workspace) ?? null,
|
|
136064
|
-
binding: null
|
|
136065
|
-
};
|
|
136066
|
-
} else {
|
|
136067
|
-
const binding = await hostedCheckoutFor(values.dir === void 0 ? [] : ["--dir", values.dir], deps2.auth.home, deps2.cwd);
|
|
136068
|
-
if (!binding) {
|
|
136069
|
-
throw new CliError("NOT_FOUND", `${path56.resolve(deps2.cwd, values.dir ?? ".")} is not a hosted checkout`, {
|
|
136070
|
-
details: { reason: "not_a_checkout" },
|
|
136071
|
-
help: `${cliInvocation()} export <bundle-id> --to ${commandToken(values.to)}`
|
|
136072
|
-
});
|
|
136073
|
-
}
|
|
136074
|
-
source = { target: resolveHostedTarget(binding.audience), bundleId: binding.bundle_id, slug: binding.workspace_slug ?? null, workspace: null, binding };
|
|
136075
|
-
}
|
|
136076
|
-
await exportToFolder(source, values.to, git2, deps2, mode, resume2);
|
|
136077
|
-
}
|
|
136078
|
-
var EXPORT_USAGE, EXPORT_DEADLINE_MS, SHOWN, JOURNAL, JOURNAL_SCHEMA, MAX_JOURNAL_BYTES, NO_HISTORY, HOSTED_UNCHANGED, PAGED_EXPORT_ATTEMPTS, PAGED_EXPORT_RESTART_MS, PAGE_RETRY_MS, pause2, ABANDONED_AFTER_MS;
|
|
136079
|
-
var init_export = __esm({
|
|
136080
|
-
"../cli/src/commands/export.ts"() {
|
|
136081
|
-
"use strict";
|
|
136082
|
-
init_define_SUPERBEE_BUILD_IDENTITY();
|
|
136083
|
-
init_define_SUPERBEE_UPDATE_POLICY();
|
|
136084
|
-
init_src();
|
|
136085
|
-
init_filesystem_push_role();
|
|
136086
|
-
init_src2();
|
|
136087
|
-
init_args();
|
|
136088
|
-
init_bundle2();
|
|
136089
|
-
init_bundle_home();
|
|
136090
|
-
init_command_spec();
|
|
136091
|
-
init_command_text();
|
|
136092
|
-
init_errors4();
|
|
136093
|
-
init_invocation();
|
|
136094
|
-
init_output2();
|
|
136095
|
-
init_private_state_bundle_boundary();
|
|
136096
|
-
init_nofollow_read();
|
|
136097
|
-
init_autopull2();
|
|
136098
|
-
init_session();
|
|
136099
|
-
init_discovery();
|
|
136100
|
-
init_binding();
|
|
136101
|
-
init_client();
|
|
136102
|
-
init_account();
|
|
136103
|
-
init_refusals();
|
|
136104
|
-
init_reference();
|
|
136105
|
-
init_sync2();
|
|
136106
|
-
init_status();
|
|
136107
|
-
init_file_journaled_backend();
|
|
136108
|
-
init_marker();
|
|
136109
|
-
init_export_archive();
|
|
136110
|
-
init_export_archive();
|
|
136111
|
-
EXPORT_USAGE = `superbee export \u2014 copy a hosted bundle out of hosted Superbee
|
|
136112
|
-
|
|
136113
|
-
Usage:
|
|
136114
|
-
superbee export <bundle-id> --to <folder> [--host <url>] [--workspace <id>] [--git] [--json]
|
|
136115
|
-
superbee export [--dir <checkout>] --to <folder> [--git] [--json]
|
|
136116
|
-
superbee export [--dir <checkout>] --in-place [--git] [--keep-unsent] [--json]
|
|
136117
|
-
|
|
136118
|
-
Fetches the hosted bundle's portable export: every document, reserved file (index.md, log.md)
|
|
136119
|
-
and blob at its latest confirmed revision, byte for byte as the host stores them. The archive is
|
|
136120
|
-
verified whole (every entry against the host's digest manifest) before any file is written.
|
|
136121
|
-
Signs in if needed (AUTH_REQUIRED, exit 4, carries the one link to relay and the command to
|
|
136122
|
-
re-run). The hosted bundle is never changed. History is not exported: the folder holds the
|
|
136123
|
-
current revision only.
|
|
136124
|
-
|
|
136125
|
-
--to <folder> writes a new local bundle there. The folder must be new or empty; it appears
|
|
136126
|
-
complete or not at all. With a bundle id the host is --host, else your last sign-in; with
|
|
136127
|
-
--dir (or run inside a checkout) the checkout's own host and bundle are used. A bundle id in more
|
|
136128
|
-
than one of your workspaces is named <workspace>/<bundle-id>, or with --workspace.
|
|
136129
|
-
|
|
136130
|
-
--in-place converts a hosted checkout into a plain local bundle: it adds the files the checkout
|
|
136131
|
-
lacks (blobs, reserved files, documents added on the host), never overwrites a file, then
|
|
136132
|
-
forgets the checkout's binding. It refuses a checkout with changes sync has not sent, conflicts
|
|
136133
|
-
or held files; run 'superbee sync' first, or pass --keep-unsent to keep them in this folder only.
|
|
136134
|
-
If it is interrupted, re-running the same command finishes it.
|
|
136135
|
-
|
|
136136
|
-
--git makes the result a Git board: a repository on branch '${BOARD_BRANCH}' whose first commit is the
|
|
136137
|
-
export. Share it with 'git remote add origin <url>' and 'superbee sync --establish'.
|
|
136138
|
-
|
|
136139
|
-
Options:
|
|
136140
|
-
--to <folder> New or empty folder to export into
|
|
136141
|
-
--in-place Convert the hosted checkout (--dir, else the current folder) into a local bundle
|
|
136142
|
-
--dir <checkout> The hosted checkout to export (default: the current folder)
|
|
136143
|
-
--host <url> Hosted Superbee URL, with a bundle id; default: your last sign-in
|
|
136144
|
-
--workspace <id> With a bundle id: your workspace that holds it, by id or slug
|
|
136145
|
-
--git Initialize a Git repository on branch '${BOARD_BRANCH}' and commit the export
|
|
136146
|
-
--keep-unsent With --in-place: convert even with unsent changes, conflicts or held files
|
|
136147
|
-
--json Emit compact JSON instead of TOON
|
|
136148
|
-
-h, --help Show this help
|
|
136149
|
-
|
|
136150
|
-
Examples:
|
|
136151
|
-
superbee export team.knowledge --to ~/work/team-knowledge --git
|
|
136152
|
-
superbee export --dir ~/work/team --to ~/work/team-copy
|
|
136153
|
-
superbee export --dir ~/work/team --in-place
|
|
136154
|
-
`;
|
|
136155
|
-
EXPORT_DEADLINE_MS = 6e4;
|
|
136156
|
-
SHOWN = 20;
|
|
136157
|
-
JOURNAL = IN_PLACE_JOURNAL;
|
|
136158
|
-
JOURNAL_SCHEMA = 1;
|
|
136159
|
-
MAX_JOURNAL_BYTES = 8 * 1024 * 1024;
|
|
136160
|
-
NO_HISTORY = "not exported: the folder holds the current revision only";
|
|
136161
|
-
HOSTED_UNCHANGED = "unchanged: the hosted bundle still exists and was not modified";
|
|
136162
|
-
PAGED_EXPORT_ATTEMPTS = 3;
|
|
136163
|
-
PAGED_EXPORT_RESTART_MS = [500, 1500];
|
|
136164
|
-
PAGE_RETRY_MS = 1e3;
|
|
136165
|
-
pause2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
136166
|
-
ABANDONED_AFTER_MS = 60 * 60 * 1e3;
|
|
136167
|
-
}
|
|
136168
|
-
});
|
|
136169
|
-
|
|
136170
136308
|
// ../cli/src/commands/op.ts
|
|
136171
136309
|
import { homedir as homedir41 } from "node:os";
|
|
136172
136310
|
import path57 from "node:path";
|
|
@@ -136453,8 +136591,9 @@ function cleanAuthor(author) {
|
|
|
136453
136591
|
function gitVersions(board, id, rel, current, okfVersion, now, documentInputBytes) {
|
|
136454
136592
|
const repoPath = board.prefix === "" ? rel : `${board.prefix}/${rel}`;
|
|
136455
136593
|
const log = runGit(board.top, ["log", "--no-renames", "--format=%H%x1f%an <%ae>%x1f%aI", "HEAD", "--", repoPath]);
|
|
136456
|
-
if (log.status !== 0) return { rows: [], skipped: 0 };
|
|
136594
|
+
if (log.status !== 0) return { rows: [], skipped: 0, oversized: [] };
|
|
136457
136595
|
const rows = [];
|
|
136596
|
+
const oversized = [];
|
|
136458
136597
|
let skipped = 0;
|
|
136459
136598
|
let previous3 = current;
|
|
136460
136599
|
for (const line2 of log.stdout.split("\n").filter(Boolean)) {
|
|
@@ -136481,19 +136620,24 @@ function gitVersions(board, id, rel, current, okfVersion, now, documentInputByte
|
|
|
136481
136620
|
skipped += 1;
|
|
136482
136621
|
continue;
|
|
136483
136622
|
}
|
|
136623
|
+
const frontmatter = Object.fromEntries(
|
|
136624
|
+
Object.entries(parsed2.frontmatter).filter(([key2]) => !IMPORT_DROPPED_FIELDS.includes(key2))
|
|
136625
|
+
);
|
|
136626
|
+
if (Buffer.byteLength(JSON.stringify(frontmatter)) > FRONTMATTER_JSON_BYTES) {
|
|
136627
|
+
skipped += 1;
|
|
136628
|
+
oversized.push(`${rel}@${sha.slice(0, 12)}`);
|
|
136629
|
+
continue;
|
|
136630
|
+
}
|
|
136484
136631
|
rows.push({
|
|
136485
136632
|
documentId: id,
|
|
136486
136633
|
label: `imported:git/${sha}`,
|
|
136487
136634
|
author: cleanAuthor(author ?? ""),
|
|
136488
136635
|
authoredAt: new Date(when).toISOString(),
|
|
136489
|
-
|
|
136490
|
-
frontmatter: Object.fromEntries(
|
|
136491
|
-
Object.entries(parsed2.frontmatter).filter(([key2]) => !IMPORT_DROPPED_FIELDS.includes(key2))
|
|
136492
|
-
),
|
|
136636
|
+
frontmatter,
|
|
136493
136637
|
body: parsed2.body ?? ""
|
|
136494
136638
|
});
|
|
136495
136639
|
}
|
|
136496
|
-
return { rows: rows.reverse(), skipped };
|
|
136640
|
+
return { rows: rows.reverse(), skipped, oversized };
|
|
136497
136641
|
}
|
|
136498
136642
|
async function okfVersionOf(rootIndex) {
|
|
136499
136643
|
if (rootIndex === null) return void 0;
|
|
@@ -136636,7 +136780,12 @@ async function planPublish(folder, options) {
|
|
|
136636
136780
|
throw error51;
|
|
136637
136781
|
}
|
|
136638
136782
|
if (request3.kind !== "create") throw new Error(`'${id}' became a ${request3.kind} request`);
|
|
136639
|
-
|
|
136783
|
+
const frontmatterBytes = Buffer.byteLength(JSON.stringify(request3.payload.frontmatter));
|
|
136784
|
+
if ("verified" in request3.payload.frontmatter && frontmatterBytes <= FRONTMATTER_JSON_BYTES && frontmatterBytes > FRONTMATTER_JSON_BYTES - VERIFIED_STAMP_ROOM_BYTES) {
|
|
136785
|
+
blockers.push({ path: entry.rel, reason: "too_large", message: `'${id}' carries verified and leaves less than ${VERIFIED_STAMP_ROOM_BYTES} bytes of the ${FRONTMATTER_JSON_BYTES / 1024} KiB of frontmatter the host stores for its stamps on later edits; shorten its frontmatter` });
|
|
136786
|
+
continue;
|
|
136787
|
+
}
|
|
136788
|
+
if (frontmatterBytes > FRONTMATTER_JSON_BYTES) {
|
|
136640
136789
|
blockers.push({ path: entry.rel, reason: "too_large", message: `'${id}' has more than the ${FRONTMATTER_JSON_BYTES / 1024} KiB of frontmatter the host stores, counting ${request3.provenance.carried.join(", ")}` });
|
|
136641
136790
|
continue;
|
|
136642
136791
|
}
|
|
@@ -136676,6 +136825,7 @@ async function planPublish(folder, options) {
|
|
|
136676
136825
|
const versions = gitVersions(options.board, file2.id, file2.rel, file2.text, okfVersion, options.now ?? Date.now(), documentInputBytes);
|
|
136677
136826
|
history.push(...versions.rows);
|
|
136678
136827
|
skippedVersions += versions.skipped;
|
|
136828
|
+
for (const version4 of versions.oversized) skipped.push({ path: version4, reason: `an earlier version with more than the ${FRONTMATTER_JSON_BYTES / 1024} KiB of frontmatter the host stores; its history is imported without it` });
|
|
136679
136829
|
}
|
|
136680
136830
|
historyPlan = {
|
|
136681
136831
|
mode: "git",
|
|
@@ -136816,7 +136966,7 @@ function packParts(content3, versions) {
|
|
|
136816
136966
|
}
|
|
136817
136967
|
return parts;
|
|
136818
136968
|
}
|
|
136819
|
-
var MiB, PUBLISH_BOUNDS, STAGED_PUBLISH_BOUNDS, CONTENT_TYPES, HOSTED_KIND_WRITE, FORMAT_AT_EDGE, FORMAT_INSIDE, WIDEST_TARGET;
|
|
136969
|
+
var MiB, PUBLISH_BOUNDS, STAGED_PUBLISH_BOUNDS, CONTENT_TYPES, HOSTED_KIND_WRITE, VERIFIED_STAMP_ROOM_BYTES, FORMAT_AT_EDGE, FORMAT_INSIDE, WIDEST_TARGET;
|
|
136820
136970
|
var init_publish_plan = __esm({
|
|
136821
136971
|
"../cli/src/hosted/publish-plan.ts"() {
|
|
136822
136972
|
"use strict";
|
|
@@ -136876,6 +137026,7 @@ var init_publish_plan = __esm({
|
|
|
136876
137026
|
".xml": "application/xml"
|
|
136877
137027
|
};
|
|
136878
137028
|
HOSTED_KIND_WRITE = Object.freeze({ strict: true, persistActor: true, producer: "process:superbee-hosted" });
|
|
137029
|
+
VERIFIED_STAMP_ROOM_BYTES = 512;
|
|
136879
137030
|
FORMAT_AT_EDGE = new RegExp("^\\p{Cf}|\\p{Cf}$", "u");
|
|
136880
137031
|
FORMAT_INSIDE = new RegExp("(?![\\u200c\\u200d])\\p{Cf}", "u");
|
|
136881
137032
|
WIDEST_TARGET = Object.freeze({ workspace: "w".repeat(64), bundleId: "b".repeat(128), name: "n".repeat(128) });
|
|
@@ -137236,7 +137387,7 @@ function provenanceOf(plan) {
|
|
|
137236
137387
|
return {
|
|
137237
137388
|
carried: plan.provenance.carried,
|
|
137238
137389
|
dropped: plan.provenance.dropped,
|
|
137239
|
-
note: "provenance travels as each file states it (a claim, not a verification)
|
|
137390
|
+
note: "provenance travels as each file states it (a claim, not a verification), so documents read on the host as they do here; the host records the creation as yours and stamps the writer itself on every later edit"
|
|
137240
137391
|
};
|
|
137241
137392
|
}
|
|
137242
137393
|
function createRefusal(code3, message, context) {
|
|
@@ -137263,6 +137414,14 @@ function createRefusal(code3, message, context) {
|
|
|
137263
137414
|
details,
|
|
137264
137415
|
help: `put the files back as they were and re-run the same command to finish it; if the creation already finished, bind this folder to it (your edits become sync conflicts): ${cliInvocation()} checkout --adopt ${commandToken(context.folder)} --host ${commandToken(bindingHostArgument(context.target))}`
|
|
137265
137416
|
});
|
|
137417
|
+
case "validation_failed":
|
|
137418
|
+
if (/sets (superbee_updated_by|verified)\b.*which only the host writes/.test(message)) {
|
|
137419
|
+
return new CliError("FORBIDDEN", `${context.target.origin} does not yet keep each document's superbee_updated_by and verified on a new bundle, which this CLI carries`, {
|
|
137420
|
+
details: { ...details, reason: "host_predates_provenance" },
|
|
137421
|
+
help: "leave the files as they are: publish again once the host is updated, or publish with an earlier superbee CLI, which leaves those fields behind"
|
|
137422
|
+
});
|
|
137423
|
+
}
|
|
137424
|
+
return new CliError("USAGE", `${context.target.origin} refused the bundle (${code3}): ${message}`, { details, help: "fix the files it names, then preview again" });
|
|
137266
137425
|
default:
|
|
137267
137426
|
return new CliError("USAGE", `${context.target.origin} refused the bundle (${code3}): ${message}`, { details, help: "fix the files it names, then preview again" });
|
|
137268
137427
|
}
|
|
@@ -137709,9 +137868,10 @@ Without --yes, previews only, with no network: what travels (documents, reserved
|
|
|
137709
137868
|
files), what stays (dot-files, links), how it is sent, every bound the host enforces, the history
|
|
137710
137869
|
plan, and what will happen to the folder.
|
|
137711
137870
|
|
|
137712
|
-
Each document's provenance (generated, timestamp, actor, okf_version
|
|
137713
|
-
it, a claim rather than a verification
|
|
137714
|
-
|
|
137871
|
+
Each document's provenance (generated, timestamp, actor, okf_version, superbee_updated_by,
|
|
137872
|
+
verified) travels as its file states it, a claim rather than a verification, so the bundle reads
|
|
137873
|
+
on the host as it does here. The host still records the creation as yours, and stamps the writer
|
|
137874
|
+
itself on every later edit. The preview and the receipt count each field.
|
|
137715
137875
|
|
|
137716
137876
|
A bundle within one request's bounds (at most 1,000 documents, 100 other files of 1 MB each,
|
|
137717
137877
|
1,000 earlier versions, 1,500 objects in all, 3 MiB) is sent in one request. A larger one is sent
|