superbee 0.4.3-pre.6 → 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 +1085 -882
- 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.");
|
|
@@ -88299,17 +88325,16 @@ function monotonicEvolutionBlockers(current, desired, blockers) {
|
|
|
88299
88325
|
);
|
|
88300
88326
|
}
|
|
88301
88327
|
}
|
|
88302
|
-
const currentDeclared = /* @__PURE__ */ new Set([...current.fields.required, ...current.fields.optional]);
|
|
88303
88328
|
const desiredDeclared = /* @__PURE__ */ new Set([...desired.fields.required, ...desired.fields.optional]);
|
|
88304
88329
|
for (const [field, desiredValues] of Object.entries(desired.fields.values)) {
|
|
88305
88330
|
const currentValues = current.fields.values[field];
|
|
88306
88331
|
if (!currentValues) {
|
|
88307
|
-
if (desiredDeclared.has(field)
|
|
88332
|
+
if (desiredDeclared.has(field)) continue;
|
|
88308
88333
|
evolutionBlocker(
|
|
88309
88334
|
blockers,
|
|
88310
88335
|
"RECIPE_EVOLUTION_NON_MONOTONIC",
|
|
88311
88336
|
desired.id,
|
|
88312
|
-
`automatic evolution cannot add an enum restriction to '${field}' for '${desired.governs}';
|
|
88337
|
+
`automatic evolution cannot add an enum restriction to '${field}' for '${desired.governs}'; declare '${field}' in fields.required or fields.optional first`,
|
|
88313
88338
|
field
|
|
88314
88339
|
);
|
|
88315
88340
|
continue;
|
|
@@ -88368,6 +88393,20 @@ function monotonicEvolutionBlockers(current, desired, blockers) {
|
|
|
88368
88393
|
);
|
|
88369
88394
|
}
|
|
88370
88395
|
}
|
|
88396
|
+
function addedFieldRestrictions(current, desired) {
|
|
88397
|
+
const types = /* @__PURE__ */ new Map();
|
|
88398
|
+
for (const [field, type] of Object.entries(desired.fields.types ?? {})) {
|
|
88399
|
+
if (!Object.hasOwn(current.fields.types ?? {}, field)) types.set(field, type);
|
|
88400
|
+
}
|
|
88401
|
+
const currentDeclared = /* @__PURE__ */ new Set([...current.fields.required, ...current.fields.optional]);
|
|
88402
|
+
const values = /* @__PURE__ */ new Map();
|
|
88403
|
+
for (const [field, allowed] of Object.entries(desired.fields.values)) {
|
|
88404
|
+
if (!Object.hasOwn(current.fields.values, field) && currentDeclared.has(field)) {
|
|
88405
|
+
values.set(field, allowed.map((value) => String(value)));
|
|
88406
|
+
}
|
|
88407
|
+
}
|
|
88408
|
+
return { types, values };
|
|
88409
|
+
}
|
|
88371
88410
|
function addedLinkTypes(current, desired) {
|
|
88372
88411
|
const currentLinks = current.links ?? {};
|
|
88373
88412
|
return Object.keys(desired.links ?? {}).filter((linkType) => !Object.hasOwn(currentLinks, linkType));
|
|
@@ -88423,6 +88462,7 @@ async function prepareRecipeEvolution(bundle, sourceRecipe) {
|
|
|
88423
88462
|
const proofs = [];
|
|
88424
88463
|
const desiredKinds = /* @__PURE__ */ new Map();
|
|
88425
88464
|
const addedLinkTexts = /* @__PURE__ */ new Set();
|
|
88465
|
+
const addedRestrictions = /* @__PURE__ */ new Map();
|
|
88426
88466
|
const skippedConventions = [];
|
|
88427
88467
|
const installedConventions = await query2(bundle, { prefix: CONVENTIONS_PREFIX, type: "Convention" }, {
|
|
88428
88468
|
onSkip: (skipped) => skippedConventions.push(skipped)
|
|
@@ -88567,6 +88607,7 @@ async function prepareRecipeEvolution(bundle, sourceRecipe) {
|
|
|
88567
88607
|
});
|
|
88568
88608
|
if (!unchanged2 && existing.doc.frontmatter.type === "Convention" && parsedCurrent.ok && parsedCandidate.ok && parsedCurrent.kind.governs === parsedCandidate.kind.governs) {
|
|
88569
88609
|
monotonicEvolutionBlockers(parsedCurrent.kind, parsedCandidate.kind, blockers);
|
|
88610
|
+
addedRestrictions.set(parsedCandidate.kind.governs, addedFieldRestrictions(parsedCurrent.kind, parsedCandidate.kind));
|
|
88570
88611
|
for (const linkType of addedLinkTypes(parsedCurrent.kind, parsedCandidate.kind)) addedLinkTexts.add(linkType);
|
|
88571
88612
|
}
|
|
88572
88613
|
}
|
|
@@ -88651,6 +88692,8 @@ async function prepareRecipeEvolution(bundle, sourceRecipe) {
|
|
|
88651
88692
|
);
|
|
88652
88693
|
}
|
|
88653
88694
|
for (const kind2 of changingKinds) {
|
|
88695
|
+
const added = addedRestrictions.get(kind2.governs);
|
|
88696
|
+
const violations = /* @__PURE__ */ new Map();
|
|
88654
88697
|
for (const doc2 of docs) {
|
|
88655
88698
|
if (String(doc2.frontmatter.type) !== kind2.governs) continue;
|
|
88656
88699
|
instancesChecked += 1;
|
|
@@ -88659,16 +88702,39 @@ async function prepareRecipeEvolution(bundle, sourceRecipe) {
|
|
|
88659
88702
|
id: doc2.id,
|
|
88660
88703
|
current_version: contentVersion(doc2)
|
|
88661
88704
|
});
|
|
88662
|
-
|
|
88663
|
-
|
|
88664
|
-
|
|
88665
|
-
|
|
88666
|
-
|
|
88667
|
-
|
|
88668
|
-
|
|
88669
|
-
|
|
88705
|
+
const stored = validateAgainstKind(doc2, kind2);
|
|
88706
|
+
const projected = projectKindValidationWarnings(okfVersion, kind2, stored);
|
|
88707
|
+
for (const [index2, warning] of projected.entries()) {
|
|
88708
|
+
const field = stored[index2].field ?? "";
|
|
88709
|
+
const restriction = warning.code === "KIND_FIELD_TYPE" && added?.types.has(field) ? `fields.types.${field}` : (warning.code === "KIND_FIELD_VALUE" || warning.code === "KIND_FIELD_ARITY") && added?.values.has(field) ? `fields.values.${field}` : void 0;
|
|
88710
|
+
if (restriction === void 0) {
|
|
88711
|
+
evolutionBlocker(
|
|
88712
|
+
blockers,
|
|
88713
|
+
"RECIPE_EVOLUTION_INSTANCE_INVALID",
|
|
88714
|
+
doc2.id,
|
|
88715
|
+
warning.message,
|
|
88716
|
+
warning.field
|
|
88717
|
+
);
|
|
88718
|
+
continue;
|
|
88719
|
+
}
|
|
88720
|
+
const entry = violations.get(restriction) ?? { field, docs: /* @__PURE__ */ new Map() };
|
|
88721
|
+
if (!entry.docs.has(doc2.id)) entry.docs.set(doc2.id, warning.message);
|
|
88722
|
+
violations.set(restriction, entry);
|
|
88670
88723
|
}
|
|
88671
88724
|
}
|
|
88725
|
+
for (const [restriction, { field, docs: violating }] of violations) {
|
|
88726
|
+
const ids = [...violating.keys()].sort();
|
|
88727
|
+
const named = ids.slice(0, RESTRICTION_BLOCKER_DOC_CAP).join(", ");
|
|
88728
|
+
const more = ids.length > RESTRICTION_BLOCKER_DOC_CAP ? `, and ${ids.length - RESTRICTION_BLOCKER_DOC_CAP} more` : "";
|
|
88729
|
+
const declared = restriction.startsWith("fields.types.") ? `type '${added.types.get(field)}'` : `allowed values [${added.values.get(field).join(", ")}]`;
|
|
88730
|
+
evolutionBlocker(
|
|
88731
|
+
blockers,
|
|
88732
|
+
"RECIPE_EVOLUTION_RESTRICTION_VIOLATED",
|
|
88733
|
+
kind2.id,
|
|
88734
|
+
`adding ${declared} to '${field}' for '${kind2.governs}' would make ${ids.length} existing document(s) invalid: ${named}${more}; fix them first (e.g. ${ids[0]}: ${violating.get(ids[0])})`,
|
|
88735
|
+
restriction
|
|
88736
|
+
);
|
|
88737
|
+
}
|
|
88672
88738
|
}
|
|
88673
88739
|
if (addedLinkTexts.size > 0) {
|
|
88674
88740
|
const proven = new Set(instanceProofs.map((proof) => proof.id));
|
|
@@ -88836,7 +88902,7 @@ async function applyRecipeEvolution(bundle, recipe2, expectedPlanToken, actor) {
|
|
|
88836
88902
|
counts: { updated, unchanged: completed.length - updated }
|
|
88837
88903
|
};
|
|
88838
88904
|
}
|
|
88839
|
-
var RECIPE_EVOLUTION_WRITE_REGISTRY, PRESERVED_ROOT_METADATA, BUNDLE_LOCAL_PLACEMENT;
|
|
88905
|
+
var RECIPE_EVOLUTION_WRITE_REGISTRY, PRESERVED_ROOT_METADATA, BUNDLE_LOCAL_PLACEMENT, RESTRICTION_BLOCKER_DOC_CAP;
|
|
88840
88906
|
var init_recipe_evolution = __esm({
|
|
88841
88907
|
"../cli/src/recipe-evolution.ts"() {
|
|
88842
88908
|
"use strict";
|
|
@@ -88849,6 +88915,7 @@ var init_recipe_evolution = __esm({
|
|
|
88849
88915
|
RECIPE_EVOLUTION_WRITE_REGISTRY = { kinds: /* @__PURE__ */ new Map(), warnings: [] };
|
|
88850
88916
|
PRESERVED_ROOT_METADATA = /* @__PURE__ */ new Set(["timestamp", "actor", "generated", "verified", "superbee_updated_by"]);
|
|
88851
88917
|
BUNDLE_LOCAL_PLACEMENT = /* @__PURE__ */ new Set(["order"]);
|
|
88918
|
+
RESTRICTION_BLOCKER_DOC_CAP = 10;
|
|
88852
88919
|
}
|
|
88853
88920
|
});
|
|
88854
88921
|
|
|
@@ -89036,7 +89103,9 @@ it compares definitions, permits additive declarations only, revalidates existin
|
|
|
89036
89103
|
returns one exact apply command. Applying requires that plan's token, recomputes the preflight, and
|
|
89037
89104
|
writes each changed convention with exact-version CAS. Concurrent non-target changes are detected
|
|
89038
89105
|
by a postcondition check and reported with completed work. It never replaces existing values,
|
|
89039
|
-
active View assets, or artifacts omitted from the source.
|
|
89106
|
+
active View assets, or artifacts omitted from the source. It may add a field type (fields.types) or
|
|
89107
|
+
an enum (fields.values) to a declared field only when every existing instance conforms; otherwise
|
|
89108
|
+
the plan names the violating documents. It never changes or removes a type or an enum.
|
|
89040
89109
|
|
|
89041
89110
|
Options:
|
|
89042
89111
|
--dir <path> Bundle directory (default: discovered from the cwd)
|
|
@@ -134329,9 +134398,806 @@ var init_publish_state = __esm({
|
|
|
134329
134398
|
}
|
|
134330
134399
|
});
|
|
134331
134400
|
|
|
134332
|
-
// ../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";
|
|
134333
134405
|
import path54 from "node:path";
|
|
134334
|
-
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";
|
|
134335
135201
|
function readLeaf(file2) {
|
|
134336
135202
|
const read = readRegularFileNoFollowSync(file2);
|
|
134337
135203
|
if (read.state === "missing") return null;
|
|
@@ -134354,11 +135220,11 @@ async function withCheckoutLock(folder, body) {
|
|
|
134354
135220
|
});
|
|
134355
135221
|
}
|
|
134356
135222
|
async function adopt(folderArg, options, deps2, mode) {
|
|
134357
|
-
const folder =
|
|
135223
|
+
const folder = path55.resolve(deps2.cwd, folderArg);
|
|
134358
135224
|
let canonical2;
|
|
134359
135225
|
try {
|
|
134360
|
-
canonical2 = await
|
|
134361
|
-
if (!(await
|
|
135226
|
+
canonical2 = await realpath12(folder);
|
|
135227
|
+
if (!(await lstat9(canonical2)).isDirectory()) throw new Error("not a folder");
|
|
134362
135228
|
} catch {
|
|
134363
135229
|
throw new CliError("NOT_FOUND", `${folder} is not a folder`, { help: `${cliInvocation()} checkout --adopt <folder> --host <url>` });
|
|
134364
135230
|
}
|
|
@@ -134373,7 +135239,7 @@ async function adopt(folderArg, options, deps2, mode) {
|
|
|
134373
135239
|
}
|
|
134374
135240
|
if (options.workspace !== void 0 && await rebindToWorkspace(canonical2, bound, options, deps2, mode)) return;
|
|
134375
135241
|
const marker2 = await writeCheckoutMarker(canonical2, bound).catch(() => null);
|
|
134376
|
-
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));
|
|
134377
135243
|
return;
|
|
134378
135244
|
}
|
|
134379
135245
|
const folderMarker = readCheckoutMarker(canonical2);
|
|
@@ -134413,7 +135279,7 @@ async function adopt(folderArg, options, deps2, mode) {
|
|
|
134413
135279
|
...marker2 ? { marker: "written" } : {},
|
|
134414
135280
|
catalog: relocated ? { relocated: true, label: relocated.label, id: relocated.id } : { relocated: false, note: "no catalog entry named the old folder" },
|
|
134415
135281
|
...options.workspace !== void 0 ? { workspace_flag: "ignored: a moved checkout keeps the workspace it was bound with" } : {},
|
|
134416
|
-
help: [...
|
|
135282
|
+
help: [...nextSteps2(canonical2), `${cliInvocation()} sync --dir ${commandToken(canonical2)}`]
|
|
134417
135283
|
},
|
|
134418
135284
|
mode
|
|
134419
135285
|
)
|
|
@@ -134465,7 +135331,7 @@ async function adopt(folderArg, options, deps2, mode) {
|
|
|
134465
135331
|
const typed = { slug: marker.workspace_slug ?? null, bundleId: bundleId2 };
|
|
134466
135332
|
await assertStandaloneFolder(canonical2);
|
|
134467
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``}`;
|
|
134468
|
-
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 });
|
|
134469
135335
|
if (typed.slug === null && connection.reference.slug !== null) {
|
|
134470
135336
|
const found = readBundleListing(await connection.client.bundles()).lookup({ slug: null, bundleId: bundleId2 });
|
|
134471
135337
|
if (found.holders > 1) {
|
|
@@ -134477,8 +135343,12 @@ async function adopt(folderArg, options, deps2, mode) {
|
|
|
134477
135343
|
}
|
|
134478
135344
|
}
|
|
134479
135345
|
const published = await readPublishedExtras(home2, canonical2);
|
|
134480
|
-
const
|
|
134481
|
-
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);
|
|
134482
135352
|
if (published) await clearPublishedExtras(home2, canonical2);
|
|
134483
135353
|
const cataloged = await registerInCatalog(home2, result3.binding);
|
|
134484
135354
|
const syncHelp = `${cliInvocation()} sync --dir ${commandToken(canonical2)}`;
|
|
@@ -134492,8 +135362,9 @@ async function adopt(folderArg, options, deps2, mode) {
|
|
|
134492
135362
|
...result3.conflicts.length > 0 ? { conflicts: listed(result3.conflicts) } : {},
|
|
134493
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" } : {},
|
|
134494
135364
|
root_index: result3.rootIndex,
|
|
135365
|
+
...result3.otherFiles ? { other_files: result3.otherFiles, warning: "files other than documents were not checked against the host" } : {},
|
|
134495
135366
|
...result3.markerWritten ? { marker: "written" } : {},
|
|
134496
|
-
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)]
|
|
134497
135368
|
},
|
|
134498
135369
|
mode
|
|
134499
135370
|
)
|
|
@@ -134508,7 +135379,7 @@ async function rebindToWorkspace(canonical2, bound, options, deps2, mode) {
|
|
|
134508
135379
|
}
|
|
134509
135380
|
const target3 = resolveHostedTarget(options.host);
|
|
134510
135381
|
const resume2 = commandFragment`${cliInvocation()} checkout --adopt ${commandToken(canonical2)} --host ${commandToken(bindingHostArgument(target3))} --workspace ${commandToken(options.workspace)}${options.json ? commandFragment` --json` : commandFragment``}`;
|
|
134511
|
-
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 });
|
|
134512
135383
|
const slug = connection.reference.slug;
|
|
134513
135384
|
if (slug === null || slug === (bound.workspace_slug ?? null)) return false;
|
|
134514
135385
|
if (connection.identity.principalId !== bound.principal_id) {
|
|
@@ -134557,7 +135428,8 @@ async function rebindToWorkspace(canonical2, bound, options, deps2, mode) {
|
|
|
134557
135428
|
await releaseCheckout(home2, bound);
|
|
134558
135429
|
return pending;
|
|
134559
135430
|
});
|
|
134560
|
-
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);
|
|
134561
135433
|
deps2.stdout(
|
|
134562
135434
|
render(
|
|
134563
135435
|
{
|
|
@@ -134571,7 +135443,7 @@ async function rebindToWorkspace(canonical2, bound, options, deps2, mode) {
|
|
|
134571
135443
|
documents: { placed: result3.placed.length, matched: result3.matched.length, conflicts: result3.conflicts.length, local_only: result3.localOnly.length },
|
|
134572
135444
|
...result3.conflicts.length > 0 ? { conflicts: listed(result3.conflicts) } : {},
|
|
134573
135445
|
...result3.localOnly.length > 0 ? { local_only: listed(result3.localOnly) } : {},
|
|
134574
|
-
help: [`${cliInvocation()} sync --dir ${commandToken(canonical2)}`, ...
|
|
135446
|
+
help: [`${cliInvocation()} sync --dir ${commandToken(canonical2)}`, ...nextSteps2(canonical2)]
|
|
134575
135447
|
},
|
|
134576
135448
|
mode
|
|
134577
135449
|
)
|
|
@@ -134629,7 +135501,7 @@ async function bindFolderInPlace(input) {
|
|
|
134629
135501
|
const hostIds = /* @__PURE__ */ new Set();
|
|
134630
135502
|
for (const head of heads) {
|
|
134631
135503
|
hostIds.add(head.id);
|
|
134632
|
-
const file2 =
|
|
135504
|
+
const file2 = path55.join(canonical2, pathFromConceptId(head.id));
|
|
134633
135505
|
if (await parentUnsafe(canonical2, file2)) {
|
|
134634
135506
|
conflicts.push(head.id);
|
|
134635
135507
|
continue;
|
|
@@ -134663,12 +135535,12 @@ async function bindFolderInPlace(input) {
|
|
|
134663
135535
|
if (index2) {
|
|
134664
135536
|
const hostRoot2 = Buffer.from(index2.content, "utf8");
|
|
134665
135537
|
root = digestOf(hostRoot2);
|
|
134666
|
-
const current = readLeaf(
|
|
135538
|
+
const current = readLeaf(path55.join(canonical2, ROOT_INDEX));
|
|
134667
135539
|
if (current === "unsafe") rootIndex = "kept (not a plain file; sync holds it)";
|
|
134668
135540
|
else if (current === null) {
|
|
134669
|
-
const placedRoot = (await placeNew(
|
|
135541
|
+
const placedRoot = (await placeNew(path55.join(canonical2, ROOT_INDEX), hostRoot2)).placed;
|
|
134670
135542
|
if (placedRoot) {
|
|
134671
|
-
placedFiles.set(
|
|
135543
|
+
placedFiles.set(path55.join(canonical2, ROOT_INDEX), root);
|
|
134672
135544
|
rootBase = index2.version;
|
|
134673
135545
|
}
|
|
134674
135546
|
rootIndex = placedRoot ? "placed" : "kept";
|
|
@@ -134703,6 +135575,25 @@ async function bindFolderInPlace(input) {
|
|
|
134703
135575
|
}
|
|
134704
135576
|
});
|
|
134705
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
|
+
}
|
|
134706
135597
|
var LISTED;
|
|
134707
135598
|
var init_checkout_adopt = __esm({
|
|
134708
135599
|
"../cli/src/commands/checkout-adopt.ts"() {
|
|
@@ -134730,24 +135621,25 @@ var init_checkout_adopt = __esm({
|
|
|
134730
135621
|
init_sync_scan();
|
|
134731
135622
|
init_checkout_store();
|
|
134732
135623
|
init_checkout();
|
|
135624
|
+
init_export();
|
|
134733
135625
|
LISTED = 20;
|
|
134734
135626
|
}
|
|
134735
135627
|
});
|
|
134736
135628
|
|
|
134737
135629
|
// ../cli/src/commands/checkout.ts
|
|
134738
|
-
import { homedir as
|
|
134739
|
-
import { lstat as
|
|
134740
|
-
import
|
|
134741
|
-
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";
|
|
134742
135634
|
function checkoutDeps(partial2) {
|
|
134743
135635
|
return {
|
|
134744
135636
|
stdout: partial2.stdout ?? ((text5) => void process.stdout.write(text5)),
|
|
134745
|
-
auth: partial2.auth ?? defaultHostedAuthDeps(
|
|
135637
|
+
auth: partial2.auth ?? defaultHostedAuthDeps(homedir40()),
|
|
134746
135638
|
cwd: partial2.cwd ?? process.cwd(),
|
|
134747
135639
|
...partial2.fetch ? { fetch: partial2.fetch } : {}
|
|
134748
135640
|
};
|
|
134749
135641
|
}
|
|
134750
|
-
async function
|
|
135642
|
+
async function entryKind2(target3) {
|
|
134751
135643
|
let info;
|
|
134752
135644
|
try {
|
|
134753
135645
|
info = await stat8(target3);
|
|
@@ -134756,7 +135648,7 @@ async function entryKind(target3) {
|
|
|
134756
135648
|
throw error51;
|
|
134757
135649
|
}
|
|
134758
135650
|
if (!info.isDirectory()) return "other";
|
|
134759
|
-
return (await
|
|
135651
|
+
return (await readdir9(target3)).length === 0 ? "empty-dir" : "dir";
|
|
134760
135652
|
}
|
|
134761
135653
|
function bindingView(binding) {
|
|
134762
135654
|
return {
|
|
@@ -134769,11 +135661,11 @@ function bindingView(binding) {
|
|
|
134769
135661
|
folder: binding.path
|
|
134770
135662
|
};
|
|
134771
135663
|
}
|
|
134772
|
-
function
|
|
135664
|
+
function nextSteps2(folder) {
|
|
134773
135665
|
return [`${cliInvocation()} list --dir ${commandToken(folder)}`, `${cliInvocation()} status --dir ${commandToken(folder)}`];
|
|
134774
135666
|
}
|
|
134775
135667
|
async function assertStandaloneFolder(folder) {
|
|
134776
|
-
const parent =
|
|
135668
|
+
const parent = path56.dirname(folder);
|
|
134777
135669
|
const enclosing = await findBundleRoot(parent).catch(() => null);
|
|
134778
135670
|
if (enclosing) {
|
|
134779
135671
|
throw new CliError("FORBIDDEN", `refusing to check out inside the local bundle at ${enclosing}`, {
|
|
@@ -134832,17 +135724,17 @@ function lockFailure2(error51, folder) {
|
|
|
134832
135724
|
return error51;
|
|
134833
135725
|
}
|
|
134834
135726
|
async function canonicalFolder(folder) {
|
|
134835
|
-
const parent =
|
|
134836
|
-
await
|
|
135727
|
+
const parent = path56.dirname(folder);
|
|
135728
|
+
await mkdir5(parent, { recursive: true });
|
|
134837
135729
|
let link4 = false;
|
|
134838
135730
|
try {
|
|
134839
|
-
link4 = (await
|
|
135731
|
+
link4 = (await lstat10(folder)).isSymbolicLink();
|
|
134840
135732
|
} catch (error51) {
|
|
134841
135733
|
if (error51.code !== "ENOENT") throw error51;
|
|
134842
135734
|
}
|
|
134843
|
-
if (!link4) return
|
|
135735
|
+
if (!link4) return path56.join(await realpath13(parent), path56.basename(folder));
|
|
134844
135736
|
try {
|
|
134845
|
-
return await
|
|
135737
|
+
return await realpath13(folder);
|
|
134846
135738
|
} catch {
|
|
134847
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>" });
|
|
134848
135740
|
}
|
|
@@ -134873,25 +135765,25 @@ async function removePlaced(folder, placed, createdFolder) {
|
|
|
134873
135765
|
const dirs = /* @__PURE__ */ new Set();
|
|
134874
135766
|
for (const [file2, digest3] of placed) {
|
|
134875
135767
|
try {
|
|
134876
|
-
if (digestOf(await readFile5(file2)) === digest3) await
|
|
135768
|
+
if (digestOf(await readFile5(file2)) === digest3) await unlink14(file2);
|
|
134877
135769
|
} catch {
|
|
134878
135770
|
}
|
|
134879
|
-
for (let dir =
|
|
135771
|
+
for (let dir = path56.dirname(file2); dir !== folder && dir.startsWith(`${folder}${path56.sep}`); dir = path56.dirname(dir)) dirs.add(dir);
|
|
134880
135772
|
}
|
|
134881
135773
|
for (const dir of [...dirs].sort((a, b) => b.length - a.length)) await rmdir3(dir).catch(() => {
|
|
134882
135774
|
});
|
|
134883
135775
|
if (createdFolder) await rmdir3(folder).catch(() => {
|
|
134884
135776
|
});
|
|
134885
|
-
return await
|
|
135777
|
+
return await entryKind2(folder) !== "dir";
|
|
134886
135778
|
}
|
|
134887
135779
|
async function release(folderArg, deps2, mode) {
|
|
134888
|
-
const folder =
|
|
135780
|
+
const folder = path56.resolve(deps2.cwd, folderArg);
|
|
134889
135781
|
let canonical2;
|
|
134890
135782
|
try {
|
|
134891
|
-
canonical2 = (await
|
|
135783
|
+
canonical2 = (await lstat10(folder)).isSymbolicLink() ? await realpath13(folder) : path56.join(await realpath13(path56.dirname(folder)), path56.basename(folder));
|
|
134892
135784
|
} catch {
|
|
134893
135785
|
try {
|
|
134894
|
-
canonical2 =
|
|
135786
|
+
canonical2 = path56.join(await realpath13(path56.dirname(folder)), path56.basename(folder));
|
|
134895
135787
|
} catch {
|
|
134896
135788
|
canonical2 = folder;
|
|
134897
135789
|
}
|
|
@@ -134928,7 +135820,7 @@ async function release(folderArg, deps2, mode) {
|
|
|
134928
135820
|
}
|
|
134929
135821
|
async function checkoutHost(flag, folder, typed, values, home2, env, fetchImpl) {
|
|
134930
135822
|
if (flag === void 0) {
|
|
134931
|
-
const real = await
|
|
135823
|
+
const real = await realpath13(folder).catch(() => null);
|
|
134932
135824
|
const bound = real ? await bindingForPath(home2, real).catch(() => null) : null;
|
|
134933
135825
|
if (bound) return { target: checkoutTarget(bound), workspace: values.workspace, origin: {} };
|
|
134934
135826
|
}
|
|
@@ -134942,7 +135834,7 @@ async function checkoutHost(flag, folder, typed, values, home2, env, fetchImpl)
|
|
|
134942
135834
|
async function checkout(argv2, partial2 = {}) {
|
|
134943
135835
|
const deps2 = checkoutDeps(partial2);
|
|
134944
135836
|
const { values, positionals } = parseLeafOrUsage(
|
|
134945
|
-
() =>
|
|
135837
|
+
() => parseArgs42({
|
|
134946
135838
|
args: argv2,
|
|
134947
135839
|
options: {
|
|
134948
135840
|
host: { type: "string" },
|
|
@@ -134984,21 +135876,21 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
134984
135876
|
throw new CliError("USAGE", `'${positionals[0]}' is not a hosted bundle id or <workspace>/<bundle-id>`, { help: `${cliInvocation()} checkout --help` });
|
|
134985
135877
|
}
|
|
134986
135878
|
const bundleId2 = typed.bundleId;
|
|
134987
|
-
const folder =
|
|
135879
|
+
const folder = path56.resolve(deps2.cwd, values.dir ?? bundleId2);
|
|
134988
135880
|
assertBundleOutsidePrivateState(folder, deps2.auth.home);
|
|
134989
135881
|
const { target: target3, workspace: workspaceFlag2, origin: hostOrigin } = await checkoutHost(values.host, folder, typed, values, deps2.auth.home, deps2.auth.env, deps2.auth.fetch);
|
|
134990
135882
|
const prefix = syncRoutePrefix(target3);
|
|
134991
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``}`;
|
|
134992
|
-
const found = await
|
|
135884
|
+
const found = await entryKind2(folder);
|
|
134993
135885
|
if (found === "other") {
|
|
134994
135886
|
throw new CliError("ALREADY_EXISTS", `${folder} exists and is not a folder`, { help: "pass --dir <new folder>" });
|
|
134995
135887
|
}
|
|
134996
135888
|
if (found === "dir") {
|
|
134997
|
-
const existing = await bindingForPath(deps2.auth.home, await
|
|
135889
|
+
const existing = await bindingForPath(deps2.auth.home, await realpath13(folder));
|
|
134998
135890
|
const sameWorkspace = workspaceFlag2 === void 0 || workspaceFlag2 === existing?.workspace_slug || workspaceFlag2 === existing?.workspace;
|
|
134999
135891
|
const asked = typed.slug === null && sameWorkspace ? { slug: existing?.workspace_slug ?? null, bundleId: bundleId2 } : typed;
|
|
135000
135892
|
if (existing && bindsBundle(existing, target3, asked)) {
|
|
135001
|
-
deps2.stdout(render({ checkout: "unchanged", ...bindingView(existing), help:
|
|
135893
|
+
deps2.stdout(render({ checkout: "unchanged", ...bindingView(existing), help: nextSteps2(existing.path) }, mode));
|
|
135002
135894
|
return;
|
|
135003
135895
|
}
|
|
135004
135896
|
const marker = existing ? null : readCheckoutMarker(folder);
|
|
@@ -135009,7 +135901,7 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135009
135901
|
});
|
|
135010
135902
|
}
|
|
135011
135903
|
await assertStandaloneFolder(folder);
|
|
135012
|
-
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 });
|
|
135013
135905
|
const canonical2 = await canonicalFolder(folder);
|
|
135014
135906
|
let createdFolder = false;
|
|
135015
135907
|
const placed = /* @__PURE__ */ new Map();
|
|
@@ -135021,7 +135913,7 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135021
135913
|
help: "wait for it to finish, then retry the same command"
|
|
135022
135914
|
});
|
|
135023
135915
|
}
|
|
135024
|
-
const kind2 = await
|
|
135916
|
+
const kind2 = await entryKind2(canonical2);
|
|
135025
135917
|
if (kind2 === "dir" || kind2 === "other") {
|
|
135026
135918
|
throw new CliError("ALREADY_EXISTS", `${canonical2} is no longer empty`, { details: { reason: "not_empty", folder: canonical2 }, help: "pass --dir <new or empty folder>" });
|
|
135027
135919
|
}
|
|
@@ -135031,7 +135923,7 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135031
135923
|
await releaseCheckout(deps2.auth.home, replaced);
|
|
135032
135924
|
}
|
|
135033
135925
|
if (kind2 === "absent") {
|
|
135034
|
-
await
|
|
135926
|
+
await mkdir5(canonical2);
|
|
135035
135927
|
createdFolder = true;
|
|
135036
135928
|
}
|
|
135037
135929
|
const identityNow = await folderIdentity(canonical2);
|
|
@@ -135066,9 +135958,12 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135066
135958
|
const stored = await store.readHeads({ project: (head) => head.id });
|
|
135067
135959
|
assertProjectable(stored, bundleId2, target3);
|
|
135068
135960
|
const exported = await exportFresh(store, canonical2, (file2, digest3) => placed.set(file2, digest3));
|
|
135069
|
-
|
|
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) {
|
|
135070
135965
|
throw new CliError("CONFLICT", `files appeared in ${canonical2} during checkout`, {
|
|
135071
|
-
details: { reason: "folder_changed", kept:
|
|
135966
|
+
details: { reason: "folder_changed", kept: kept.slice(0, 20), kept_total: kept.length },
|
|
135072
135967
|
help: "retry the same command once nothing else writes to the folder"
|
|
135073
135968
|
});
|
|
135074
135969
|
}
|
|
@@ -135079,14 +135974,14 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135079
135974
|
if (id !== ROOT_INDEX && version4) files[id] = { digest: digest3, version: version4 };
|
|
135080
135975
|
}
|
|
135081
135976
|
const rootBase = exported.exported[ROOT_INDEX] !== void 0 ? (await store.readReserved("", "index.md"))?.version ?? null : null;
|
|
135082
|
-
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 });
|
|
135083
135978
|
const markerFile = await writeCheckoutMarker(canonical2, binding);
|
|
135084
135979
|
if (markerFile) placed.set(markerFile, digestOf(checkoutMarkerBytes(binding)));
|
|
135085
135980
|
const ready = { ...binding, state: "ready" };
|
|
135086
135981
|
await writeBinding(deps2.auth.home, ready);
|
|
135087
135982
|
await recordPulled(deps2.auth.home, ready.checkout_id);
|
|
135088
135983
|
await indexCheckoutPath(deps2.auth.home, ready);
|
|
135089
|
-
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 };
|
|
135090
135985
|
} catch (error51) {
|
|
135091
135986
|
await store?.close().catch(() => {
|
|
135092
135987
|
});
|
|
@@ -135119,22 +136014,96 @@ async function checkout(argv2, partial2 = {}) {
|
|
|
135119
136014
|
catalog: cataloged,
|
|
135120
136015
|
documents: result3.documents,
|
|
135121
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
|
+
},
|
|
135122
136021
|
heads_digest: result3.digest,
|
|
135123
136022
|
...replacedBinding ? { replaced_stale_checkout: { bundle_id: replacedBinding.bundle_id, host: replacedBinding.origin } } : {},
|
|
135124
136023
|
mode: "sync",
|
|
135125
136024
|
refused: REFUSED_SUMMARY,
|
|
135126
|
-
help:
|
|
136025
|
+
help: nextSteps2(result3.binding.path)
|
|
135127
136026
|
},
|
|
135128
136027
|
mode
|
|
135129
136028
|
)
|
|
135130
136029
|
);
|
|
135131
136030
|
}
|
|
135132
|
-
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 = {}) {
|
|
135133
136097
|
const { bundleId: bundleId2 } = typed;
|
|
135134
136098
|
const command = (named) => `${cliInvocation()} checkout ${commandToken(named)} --host ${commandToken(hostArgument(target3))}`;
|
|
135135
136099
|
const account = await connectHostedAccount(
|
|
135136
136100
|
target3,
|
|
135137
|
-
{
|
|
136101
|
+
{
|
|
136102
|
+
workspace: workspaceFlag2,
|
|
136103
|
+
resume: resume2,
|
|
136104
|
+
otherWorkspace: `${command(hostedBundleReferenceText(typed))} --workspace <id>`,
|
|
136105
|
+
...options.deadlineMs !== void 0 ? { deadlineMs: options.deadlineMs } : {}
|
|
136106
|
+
},
|
|
135138
136107
|
deps2
|
|
135139
136108
|
);
|
|
135140
136109
|
const { client, identity, workspace } = account;
|
|
@@ -135218,7 +136187,7 @@ function assertProjectable(ids, bundleId2, target3) {
|
|
|
135218
136187
|
});
|
|
135219
136188
|
}
|
|
135220
136189
|
}
|
|
135221
|
-
var CHECKOUT_DOCUMENT_LIMIT, CHECKOUT_USAGE, REFUSED_SUMMARY;
|
|
136190
|
+
var CHECKOUT_DOCUMENT_LIMIT, CHECKOUT_USAGE, NO_EXTRAS, REFUSED_SUMMARY;
|
|
135222
136191
|
var init_checkout = __esm({
|
|
135223
136192
|
"../cli/src/commands/checkout.ts"() {
|
|
135224
136193
|
"use strict";
|
|
@@ -135236,6 +136205,7 @@ var init_checkout = __esm({
|
|
|
135236
136205
|
init_invocation();
|
|
135237
136206
|
init_output2();
|
|
135238
136207
|
init_private_state_bundle_boundary();
|
|
136208
|
+
init_nofollow_read();
|
|
135239
136209
|
init_session();
|
|
135240
136210
|
init_account();
|
|
135241
136211
|
init_freshness2();
|
|
@@ -135247,6 +136217,7 @@ var init_checkout = __esm({
|
|
|
135247
136217
|
init_catalog();
|
|
135248
136218
|
init_marker();
|
|
135249
136219
|
init_checkout_adopt();
|
|
136220
|
+
init_export();
|
|
135250
136221
|
init_reference();
|
|
135251
136222
|
CHECKOUT_DOCUMENT_LIMIT = 1e4;
|
|
135252
136223
|
CHECKOUT_USAGE = `superbee checkout \u2014 mirror a hosted bundle into a local folder
|
|
@@ -135262,8 +136233,11 @@ hosted bundle into --dir (default: ./<bundle-id>), which must be new or empty. T
|
|
|
135262
136233
|
else, for a folder that is already a checkout, its own host, else the host of your last sign-in
|
|
135263
136234
|
when it is the only host you are signed in to (never SUPERBEE_HOST alone); signed in to more than
|
|
135264
136235
|
one host, a checkout without --host is refused and names them. The receipt names the host it
|
|
135265
|
-
bound. The folder holds plain bundle files, so every command runs on it with --dir <folder
|
|
135266
|
-
|
|
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.
|
|
135267
136241
|
Re-running for the same folder and bundle is a no-op. A checkout whose folder was deleted, or
|
|
135268
136242
|
replaced by a new empty folder, is replaced, unless it holds changes sync has not sent yet; a
|
|
135269
136243
|
checkout emptied in place is refused, because removing every file is a pending edit.
|
|
@@ -135321,6 +136295,7 @@ Examples:
|
|
|
135321
136295
|
superbee checkout --adopt ~/restored/team --host https://mcp.getsuperbee.com
|
|
135322
136296
|
superbee checkout --release ~/work/team
|
|
135323
136297
|
`;
|
|
136298
|
+
NO_EXTRAS = Object.freeze({ placed: {}, reserved: 0, other: 0, kept: [] });
|
|
135324
136299
|
REFUSED_SUMMARY = Object.freeze(
|
|
135325
136300
|
HOSTED_CHECKOUT_REFUSALS.filter((row4) => row4.reason !== "checkout_target").map((row4) => {
|
|
135326
136301
|
const words = row4.words.join(" ");
|
|
@@ -135330,800 +136305,6 @@ Examples:
|
|
|
135330
136305
|
}
|
|
135331
136306
|
});
|
|
135332
136307
|
|
|
135333
|
-
// ../cli/src/commands/export.ts
|
|
135334
|
-
import { randomBytes as randomBytes9 } from "node:crypto";
|
|
135335
|
-
import { homedir as homedir40 } from "node:os";
|
|
135336
|
-
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";
|
|
135337
|
-
import path56 from "node:path";
|
|
135338
|
-
import { parseArgs as parseArgs42 } from "node:util";
|
|
135339
|
-
function exportDeps(partial2) {
|
|
135340
|
-
return {
|
|
135341
|
-
stdout: partial2.stdout ?? ((text5) => void process.stdout.write(text5)),
|
|
135342
|
-
auth: partial2.auth ?? defaultHostedAuthDeps(homedir40()),
|
|
135343
|
-
cwd: partial2.cwd ?? process.cwd(),
|
|
135344
|
-
...partial2.fetch ? { fetch: partial2.fetch } : {}
|
|
135345
|
-
};
|
|
135346
|
-
}
|
|
135347
|
-
async function entryKind2(target3) {
|
|
135348
|
-
let info;
|
|
135349
|
-
try {
|
|
135350
|
-
info = await lstat10(target3);
|
|
135351
|
-
} catch (error51) {
|
|
135352
|
-
if (error51.code === "ENOENT") return "absent";
|
|
135353
|
-
throw error51;
|
|
135354
|
-
}
|
|
135355
|
-
if (!info.isDirectory()) return "other";
|
|
135356
|
-
return (await readdir9(target3)).length === 0 ? "empty-dir" : "dir";
|
|
135357
|
-
}
|
|
135358
|
-
function shown(paths) {
|
|
135359
|
-
return [...paths].sort().slice(0, SHOWN);
|
|
135360
|
-
}
|
|
135361
|
-
function refusalCode(body) {
|
|
135362
|
-
const error51 = body?.error;
|
|
135363
|
-
if (typeof error51 === "string") return error51;
|
|
135364
|
-
const code3 = error51?.code;
|
|
135365
|
-
return typeof code3 === "string" ? code3 : void 0;
|
|
135366
|
-
}
|
|
135367
|
-
async function namedClient(source, deps2, resume2) {
|
|
135368
|
-
const { target: target3 } = source;
|
|
135369
|
-
const account = await connectHostedAccount(target3, { ...source.workspace !== null ? { workspace: source.workspace } : {}, resume: resume2, deadlineMs: EXPORT_DEADLINE_MS }, deps2);
|
|
135370
|
-
const { reference } = resolveBundleReference(
|
|
135371
|
-
{ slug: source.slug, bundleId: source.bundleId },
|
|
135372
|
-
account,
|
|
135373
|
-
target3,
|
|
135374
|
-
(named) => `${cliInvocation()} export ${commandToken(named)} --host ${commandToken(hostArgument(target3))} --to <folder>`
|
|
135375
|
-
);
|
|
135376
|
-
return { client: reference.slug === null ? account.client : account.client.within(reference.slug), slug: reference.slug };
|
|
135377
|
-
}
|
|
135378
|
-
async function fetchExport(source, deps2, resume2) {
|
|
135379
|
-
const { bundleId: bundleId2, target: target3 } = source;
|
|
135380
|
-
const named = source.binding ? null : await namedClient(source, deps2, resume2);
|
|
135381
|
-
const client = named ? named.client : (await connectCheckout(source.binding, { resume: resume2, deadlineMs: EXPORT_DEADLINE_MS }, deps2)).client;
|
|
135382
|
-
const slug = named ? named.slug : source.binding.workspace_slug ?? null;
|
|
135383
|
-
const request3 = { client, slug, source, resume: resume2 };
|
|
135384
|
-
for (let attempt = 1; ; attempt += 1) {
|
|
135385
|
-
const chain = new ExportPageChain();
|
|
135386
|
-
let bytes = 0;
|
|
135387
|
-
let cursor;
|
|
135388
|
-
let moved = false;
|
|
135389
|
-
for (; ; ) {
|
|
135390
|
-
let page;
|
|
135391
|
-
for (let tries = 1; page === void 0; tries += 1) {
|
|
135392
|
-
const answer2 = await exportAnswer(request3, { bundleId: bundleId2, paged: true, ...cursor !== void 0 ? { cursor } : {} });
|
|
135393
|
-
if (answer2.refused === "invalid_input" && cursor === void 0 && attempt === 1) return verified(await readArchive(await wholeAnswer(request3), source, MAX_EXPORT_BYTES), source);
|
|
135394
|
-
if (answer2.refused === "concurrent_change") {
|
|
135395
|
-
moved = true;
|
|
135396
|
-
break;
|
|
135397
|
-
}
|
|
135398
|
-
try {
|
|
135399
|
-
if (answer2.refused !== void 0) throw await exportRefusal(request3, answer2.refused, answer2.status);
|
|
135400
|
-
page = verified(await readArchive(answer2.ok, source, MAX_EXPORT_BYTES - bytes), source, { paged: true });
|
|
135401
|
-
} catch (error51) {
|
|
135402
|
-
const again = error51 instanceof CliError && (error51.details?.reason === "export_incomplete" || answer2.refused === "backend_unavailable" && error51.code === "TRANSIENT");
|
|
135403
|
-
if (!again || tries >= 2) throw error51;
|
|
135404
|
-
await pause2(PAGE_RETRY_MS);
|
|
135405
|
-
}
|
|
135406
|
-
}
|
|
135407
|
-
if (moved) break;
|
|
135408
|
-
bytes += page.bytes;
|
|
135409
|
-
cursor = joining(source, () => chain.add(page));
|
|
135410
|
-
if (cursor === void 0) return joining(source, () => chain.joined());
|
|
135411
|
-
}
|
|
135412
|
-
if (attempt >= PAGED_EXPORT_ATTEMPTS) {
|
|
135413
|
-
throw new CliError("TRANSIENT", `'${bundleId2}' kept changing on ${target3.origin} while it was exported; nothing was written`, {
|
|
135414
|
-
details: { reason: "export_source_changed", bundle_id: bundleId2, host: target3.origin, retryable: true },
|
|
135415
|
-
help: "retry the same command once the bundle is not being edited"
|
|
135416
|
-
});
|
|
135417
|
-
}
|
|
135418
|
-
const base = PAGED_EXPORT_RESTART_MS[attempt - 1];
|
|
135419
|
-
await pause2(Math.round(base / 2 + Math.random() * base / 2));
|
|
135420
|
-
}
|
|
135421
|
-
}
|
|
135422
|
-
async function exportAnswer({ client, source, resume: resume2 }, input) {
|
|
135423
|
-
let answer2;
|
|
135424
|
-
try {
|
|
135425
|
-
answer2 = await client.carrier.stream(`${client.prefix}/export`, input, client.signal);
|
|
135426
|
-
} catch (error51) {
|
|
135427
|
-
throw hostedFailure(error51, source.target, resume2);
|
|
135428
|
-
}
|
|
135429
|
-
if (!answer2.ok) return { refused: refusalCode(answer2.body) ?? "RUNTIME", status: answer2.status };
|
|
135430
|
-
return { ok: answer2.body };
|
|
135431
|
-
}
|
|
135432
|
-
async function wholeAnswer(request3) {
|
|
135433
|
-
const answer2 = await exportAnswer(request3, { bundleId: request3.source.bundleId });
|
|
135434
|
-
if (answer2.refused !== void 0) throw await exportRefusal(request3, answer2.refused, answer2.status);
|
|
135435
|
-
return answer2.ok;
|
|
135436
|
-
}
|
|
135437
|
-
function exportTooLarge(source) {
|
|
135438
|
-
return new CliError("RUNTIME", `${source.target.origin} answered an export larger than ${MAX_EXPORT_BYTES} bytes`, {
|
|
135439
|
-
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" },
|
|
135440
|
-
help: `${cliInvocation()} whoami --host ${commandToken(hostArgument(source.target))}`
|
|
135441
|
-
});
|
|
135442
|
-
}
|
|
135443
|
-
async function exportRefusal({ client, slug, source, resume: resume2 }, code3, status2) {
|
|
135444
|
-
const { target: target3, bundleId: bundleId2 } = source;
|
|
135445
|
-
if (code3 === "bundle_not_found") {
|
|
135446
|
-
if (source.binding) return await bundleAbsent(source.binding, client);
|
|
135447
|
-
const text5 = hostedBundleReferenceText({ slug, bundleId: bundleId2 });
|
|
135448
|
-
return new CliError("NOT_FOUND", `no hosted bundle '${text5}' is visible to you on ${target3.origin}`, {
|
|
135449
|
-
details: { bundle_id: bundleId2, ...slug !== null ? { reference: text5 } : {}, host: target3.origin },
|
|
135450
|
-
help: hostedListCommand(target3)
|
|
135451
|
-
});
|
|
135452
|
-
}
|
|
135453
|
-
if (code3 === "result_too_large") {
|
|
135454
|
-
return new CliError("FORBIDDEN", `hosted bundle '${bundleId2}' is larger than an export carries`, {
|
|
135455
|
-
details: { reason: "bundle_too_large", bundle_id: bundleId2, host: target3.origin, next: "the person exports it with Export bundle in the Superbee app" },
|
|
135456
|
-
help: `${cliInvocation()} whoami --host ${commandToken(hostArgument(target3))}`
|
|
135457
|
-
});
|
|
135458
|
-
}
|
|
135459
|
-
if (code3 === "validation_failed") {
|
|
135460
|
-
return new CliError("FORBIDDEN", `hosted bundle '${bundleId2}' holds a path an export cannot carry`, {
|
|
135461
|
-
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" },
|
|
135462
|
-
help: `${cliInvocation()} whoami --host ${commandToken(hostArgument(target3))}`
|
|
135463
|
-
});
|
|
135464
|
-
}
|
|
135465
|
-
return hostedFailure(new RemoteError(`hosted export answered ${status2}`, code3, status2), target3, resume2);
|
|
135466
|
-
}
|
|
135467
|
-
async function readArchive(body, source, budget) {
|
|
135468
|
-
const { target: target3, bundleId: bundleId2 } = source;
|
|
135469
|
-
const reader = body.getReader();
|
|
135470
|
-
const chunks = [];
|
|
135471
|
-
let size = 0;
|
|
135472
|
-
try {
|
|
135473
|
-
for (; ; ) {
|
|
135474
|
-
const part = await reader.read();
|
|
135475
|
-
if (part.done) break;
|
|
135476
|
-
size += part.value.byteLength;
|
|
135477
|
-
if (size > budget) throw exportTooLarge(source);
|
|
135478
|
-
chunks.push(part.value);
|
|
135479
|
-
}
|
|
135480
|
-
} catch (error51) {
|
|
135481
|
-
if (error51 instanceof CliError) throw error51;
|
|
135482
|
-
throw new CliError("TRANSIENT", `the export of '${bundleId2}' from ${target3.origin} stopped part way; nothing was written`, {
|
|
135483
|
-
details: { reason: "export_incomplete", bundle_id: bundleId2, host: target3.origin, retryable: true },
|
|
135484
|
-
help: "retry the same command"
|
|
135485
|
-
});
|
|
135486
|
-
} finally {
|
|
135487
|
-
void reader.cancel().catch(() => {
|
|
135488
|
-
});
|
|
135489
|
-
}
|
|
135490
|
-
const archive = new Uint8Array(size);
|
|
135491
|
-
let offset = 0;
|
|
135492
|
-
for (const chunk of chunks) {
|
|
135493
|
-
archive.set(chunk, offset);
|
|
135494
|
-
offset += chunk.byteLength;
|
|
135495
|
-
}
|
|
135496
|
-
return archive;
|
|
135497
|
-
}
|
|
135498
|
-
function verified(archive, source, options = {}) {
|
|
135499
|
-
return refusingArchive(source, () => verifyExport(archive, source.bundleId, options));
|
|
135500
|
-
}
|
|
135501
|
-
function joining(source, join19) {
|
|
135502
|
-
return refusingArchive(source, join19);
|
|
135503
|
-
}
|
|
135504
|
-
function refusingArchive(source, verify) {
|
|
135505
|
-
try {
|
|
135506
|
-
return verify();
|
|
135507
|
-
} catch (error51) {
|
|
135508
|
-
if (!(error51 instanceof ExportArchiveError)) throw error51;
|
|
135509
|
-
if (error51.problem === "incomplete") {
|
|
135510
|
-
throw new CliError("TRANSIENT", `the export of '${source.bundleId}' was not completed by the host; nothing was written`, {
|
|
135511
|
-
details: { reason: "export_incomplete", bundle_id: source.bundleId, host: source.target.origin, retryable: true },
|
|
135512
|
-
help: "retry the same command"
|
|
135513
|
-
});
|
|
135514
|
-
}
|
|
135515
|
-
throw new CliError("RUNTIME", `${source.target.origin} answered an export this CLI refuses (${error51.message}); nothing was written`, {
|
|
135516
|
-
details: { reason: `export_${error51.problem}`, bundle_id: source.bundleId, host: source.target.origin, retryable: false },
|
|
135517
|
-
help: "upgrade Superbee (npm install -g superbee); if it persists, report this bundle and reason"
|
|
135518
|
-
});
|
|
135519
|
-
}
|
|
135520
|
-
}
|
|
135521
|
-
async function writeNewFile(file2, bytes) {
|
|
135522
|
-
const handle = await open4(file2, "wx", 420);
|
|
135523
|
-
try {
|
|
135524
|
-
await handle.writeFile(bytes);
|
|
135525
|
-
await handle.sync();
|
|
135526
|
-
} finally {
|
|
135527
|
-
await handle.close();
|
|
135528
|
-
}
|
|
135529
|
-
}
|
|
135530
|
-
async function writeEntries(root, entries) {
|
|
135531
|
-
for (const entry of entries) {
|
|
135532
|
-
const file2 = path56.join(root, ...entry.path.split("/"));
|
|
135533
|
-
await mkdir5(path56.dirname(file2), { recursive: true });
|
|
135534
|
-
await writeNewFile(file2, entry.bytes);
|
|
135535
|
-
}
|
|
135536
|
-
}
|
|
135537
|
-
function gitRun(dir, args) {
|
|
135538
|
-
let result3;
|
|
135539
|
-
try {
|
|
135540
|
-
result3 = runGit(dir, args);
|
|
135541
|
-
} catch (error51) {
|
|
135542
|
-
const missing2 = error51.code === "GIT_MISSING";
|
|
135543
|
-
throw new CliError(missing2 ? "GIT_MISSING" : "RUNTIME", `git could not run (${error51 instanceof Error ? error51.message : String(error51)})`, {
|
|
135544
|
-
details: { reason: missing2 ? "git_missing" : "git_unavailable" },
|
|
135545
|
-
help: "install git, or export without --git"
|
|
135546
|
-
});
|
|
135547
|
-
}
|
|
135548
|
-
if (result3.status !== 0) {
|
|
135549
|
-
throw new CliError("RUNTIME", `git ${args.find((arg) => !arg.startsWith("-") && !arg.includes("=")) ?? ""} failed: ${result3.stderr.trim().split("\n").at(-1) ?? ""}`, {
|
|
135550
|
-
details: { reason: "git_failed" },
|
|
135551
|
-
help: "fix the git error, then retry the same command"
|
|
135552
|
-
});
|
|
135553
|
-
}
|
|
135554
|
-
return result3.stdout.trim();
|
|
135555
|
-
}
|
|
135556
|
-
function commitExport(dir, message, options) {
|
|
135557
|
-
const quiet = ["-c", "core.hooksPath=/dev/null"];
|
|
135558
|
-
gitRun(dir, [...quiet, "init", "-q"]);
|
|
135559
|
-
gitRun(dir, ["symbolic-ref", "HEAD", `refs/heads/${BOARD_BRANCH}`]);
|
|
135560
|
-
const excluded = ["--", ".", `:(exclude)${IN_PLACE_STAGING}`, `:(exclude)${CHECKOUT_MARKER_DIR}`];
|
|
135561
|
-
gitRun(dir, ["add", "-A", ...options.force ? ["--force"] : [], ...excluded]);
|
|
135562
|
-
gitRun(dir, [...quiet, ...identityFlags(dir), "-c", "commit.gpgsign=false", "commit", "-q", "--no-verify", "--allow-empty", "-m", message]);
|
|
135563
|
-
const committed = { branch: BOARD_BRANCH, commit: gitRun(dir, ["rev-parse", "HEAD"]) };
|
|
135564
|
-
if (options.force) return committed;
|
|
135565
|
-
const ignored = gitRun(dir, ["ls-files", "--others", "--ignored", "--exclude-standard", ...excluded]).split("\n").filter(Boolean);
|
|
135566
|
-
return ignored.length === 0 ? committed : { ...committed, ignored: { count: ignored.length, paths: shown(ignored) } };
|
|
135567
|
-
}
|
|
135568
|
-
function commitMessage(source, exported) {
|
|
135569
|
-
return `Export ${source.bundleId} from ${source.target.origin} at revision ${exported.source.revision}
|
|
135570
|
-
|
|
135571
|
-
superbee export, ${exported.exportedAt}: ${exported.counts.documents} documents, ${exported.counts.reserved} reserved files, ${exported.counts.blobs} blobs. History is not included.
|
|
135572
|
-
`;
|
|
135573
|
-
}
|
|
135574
|
-
function stagingName(target3) {
|
|
135575
|
-
return path56.join(path56.dirname(target3), `.${path56.basename(target3)}.superbee-export-${process.pid}-${randomBytes9(6).toString("hex")}.partial`);
|
|
135576
|
-
}
|
|
135577
|
-
function processAlive(pid) {
|
|
135578
|
-
try {
|
|
135579
|
-
process.kill(pid, 0);
|
|
135580
|
-
return true;
|
|
135581
|
-
} catch (error51) {
|
|
135582
|
-
return error51.code === "EPERM";
|
|
135583
|
-
}
|
|
135584
|
-
}
|
|
135585
|
-
async function removeAbandonedStaging(target3) {
|
|
135586
|
-
const prefix = `.${path56.basename(target3)}.superbee-export-`;
|
|
135587
|
-
let removed = 0;
|
|
135588
|
-
let names;
|
|
135589
|
-
try {
|
|
135590
|
-
names = await readdir9(path56.dirname(target3));
|
|
135591
|
-
} catch {
|
|
135592
|
-
return 0;
|
|
135593
|
-
}
|
|
135594
|
-
for (const name of names) {
|
|
135595
|
-
if (!name.startsWith(prefix)) continue;
|
|
135596
|
-
const match = /^(\d+)-[0-9a-f]{12}\.partial$/.exec(name.slice(prefix.length));
|
|
135597
|
-
if (!match) continue;
|
|
135598
|
-
const age = await lstat10(path56.join(path56.dirname(target3), name)).then((info) => Date.now() - info.mtimeMs, () => 0);
|
|
135599
|
-
if (processAlive(Number(match[1])) && age < ABANDONED_AFTER_MS) continue;
|
|
135600
|
-
await rm2(path56.join(path56.dirname(target3), name), { recursive: true, force: true });
|
|
135601
|
-
removed += 1;
|
|
135602
|
-
}
|
|
135603
|
-
return removed;
|
|
135604
|
-
}
|
|
135605
|
-
async function assertStandalone(target3, home2) {
|
|
135606
|
-
const parent = path56.dirname(target3);
|
|
135607
|
-
const help2 = "pass --to <folder> outside any bundle, board or checkout";
|
|
135608
|
-
const checkout2 = await hostedCheckoutAt(parent, home2);
|
|
135609
|
-
if (checkout2) {
|
|
135610
|
-
throw new CliError("FORBIDDEN", `refusing to export inside the hosted checkout at ${checkout2.path}`, { details: { reason: "inside_checkout", enclosing: checkout2.path }, help: help2 });
|
|
135611
|
-
}
|
|
135612
|
-
const enclosing = await findBundleRoot(parent).catch(() => null);
|
|
135613
|
-
if (enclosing) {
|
|
135614
|
-
throw new CliError("FORBIDDEN", `refusing to export inside the local bundle at ${enclosing}`, { details: { reason: "inside_bundle", enclosing }, help: help2 });
|
|
135615
|
-
}
|
|
135616
|
-
const binding = await resolveProjectBinding(parent).catch(() => null);
|
|
135617
|
-
if (binding) {
|
|
135618
|
-
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 });
|
|
135619
|
-
}
|
|
135620
|
-
}
|
|
135621
|
-
async function exportToFolder(source, toArg, git2, deps2, mode, resume2) {
|
|
135622
|
-
const requested = path56.resolve(deps2.cwd, toArg);
|
|
135623
|
-
assertBundleOutsidePrivateState(requested, deps2.auth.home);
|
|
135624
|
-
const found = await entryKind2(requested);
|
|
135625
|
-
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>" });
|
|
135626
|
-
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>" });
|
|
135627
|
-
await mkdir5(path56.dirname(requested), { recursive: true });
|
|
135628
|
-
const target3 = path56.join(await realpath13(path56.dirname(requested)), path56.basename(requested));
|
|
135629
|
-
assertBundleOutsidePrivateState(target3, deps2.auth.home);
|
|
135630
|
-
await assertStandalone(target3, deps2.auth.home);
|
|
135631
|
-
const exported = await fetchExport(source, deps2, resume2);
|
|
135632
|
-
const abandoned = await removeAbandonedStaging(target3);
|
|
135633
|
-
const staging = stagingName(target3);
|
|
135634
|
-
await mkdir5(staging);
|
|
135635
|
-
let gitResult = null;
|
|
135636
|
-
try {
|
|
135637
|
-
await writeEntries(staging, exported.entries);
|
|
135638
|
-
if (git2) gitResult = commitExport(staging, commitMessage(source, exported), { force: true });
|
|
135639
|
-
const now = await entryKind2(target3);
|
|
135640
|
-
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>" });
|
|
135641
|
-
try {
|
|
135642
|
-
await rename2(staging, target3);
|
|
135643
|
-
} catch (error51) {
|
|
135644
|
-
const code3 = error51.code;
|
|
135645
|
-
if (code3 === "ENOTEMPTY" || code3 === "EEXIST" || code3 === "ENOTDIR") {
|
|
135646
|
-
throw new CliError("ALREADY_EXISTS", `${target3} is no longer empty`, { details: { reason: "not_empty", folder: target3 }, help: "pass --to <new or empty folder>" });
|
|
135647
|
-
}
|
|
135648
|
-
throw error51;
|
|
135649
|
-
}
|
|
135650
|
-
} catch (error51) {
|
|
135651
|
-
await rm2(staging, { recursive: true, force: true }).catch(() => {
|
|
135652
|
-
});
|
|
135653
|
-
throw error51;
|
|
135654
|
-
}
|
|
135655
|
-
const home2 = await bundleHomeAt(target3, { home: deps2.auth.home });
|
|
135656
|
-
deps2.stdout(
|
|
135657
|
-
render(
|
|
135658
|
-
{
|
|
135659
|
-
export: "created",
|
|
135660
|
-
folder: target3,
|
|
135661
|
-
home: home2.home,
|
|
135662
|
-
bundle_id: source.bundleId,
|
|
135663
|
-
host: source.target.origin,
|
|
135664
|
-
revision: exported.source.revision,
|
|
135665
|
-
exported_at: exported.exportedAt,
|
|
135666
|
-
okf_edition: exported.source.okfEdition,
|
|
135667
|
-
documents: exported.counts.documents,
|
|
135668
|
-
reserved: exported.counts.reserved,
|
|
135669
|
-
blobs: exported.counts.blobs,
|
|
135670
|
-
bytes: exported.bytes,
|
|
135671
|
-
root_index: exported.entries.some((entry) => entry.path === "index.md"),
|
|
135672
|
-
...gitResult ? { git: gitResult } : {},
|
|
135673
|
-
...abandoned > 0 ? { removed_abandoned_exports: abandoned } : {},
|
|
135674
|
-
hosted: HOSTED_UNCHANGED,
|
|
135675
|
-
history: NO_HISTORY,
|
|
135676
|
-
help: nextSteps2(target3, gitResult !== null)
|
|
135677
|
-
},
|
|
135678
|
-
mode
|
|
135679
|
-
)
|
|
135680
|
-
);
|
|
135681
|
-
}
|
|
135682
|
-
function nextSteps2(folder, git2) {
|
|
135683
|
-
const steps = [commandFragment`${cliInvocation()} status --dir ${commandToken(folder)}`];
|
|
135684
|
-
if (git2) steps.push(commandFragment`git -C ${commandToken(folder)} remote add origin <url> && ${cliInvocation()} sync --establish --dir ${commandToken(folder)}`);
|
|
135685
|
-
return steps;
|
|
135686
|
-
}
|
|
135687
|
-
function isJournal(value) {
|
|
135688
|
-
const record6 = value;
|
|
135689
|
-
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);
|
|
135690
|
-
}
|
|
135691
|
-
async function stagingDir(folder) {
|
|
135692
|
-
const staging = path56.join(folder, IN_PLACE_STAGING);
|
|
135693
|
-
try {
|
|
135694
|
-
if (!(await lstat10(staging)).isDirectory()) return null;
|
|
135695
|
-
return await realpath13(staging) === path56.join(await realpath13(folder), IN_PLACE_STAGING) ? staging : null;
|
|
135696
|
-
} catch {
|
|
135697
|
-
return null;
|
|
135698
|
-
}
|
|
135699
|
-
}
|
|
135700
|
-
async function readJournal(folder) {
|
|
135701
|
-
const staging = await stagingDir(folder);
|
|
135702
|
-
if (!staging) return null;
|
|
135703
|
-
let journal;
|
|
135704
|
-
const read = readRegularFileNoFollowSync(path56.join(staging, JOURNAL));
|
|
135705
|
-
if (read.state !== "present" || read.bytes.byteLength > MAX_JOURNAL_BYTES) return null;
|
|
135706
|
-
try {
|
|
135707
|
-
const value = JSON.parse(read.bytes.toString("utf8"));
|
|
135708
|
-
if (!isJournal(value)) return null;
|
|
135709
|
-
journal = value;
|
|
135710
|
-
} catch {
|
|
135711
|
-
return null;
|
|
135712
|
-
}
|
|
135713
|
-
const marker = readCheckoutMarker(folder);
|
|
135714
|
-
if (!marker || marker.bundle_id !== journal.bundle_id || marker.host !== bindingHostArgument({ origin: journal.host, audience: journal.audience })) return null;
|
|
135715
|
-
return journal;
|
|
135716
|
-
}
|
|
135717
|
-
async function sameBytes(file2, bytes) {
|
|
135718
|
-
const read = readRegularFileNoFollowSync(file2);
|
|
135719
|
-
return read.state === "present" && Buffer.compare(read.bytes, Buffer.from(bytes)) === 0;
|
|
135720
|
-
}
|
|
135721
|
-
async function placementOf(folder, entry) {
|
|
135722
|
-
let info;
|
|
135723
|
-
try {
|
|
135724
|
-
info = await lstat10(path56.join(folder, ...entry.path.split("/")));
|
|
135725
|
-
} catch (error51) {
|
|
135726
|
-
const code3 = error51.code;
|
|
135727
|
-
if (code3 === "ENOENT") return "add";
|
|
135728
|
-
if (code3 === "ENOTDIR") return "kept";
|
|
135729
|
-
throw error51;
|
|
135730
|
-
}
|
|
135731
|
-
if (!info.isFile()) return "kept";
|
|
135732
|
-
return await sameBytes(path56.join(folder, ...entry.path.split("/")), entry.bytes) ? "same" : "kept";
|
|
135733
|
-
}
|
|
135734
|
-
async function placeStaged(folder, staging, relative2) {
|
|
135735
|
-
const final = path56.join(folder, ...relative2.split("/"));
|
|
135736
|
-
const staged = path56.join(staging, "files", ...relative2.split("/"));
|
|
135737
|
-
try {
|
|
135738
|
-
if ((await lstat10(staged)).isFile()) {
|
|
135739
|
-
const stagedParent = await realpath13(path56.dirname(staged));
|
|
135740
|
-
if (!stagedParent.startsWith(`${await realpath13(staging)}${path56.sep}`)) return "kept";
|
|
135741
|
-
} else return "kept";
|
|
135742
|
-
} catch (error51) {
|
|
135743
|
-
if (error51.code !== "ENOENT") return "kept";
|
|
135744
|
-
return await lstat10(final).then((info) => info.isFile(), () => false) ? "same" : "kept";
|
|
135745
|
-
}
|
|
135746
|
-
try {
|
|
135747
|
-
await mkdir5(path56.dirname(final), { recursive: true });
|
|
135748
|
-
const parent = await realpath13(path56.dirname(final));
|
|
135749
|
-
if (parent !== folder && !parent.startsWith(`${folder}${path56.sep}`)) return "kept";
|
|
135750
|
-
} catch {
|
|
135751
|
-
return "kept";
|
|
135752
|
-
}
|
|
135753
|
-
let outcome;
|
|
135754
|
-
try {
|
|
135755
|
-
await link3(staged, final);
|
|
135756
|
-
outcome = "added";
|
|
135757
|
-
} catch (error51) {
|
|
135758
|
-
const code3 = error51.code;
|
|
135759
|
-
if (code3 !== "EEXIST") throw error51;
|
|
135760
|
-
const stagedRead = readRegularFileNoFollowSync(staged);
|
|
135761
|
-
outcome = stagedRead.state === "present" && await sameBytes(final, stagedRead.bytes) ? "same" : "kept";
|
|
135762
|
-
}
|
|
135763
|
-
await unlink14(staged).catch(() => {
|
|
135764
|
-
});
|
|
135765
|
-
return outcome;
|
|
135766
|
-
}
|
|
135767
|
-
async function writeJournal(staging, journal) {
|
|
135768
|
-
const temp = path56.join(staging, `.${JOURNAL}.${randomBytes9(6).toString("hex")}.tmp`);
|
|
135769
|
-
await writeNewFile(temp, Buffer.from(`${JSON.stringify(journal)}
|
|
135770
|
-
`, "utf8"));
|
|
135771
|
-
await rename2(temp, path56.join(staging, JOURNAL));
|
|
135772
|
-
}
|
|
135773
|
-
async function finishInPlace(folder, journal) {
|
|
135774
|
-
const staging = await stagingDir(folder);
|
|
135775
|
-
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)}` });
|
|
135776
|
-
const added = [];
|
|
135777
|
-
const kept = [...journal.kept_local];
|
|
135778
|
-
let same = journal.same;
|
|
135779
|
-
for (const relative2 of journal.adds) {
|
|
135780
|
-
classifyEntryPath(relative2);
|
|
135781
|
-
const outcome = await placeStaged(folder, staging, relative2);
|
|
135782
|
-
if (outcome === "added") added.push(relative2);
|
|
135783
|
-
else if (outcome === "same") same += 1;
|
|
135784
|
-
else kept.push(relative2);
|
|
135785
|
-
}
|
|
135786
|
-
let git2 = null;
|
|
135787
|
-
if (journal.git) {
|
|
135788
|
-
git2 = await ownCommit(folder) ?? commitExport(folder, `Export ${journal.bundle_id} from ${journal.host} at revision ${journal.revision}
|
|
135789
|
-
|
|
135790
|
-
superbee export --in-place, ${journal.exported_at}. History is not included.
|
|
135791
|
-
`, { force: false });
|
|
135792
|
-
}
|
|
135793
|
-
await rm2(path56.join(staging, "files"), { recursive: true, force: true });
|
|
135794
|
-
await unlink14(path56.join(staging, JOURNAL)).catch(() => {
|
|
135795
|
-
});
|
|
135796
|
-
await rm2(staging, { recursive: true, force: true });
|
|
135797
|
-
const marker = await removeCheckoutMarker(folder, { origin: journal.host, audience: journal.audience, bundle_id: journal.bundle_id }).catch(() => false) ? "removed" : "none";
|
|
135798
|
-
return { added, kept, same, git: git2, marker };
|
|
135799
|
-
}
|
|
135800
|
-
function inPlaceReceipt(folder, journal, finished, homeName, resumed) {
|
|
135801
|
-
return {
|
|
135802
|
-
export: "converted",
|
|
135803
|
-
folder,
|
|
135804
|
-
home: homeName,
|
|
135805
|
-
bundle_id: journal.bundle_id,
|
|
135806
|
-
host: journal.host,
|
|
135807
|
-
revision: journal.revision,
|
|
135808
|
-
exported_at: journal.exported_at,
|
|
135809
|
-
...resumed ? { resumed: true } : {},
|
|
135810
|
-
binding: "removed (the folder is no longer a hosted checkout)",
|
|
135811
|
-
marker: finished.marker,
|
|
135812
|
-
added: finished.added.length,
|
|
135813
|
-
...finished.added.length > 0 ? { added_paths: shown(finished.added) } : {},
|
|
135814
|
-
unchanged: finished.same,
|
|
135815
|
-
kept_local: finished.kept.length,
|
|
135816
|
-
...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" } : {},
|
|
135817
|
-
...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" } : {},
|
|
135818
|
-
...journal.unsent_kept > 0 ? { unsent_kept: journal.unsent_kept, unsent_note: "changes sync had not sent stay in this folder only" } : {},
|
|
135819
|
-
...finished.git ? { git: finished.git } : {},
|
|
135820
|
-
hosted: HOSTED_UNCHANGED,
|
|
135821
|
-
history: NO_HISTORY,
|
|
135822
|
-
help: nextSteps2(folder, finished.git !== null)
|
|
135823
|
-
};
|
|
135824
|
-
}
|
|
135825
|
-
async function ownCommit(folder) {
|
|
135826
|
-
const head = runGit(folder, ["rev-parse", "--verify", "--quiet", "HEAD"]);
|
|
135827
|
-
const top = runGit(folder, ["rev-parse", "--show-toplevel"]);
|
|
135828
|
-
const own5 = top.status === 0 && await realpath13(top.stdout.trim()).catch(() => "") === await realpath13(folder).catch(() => folder);
|
|
135829
|
-
if (!own5 || head.status !== 0) return null;
|
|
135830
|
-
return { branch: runGit(folder, ["symbolic-ref", "-q", "--short", "HEAD"]).stdout.trim() || BOARD_BRANCH, commit: head.stdout.trim() };
|
|
135831
|
-
}
|
|
135832
|
-
async function assertNoRepository(folder) {
|
|
135833
|
-
if (await lstat10(path56.join(folder, ".git")).then(() => true, () => false)) {
|
|
135834
|
-
throw new CliError("ALREADY_EXISTS", `${folder} already holds a Git repository`, {
|
|
135835
|
-
details: { reason: "already_git", folder },
|
|
135836
|
-
help: `${cliInvocation()} export --in-place --dir ${commandToken(folder)}`
|
|
135837
|
-
});
|
|
135838
|
-
}
|
|
135839
|
-
}
|
|
135840
|
-
async function exportInPlace(dirArg, git2, keepUnsent, deps2, mode, resume2) {
|
|
135841
|
-
const requested = path56.resolve(deps2.cwd, dirArg ?? ".");
|
|
135842
|
-
assertBundleOutsidePrivateState(requested, deps2.auth.home);
|
|
135843
|
-
const binding = await hostedCheckoutFor(dirArg === void 0 ? [] : ["--dir", dirArg], deps2.auth.home, deps2.cwd);
|
|
135844
|
-
if (!binding) {
|
|
135845
|
-
const folder2 = await realpath13(requested).catch(() => requested);
|
|
135846
|
-
const journal = await readJournal(folder2);
|
|
135847
|
-
if (journal) {
|
|
135848
|
-
const finished = await finishInPlace(folder2, journal);
|
|
135849
|
-
const home4 = await bundleHomeAt(folder2, { home: deps2.auth.home });
|
|
135850
|
-
deps2.stdout(render(inPlaceReceipt(folder2, journal, finished, home4.home, true), mode));
|
|
135851
|
-
return;
|
|
135852
|
-
}
|
|
135853
|
-
if (await entryKind2(folder2) === "absent") throw new CliError("NOT_FOUND", `${folder2} does not exist`, { help: `${cliInvocation()} export --in-place --dir <checkout folder>` });
|
|
135854
|
-
const marker = readCheckoutMarker(folder2);
|
|
135855
|
-
if (marker) {
|
|
135856
|
-
const existing = git2 ? await ownCommit(folder2) : null;
|
|
135857
|
-
if (git2 && !existing) await assertNoRepository(folder2);
|
|
135858
|
-
const staging = await stagingDir(folder2);
|
|
135859
|
-
if (staging && !await lstat10(path56.join(staging, JOURNAL)).then(() => true, () => false)) await rm2(staging, { recursive: true, force: true });
|
|
135860
|
-
await removeCheckoutMarker(folder2, { origin: marker.host, audience: `${marker.host}/mcp`, bundle_id: marker.bundle_id });
|
|
135861
|
-
const committed = existing ?? (git2 ? commitExport(folder2, `Export ${marker.bundle_id} (an unbound copy of a hosted checkout)
|
|
135862
|
-
|
|
135863
|
-
superbee export --in-place. Nothing was fetched from the host; history is not included.
|
|
135864
|
-
`, { force: false }) : null);
|
|
135865
|
-
const home4 = await bundleHomeAt(folder2, { home: deps2.auth.home });
|
|
135866
|
-
deps2.stdout(
|
|
135867
|
-
render(
|
|
135868
|
-
{
|
|
135869
|
-
export: "converted",
|
|
135870
|
-
folder: folder2,
|
|
135871
|
-
home: home4.home,
|
|
135872
|
-
bundle_id: marker.bundle_id,
|
|
135873
|
-
from: "an unbound copy of a hosted checkout",
|
|
135874
|
-
marker: "removed",
|
|
135875
|
-
fetched: false,
|
|
135876
|
-
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`,
|
|
135877
|
-
...committed ? { git: committed } : {},
|
|
135878
|
-
hosted: HOSTED_UNCHANGED,
|
|
135879
|
-
history: NO_HISTORY,
|
|
135880
|
-
help: nextSteps2(folder2, committed !== null)
|
|
135881
|
-
},
|
|
135882
|
-
mode
|
|
135883
|
-
)
|
|
135884
|
-
);
|
|
135885
|
-
return;
|
|
135886
|
-
}
|
|
135887
|
-
const home3 = await bundleHomeAt(folder2, { home: deps2.auth.home });
|
|
135888
|
-
deps2.stdout(render({ export: "unchanged", folder: folder2, home: home3.home, reason: "not a hosted checkout: nothing to convert" }, mode));
|
|
135889
|
-
return;
|
|
135890
|
-
}
|
|
135891
|
-
const folder = binding.path;
|
|
135892
|
-
if (git2) await assertNoRepository(folder);
|
|
135893
|
-
const status2 = await hostedStatus(binding, deps2.auth.home);
|
|
135894
|
-
if (status2.sync.state === "busy") {
|
|
135895
|
-
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" });
|
|
135896
|
-
}
|
|
135897
|
-
const unsentRefusal = (count5, sync3) => new CliError("CONFLICT", `the checkout of '${binding.bundle_id}' at ${folder} has ${count5} change(s) not on the host`, {
|
|
135898
|
-
details: {
|
|
135899
|
-
reason: "unsent_changes",
|
|
135900
|
-
folder,
|
|
135901
|
-
...sync3,
|
|
135902
|
-
keep_them_here_only: commandFragment`${cliInvocation()} export --in-place --dir ${commandToken(folder)} --keep-unsent${git2 ? commandFragment` --git` : commandFragment``}`
|
|
135903
|
-
},
|
|
135904
|
-
help: commandFragment`${cliInvocation()} sync --dir ${commandToken(folder)}`
|
|
135905
|
-
});
|
|
135906
|
-
const early = Number(status2.sync.unsent ?? 0) + Number(status2.sync.conflicts ?? 0) + Number(status2.sync.held_files ?? 0) + Number(status2.sync.held_deletions ?? 0);
|
|
135907
|
-
if (early > 0 && !keepUnsent) throw unsentRefusal(early, status2.sync);
|
|
135908
|
-
const source = { target: resolveHostedTarget(binding.audience), bundleId: binding.bundle_id, slug: binding.workspace_slug ?? null, workspace: null, binding };
|
|
135909
|
-
const exported = await fetchExport(source, deps2, resume2);
|
|
135910
|
-
const result3 = await filesystemPushRoleLocks().request(checkoutLockName(folder), { ifAvailable: true }, async (lock) => {
|
|
135911
|
-
if (!lock) {
|
|
135912
|
-
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" });
|
|
135913
|
-
}
|
|
135914
|
-
const current = await bindingForPath(deps2.auth.home, folder);
|
|
135915
|
-
if (!current || current.checkout_id !== binding.checkout_id) {
|
|
135916
|
-
throw new CliError("CONFLICT", `the checkout at ${folder} changed while it was exported`, { details: { reason: "checkout_changed", folder }, help: "retry the same command" });
|
|
135917
|
-
}
|
|
135918
|
-
const store = await FileJournaledBackend.open({ directory: checkoutStoreDir(deps2.auth.home, binding.checkout_id), readOnly: true });
|
|
135919
|
-
let pending;
|
|
135920
|
-
try {
|
|
135921
|
-
const classified = await classifyCheckout(binding, deps2.auth.home, store);
|
|
135922
|
-
pending = /* @__PURE__ */ new Set([...classified.unsent, ...classified.conflicts, ...classified.held.keys(), ...classified.heldDeletions]);
|
|
135923
|
-
} finally {
|
|
135924
|
-
await store.close();
|
|
135925
|
-
}
|
|
135926
|
-
if (pending.size > 0 && !keepUnsent) throw unsentRefusal(pending.size, { unsent_ids: shown([...pending]) });
|
|
135927
|
-
const staging = path56.join(folder, IN_PLACE_STAGING);
|
|
135928
|
-
await rm2(staging, { recursive: true, force: true });
|
|
135929
|
-
await mkdir5(path56.join(staging, "files"), { recursive: true });
|
|
135930
|
-
const adds = [];
|
|
135931
|
-
const kept = [];
|
|
135932
|
-
const deletedLocally = [];
|
|
135933
|
-
let same = 0;
|
|
135934
|
-
for (const entry of exported.entries) {
|
|
135935
|
-
const id = entry.kind === "document" ? entry.path.slice(0, -3) : entry.path;
|
|
135936
|
-
const placement = await placementOf(folder, entry);
|
|
135937
|
-
if (placement === "add" && pending.has(id)) {
|
|
135938
|
-
deletedLocally.push(entry.path);
|
|
135939
|
-
continue;
|
|
135940
|
-
}
|
|
135941
|
-
if (placement === "same") same += 1;
|
|
135942
|
-
else if (placement === "kept") kept.push(entry.path);
|
|
135943
|
-
else {
|
|
135944
|
-
const file2 = path56.join(staging, "files", ...entry.path.split("/"));
|
|
135945
|
-
await mkdir5(path56.dirname(file2), { recursive: true });
|
|
135946
|
-
await writeNewFile(file2, entry.bytes);
|
|
135947
|
-
adds.push(entry.path);
|
|
135948
|
-
}
|
|
135949
|
-
}
|
|
135950
|
-
const journal = {
|
|
135951
|
-
schema: JOURNAL_SCHEMA,
|
|
135952
|
-
bundle_id: binding.bundle_id,
|
|
135953
|
-
host: binding.origin,
|
|
135954
|
-
audience: binding.audience,
|
|
135955
|
-
revision: exported.source.revision,
|
|
135956
|
-
exported_at: exported.exportedAt,
|
|
135957
|
-
git: git2,
|
|
135958
|
-
adds,
|
|
135959
|
-
same,
|
|
135960
|
-
kept_local: kept,
|
|
135961
|
-
deleted_locally: deletedLocally,
|
|
135962
|
-
unsent_kept: pending.size
|
|
135963
|
-
};
|
|
135964
|
-
await writeJournal(staging, journal);
|
|
135965
|
-
await releaseCheckout(deps2.auth.home, binding);
|
|
135966
|
-
return { journal, finished: await finishInPlace(folder, journal) };
|
|
135967
|
-
});
|
|
135968
|
-
const home2 = await bundleHomeAt(folder, { home: deps2.auth.home });
|
|
135969
|
-
deps2.stdout(render(inPlaceReceipt(folder, result3.journal, result3.finished, home2.home, false), mode));
|
|
135970
|
-
}
|
|
135971
|
-
async function exportCommand(argv2, partial2 = {}) {
|
|
135972
|
-
const deps2 = exportDeps(partial2);
|
|
135973
|
-
const { values, positionals } = parseLeafOrUsage(
|
|
135974
|
-
() => parseArgs42({
|
|
135975
|
-
args: argv2,
|
|
135976
|
-
options: {
|
|
135977
|
-
to: { type: "string" },
|
|
135978
|
-
"in-place": { type: "boolean" },
|
|
135979
|
-
dir: { type: "string" },
|
|
135980
|
-
host: { type: "string" },
|
|
135981
|
-
workspace: { type: "string" },
|
|
135982
|
-
git: { type: "boolean" },
|
|
135983
|
-
"keep-unsent": { type: "boolean" },
|
|
135984
|
-
json: { type: "boolean" },
|
|
135985
|
-
help: { type: "boolean", short: "h" }
|
|
135986
|
-
},
|
|
135987
|
-
allowPositionals: true
|
|
135988
|
-
}),
|
|
135989
|
-
CLI_LEAVES.export
|
|
135990
|
-
);
|
|
135991
|
-
if (values.help) {
|
|
135992
|
-
deps2.stdout(renderUsage(EXPORT_USAGE));
|
|
135993
|
-
return;
|
|
135994
|
-
}
|
|
135995
|
-
const mode = resolveMode(values);
|
|
135996
|
-
const usage3 = (message) => new CliError("USAGE", message, { help: `${cliInvocation()} export --help` });
|
|
135997
|
-
const named = positionals[0];
|
|
135998
|
-
const inPlace = values["in-place"] === true;
|
|
135999
|
-
if (inPlace === (values.to !== void 0)) throw usage3("pass exactly one of --to <folder> and --in-place");
|
|
136000
|
-
if (named !== void 0 && values.dir !== void 0) throw usage3("pass a bundle id or --dir <checkout>, not both");
|
|
136001
|
-
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");
|
|
136002
|
-
if (inPlace && named !== void 0) throw usage3("--in-place converts a checkout folder: pass --dir <checkout>, not a bundle id");
|
|
136003
|
-
if (!inPlace && values["keep-unsent"]) throw usage3("--keep-unsent goes with --in-place");
|
|
136004
|
-
const reference = named === void 0 ? void 0 : parseHostedBundleReference(named);
|
|
136005
|
-
if (named !== void 0 && !reference) throw usage3(`'${named}' is not a hosted bundle id or <workspace>/<bundle-id>`);
|
|
136006
|
-
const git2 = values.git === true;
|
|
136007
|
-
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``}`;
|
|
136008
|
-
if (values.dir !== void 0) assertBundleOutsidePrivateState(path56.resolve(deps2.cwd, values.dir), deps2.auth.home);
|
|
136009
|
-
if (inPlace) {
|
|
136010
|
-
await exportInPlace(values.dir, git2, values["keep-unsent"] === true, deps2, mode, resume2);
|
|
136011
|
-
return;
|
|
136012
|
-
}
|
|
136013
|
-
let source;
|
|
136014
|
-
if (reference) {
|
|
136015
|
-
const named2 = await requireHostedBundleHost(values.host, deps2.auth);
|
|
136016
|
-
source = {
|
|
136017
|
-
target: named2.target,
|
|
136018
|
-
bundleId: reference.bundleId,
|
|
136019
|
-
slug: reference.slug,
|
|
136020
|
-
workspace: workspaceForNamedHost(named2, values.workspace) ?? null,
|
|
136021
|
-
binding: null
|
|
136022
|
-
};
|
|
136023
|
-
} else {
|
|
136024
|
-
const binding = await hostedCheckoutFor(values.dir === void 0 ? [] : ["--dir", values.dir], deps2.auth.home, deps2.cwd);
|
|
136025
|
-
if (!binding) {
|
|
136026
|
-
throw new CliError("NOT_FOUND", `${path56.resolve(deps2.cwd, values.dir ?? ".")} is not a hosted checkout`, {
|
|
136027
|
-
details: { reason: "not_a_checkout" },
|
|
136028
|
-
help: `${cliInvocation()} export <bundle-id> --to ${commandToken(values.to)}`
|
|
136029
|
-
});
|
|
136030
|
-
}
|
|
136031
|
-
source = { target: resolveHostedTarget(binding.audience), bundleId: binding.bundle_id, slug: binding.workspace_slug ?? null, workspace: null, binding };
|
|
136032
|
-
}
|
|
136033
|
-
await exportToFolder(source, values.to, git2, deps2, mode, resume2);
|
|
136034
|
-
}
|
|
136035
|
-
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;
|
|
136036
|
-
var init_export = __esm({
|
|
136037
|
-
"../cli/src/commands/export.ts"() {
|
|
136038
|
-
"use strict";
|
|
136039
|
-
init_define_SUPERBEE_BUILD_IDENTITY();
|
|
136040
|
-
init_define_SUPERBEE_UPDATE_POLICY();
|
|
136041
|
-
init_src();
|
|
136042
|
-
init_filesystem_push_role();
|
|
136043
|
-
init_src2();
|
|
136044
|
-
init_args();
|
|
136045
|
-
init_bundle2();
|
|
136046
|
-
init_bundle_home();
|
|
136047
|
-
init_command_spec();
|
|
136048
|
-
init_command_text();
|
|
136049
|
-
init_errors4();
|
|
136050
|
-
init_invocation();
|
|
136051
|
-
init_output2();
|
|
136052
|
-
init_private_state_bundle_boundary();
|
|
136053
|
-
init_nofollow_read();
|
|
136054
|
-
init_autopull2();
|
|
136055
|
-
init_session();
|
|
136056
|
-
init_discovery();
|
|
136057
|
-
init_binding();
|
|
136058
|
-
init_client();
|
|
136059
|
-
init_account();
|
|
136060
|
-
init_refusals();
|
|
136061
|
-
init_reference();
|
|
136062
|
-
init_sync2();
|
|
136063
|
-
init_status();
|
|
136064
|
-
init_file_journaled_backend();
|
|
136065
|
-
init_marker();
|
|
136066
|
-
init_export_archive();
|
|
136067
|
-
init_export_archive();
|
|
136068
|
-
EXPORT_USAGE = `superbee export \u2014 copy a hosted bundle out of hosted Superbee
|
|
136069
|
-
|
|
136070
|
-
Usage:
|
|
136071
|
-
superbee export <bundle-id> --to <folder> [--host <url>] [--workspace <id>] [--git] [--json]
|
|
136072
|
-
superbee export [--dir <checkout>] --to <folder> [--git] [--json]
|
|
136073
|
-
superbee export [--dir <checkout>] --in-place [--git] [--keep-unsent] [--json]
|
|
136074
|
-
|
|
136075
|
-
Fetches the hosted bundle's portable export: every document, reserved file (index.md, log.md)
|
|
136076
|
-
and blob at its latest confirmed revision, byte for byte as the host stores them. The archive is
|
|
136077
|
-
verified whole (every entry against the host's digest manifest) before any file is written.
|
|
136078
|
-
Signs in if needed (AUTH_REQUIRED, exit 4, carries the one link to relay and the command to
|
|
136079
|
-
re-run). The hosted bundle is never changed. History is not exported: the folder holds the
|
|
136080
|
-
current revision only.
|
|
136081
|
-
|
|
136082
|
-
--to <folder> writes a new local bundle there. The folder must be new or empty; it appears
|
|
136083
|
-
complete or not at all. With a bundle id the host is --host, else your last sign-in; with
|
|
136084
|
-
--dir (or run inside a checkout) the checkout's own host and bundle are used. A bundle id in more
|
|
136085
|
-
than one of your workspaces is named <workspace>/<bundle-id>, or with --workspace.
|
|
136086
|
-
|
|
136087
|
-
--in-place converts a hosted checkout into a plain local bundle: it adds the files the checkout
|
|
136088
|
-
lacks (blobs, reserved files, documents added on the host), never overwrites a file, then
|
|
136089
|
-
forgets the checkout's binding. It refuses a checkout with changes sync has not sent, conflicts
|
|
136090
|
-
or held files; run 'superbee sync' first, or pass --keep-unsent to keep them in this folder only.
|
|
136091
|
-
If it is interrupted, re-running the same command finishes it.
|
|
136092
|
-
|
|
136093
|
-
--git makes the result a Git board: a repository on branch '${BOARD_BRANCH}' whose first commit is the
|
|
136094
|
-
export. Share it with 'git remote add origin <url>' and 'superbee sync --establish'.
|
|
136095
|
-
|
|
136096
|
-
Options:
|
|
136097
|
-
--to <folder> New or empty folder to export into
|
|
136098
|
-
--in-place Convert the hosted checkout (--dir, else the current folder) into a local bundle
|
|
136099
|
-
--dir <checkout> The hosted checkout to export (default: the current folder)
|
|
136100
|
-
--host <url> Hosted Superbee URL, with a bundle id; default: your last sign-in
|
|
136101
|
-
--workspace <id> With a bundle id: your workspace that holds it, by id or slug
|
|
136102
|
-
--git Initialize a Git repository on branch '${BOARD_BRANCH}' and commit the export
|
|
136103
|
-
--keep-unsent With --in-place: convert even with unsent changes, conflicts or held files
|
|
136104
|
-
--json Emit compact JSON instead of TOON
|
|
136105
|
-
-h, --help Show this help
|
|
136106
|
-
|
|
136107
|
-
Examples:
|
|
136108
|
-
superbee export team.knowledge --to ~/work/team-knowledge --git
|
|
136109
|
-
superbee export --dir ~/work/team --to ~/work/team-copy
|
|
136110
|
-
superbee export --dir ~/work/team --in-place
|
|
136111
|
-
`;
|
|
136112
|
-
EXPORT_DEADLINE_MS = 6e4;
|
|
136113
|
-
SHOWN = 20;
|
|
136114
|
-
JOURNAL = IN_PLACE_JOURNAL;
|
|
136115
|
-
JOURNAL_SCHEMA = 1;
|
|
136116
|
-
MAX_JOURNAL_BYTES = 8 * 1024 * 1024;
|
|
136117
|
-
NO_HISTORY = "not exported: the folder holds the current revision only";
|
|
136118
|
-
HOSTED_UNCHANGED = "unchanged: the hosted bundle still exists and was not modified";
|
|
136119
|
-
PAGED_EXPORT_ATTEMPTS = 3;
|
|
136120
|
-
PAGED_EXPORT_RESTART_MS = [500, 1500];
|
|
136121
|
-
PAGE_RETRY_MS = 1e3;
|
|
136122
|
-
pause2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
136123
|
-
ABANDONED_AFTER_MS = 60 * 60 * 1e3;
|
|
136124
|
-
}
|
|
136125
|
-
});
|
|
136126
|
-
|
|
136127
136308
|
// ../cli/src/commands/op.ts
|
|
136128
136309
|
import { homedir as homedir41 } from "node:os";
|
|
136129
136310
|
import path57 from "node:path";
|
|
@@ -136410,8 +136591,9 @@ function cleanAuthor(author) {
|
|
|
136410
136591
|
function gitVersions(board, id, rel, current, okfVersion, now, documentInputBytes) {
|
|
136411
136592
|
const repoPath = board.prefix === "" ? rel : `${board.prefix}/${rel}`;
|
|
136412
136593
|
const log = runGit(board.top, ["log", "--no-renames", "--format=%H%x1f%an <%ae>%x1f%aI", "HEAD", "--", repoPath]);
|
|
136413
|
-
if (log.status !== 0) return { rows: [], skipped: 0 };
|
|
136594
|
+
if (log.status !== 0) return { rows: [], skipped: 0, oversized: [] };
|
|
136414
136595
|
const rows = [];
|
|
136596
|
+
const oversized = [];
|
|
136415
136597
|
let skipped = 0;
|
|
136416
136598
|
let previous3 = current;
|
|
136417
136599
|
for (const line2 of log.stdout.split("\n").filter(Boolean)) {
|
|
@@ -136438,19 +136620,24 @@ function gitVersions(board, id, rel, current, okfVersion, now, documentInputByte
|
|
|
136438
136620
|
skipped += 1;
|
|
136439
136621
|
continue;
|
|
136440
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
|
+
}
|
|
136441
136631
|
rows.push({
|
|
136442
136632
|
documentId: id,
|
|
136443
136633
|
label: `imported:git/${sha}`,
|
|
136444
136634
|
author: cleanAuthor(author ?? ""),
|
|
136445
136635
|
authoredAt: new Date(when).toISOString(),
|
|
136446
|
-
|
|
136447
|
-
frontmatter: Object.fromEntries(
|
|
136448
|
-
Object.entries(parsed2.frontmatter).filter(([key2]) => !IMPORT_DROPPED_FIELDS.includes(key2))
|
|
136449
|
-
),
|
|
136636
|
+
frontmatter,
|
|
136450
136637
|
body: parsed2.body ?? ""
|
|
136451
136638
|
});
|
|
136452
136639
|
}
|
|
136453
|
-
return { rows: rows.reverse(), skipped };
|
|
136640
|
+
return { rows: rows.reverse(), skipped, oversized };
|
|
136454
136641
|
}
|
|
136455
136642
|
async function okfVersionOf(rootIndex) {
|
|
136456
136643
|
if (rootIndex === null) return void 0;
|
|
@@ -136593,7 +136780,12 @@ async function planPublish(folder, options) {
|
|
|
136593
136780
|
throw error51;
|
|
136594
136781
|
}
|
|
136595
136782
|
if (request3.kind !== "create") throw new Error(`'${id}' became a ${request3.kind} request`);
|
|
136596
|
-
|
|
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) {
|
|
136597
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(", ")}` });
|
|
136598
136790
|
continue;
|
|
136599
136791
|
}
|
|
@@ -136633,6 +136825,7 @@ async function planPublish(folder, options) {
|
|
|
136633
136825
|
const versions = gitVersions(options.board, file2.id, file2.rel, file2.text, okfVersion, options.now ?? Date.now(), documentInputBytes);
|
|
136634
136826
|
history.push(...versions.rows);
|
|
136635
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` });
|
|
136636
136829
|
}
|
|
136637
136830
|
historyPlan = {
|
|
136638
136831
|
mode: "git",
|
|
@@ -136773,7 +136966,7 @@ function packParts(content3, versions) {
|
|
|
136773
136966
|
}
|
|
136774
136967
|
return parts;
|
|
136775
136968
|
}
|
|
136776
|
-
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;
|
|
136777
136970
|
var init_publish_plan = __esm({
|
|
136778
136971
|
"../cli/src/hosted/publish-plan.ts"() {
|
|
136779
136972
|
"use strict";
|
|
@@ -136833,6 +137026,7 @@ var init_publish_plan = __esm({
|
|
|
136833
137026
|
".xml": "application/xml"
|
|
136834
137027
|
};
|
|
136835
137028
|
HOSTED_KIND_WRITE = Object.freeze({ strict: true, persistActor: true, producer: "process:superbee-hosted" });
|
|
137029
|
+
VERIFIED_STAMP_ROOM_BYTES = 512;
|
|
136836
137030
|
FORMAT_AT_EDGE = new RegExp("^\\p{Cf}|\\p{Cf}$", "u");
|
|
136837
137031
|
FORMAT_INSIDE = new RegExp("(?![\\u200c\\u200d])\\p{Cf}", "u");
|
|
136838
137032
|
WIDEST_TARGET = Object.freeze({ workspace: "w".repeat(64), bundleId: "b".repeat(128), name: "n".repeat(128) });
|
|
@@ -137193,7 +137387,7 @@ function provenanceOf(plan) {
|
|
|
137193
137387
|
return {
|
|
137194
137388
|
carried: plan.provenance.carried,
|
|
137195
137389
|
dropped: plan.provenance.dropped,
|
|
137196
|
-
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"
|
|
137197
137391
|
};
|
|
137198
137392
|
}
|
|
137199
137393
|
function createRefusal(code3, message, context) {
|
|
@@ -137220,6 +137414,14 @@ function createRefusal(code3, message, context) {
|
|
|
137220
137414
|
details,
|
|
137221
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))}`
|
|
137222
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" });
|
|
137223
137425
|
default:
|
|
137224
137426
|
return new CliError("USAGE", `${context.target.origin} refused the bundle (${code3}): ${message}`, { details, help: "fix the files it names, then preview again" });
|
|
137225
137427
|
}
|
|
@@ -137666,9 +137868,10 @@ Without --yes, previews only, with no network: what travels (documents, reserved
|
|
|
137666
137868
|
files), what stays (dot-files, links), how it is sent, every bound the host enforces, the history
|
|
137667
137869
|
plan, and what will happen to the folder.
|
|
137668
137870
|
|
|
137669
|
-
Each document's provenance (generated, timestamp, actor, okf_version
|
|
137670
|
-
it, a claim rather than a verification
|
|
137671
|
-
|
|
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.
|
|
137672
137875
|
|
|
137673
137876
|
A bundle within one request's bounds (at most 1,000 documents, 100 other files of 1 MB each,
|
|
137674
137877
|
1,000 earlier versions, 1,500 objects in all, 3 MiB) is sent in one request. A larger one is sent
|