busa-cli 0.11.4 → 0.13.0
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/{chunk-CEH54HWM.js → chunk-KX5SSSEN.js} +237 -59
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
|
@@ -82,17 +82,17 @@ function isNodeLike(value) {
|
|
|
82
82
|
}
|
|
83
83
|
function renderNodeTree(nodes) {
|
|
84
84
|
const lines = [];
|
|
85
|
-
const
|
|
85
|
+
const walk = (items, prefix, isRoot) => {
|
|
86
86
|
items.forEach((node, index) => {
|
|
87
87
|
const isLast = index === items.length - 1;
|
|
88
88
|
const branch = isRoot ? "" : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
89
89
|
const childPrefix = isRoot ? "" : `${prefix}${isLast ? " " : "\u2502 "}`;
|
|
90
90
|
lines.push(`${prefix}${branch}${formatNode(node)}`);
|
|
91
91
|
const children = Array.isArray(node.children) ? node.children.filter(isNodeLike) : [];
|
|
92
|
-
|
|
92
|
+
walk(children, childPrefix, false);
|
|
93
93
|
});
|
|
94
94
|
};
|
|
95
|
-
|
|
95
|
+
walk(nodes, "", true);
|
|
96
96
|
return lines.join("\n");
|
|
97
97
|
}
|
|
98
98
|
function formatNode(node) {
|
|
@@ -122,7 +122,7 @@ function nodeIcon(type) {
|
|
|
122
122
|
// src/run.ts
|
|
123
123
|
import { createHash as createHash6 } from "crypto";
|
|
124
124
|
import { readFileSync as readFileSync2 } from "fs";
|
|
125
|
-
import { basename as basename2, extname as
|
|
125
|
+
import { basename as basename2, extname as extname3 } from "path";
|
|
126
126
|
|
|
127
127
|
// ../../packages/busabase-contract/src/tasks/base-field.ts
|
|
128
128
|
var OPERATIONS = ["add", "update", "delete", "convert", "reorder", "restore"];
|
|
@@ -1255,7 +1255,13 @@ var nodeCreateTask = {
|
|
|
1255
1255
|
name: "version",
|
|
1256
1256
|
kind: "string",
|
|
1257
1257
|
appliesWhen: { param: "type", values: ["skill", "drive", "airapp"] },
|
|
1258
|
-
description: "Skill/Drive/AirApp only. Defaults to 0.1.0."
|
|
1258
|
+
description: "Skill/Drive/AirApp only. Defaults to 0.1.0.",
|
|
1259
|
+
// A bare `--version` collides with the CLI's own root `-v, --version`
|
|
1260
|
+
// flag: commander resolves that flag at the root command regardless of
|
|
1261
|
+
// where it appears in argv, so `nodes create --version 0.4.0` silently
|
|
1262
|
+
// printed the CLI's package version instead of setting the app's
|
|
1263
|
+
// version. `--app-version` sidesteps the collision outright.
|
|
1264
|
+
cliFlag: "--app-version"
|
|
1259
1265
|
},
|
|
1260
1266
|
{
|
|
1261
1267
|
name: "autoMerge",
|
|
@@ -3021,29 +3027,16 @@ async function refreshToken(baseUrl, refreshToken2) {
|
|
|
3021
3027
|
expiresAt: typeof payload.expires_in === "number" ? new Date(Date.now() + payload.expires_in * 1e3).toISOString() : void 0
|
|
3022
3028
|
};
|
|
3023
3029
|
}
|
|
3024
|
-
|
|
3030
|
+
function pickSpaceId(verify, preselected) {
|
|
3025
3031
|
if (preselected) return preselected;
|
|
3026
3032
|
const spaces = verify.spaces ?? [];
|
|
3027
|
-
|
|
3028
|
-
if (
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
say2("");
|
|
3035
|
-
say2("Which space should this CLI target?");
|
|
3036
|
-
spaces.forEach((space, index) => {
|
|
3037
|
-
const marker = space.id === verify.space?.id ? " (default)" : "";
|
|
3038
|
-
say2(` ${index + 1}. ${space.name}${marker} [${space.id}]`);
|
|
3039
|
-
});
|
|
3040
|
-
const answer = await ask2(`Choose 1-${spaces.length} (Enter for default): `);
|
|
3041
|
-
if (!answer) return verify.space?.id ?? spaces[0]?.id;
|
|
3042
|
-
const choice = Number(answer);
|
|
3043
|
-
if (Number.isInteger(choice) && choice >= 1 && choice <= spaces.length) {
|
|
3044
|
-
return spaces[choice - 1]?.id;
|
|
3045
|
-
}
|
|
3046
|
-
throw new Error("Not a valid space choice. Re-run login and choose one of the listed spaces.");
|
|
3033
|
+
const fallback = verify.space?.id ?? spaces[0]?.id;
|
|
3034
|
+
if (spaces.length <= 1) return fallback;
|
|
3035
|
+
const choices = spaces.map((space) => `${space.name} [${space.id}]`).join(", ");
|
|
3036
|
+
say2(
|
|
3037
|
+
`Note: this account belongs to ${spaces.length} spaces; defaulted to "${verify.space?.name ?? spaces[0]?.name}". Run \`busabase-cli space use <id>\` to switch. Available: ${choices}`
|
|
3038
|
+
);
|
|
3039
|
+
return fallback;
|
|
3047
3040
|
}
|
|
3048
3041
|
async function chooseTarget() {
|
|
3049
3042
|
const cloud = normalizeBaseUrl(DEFAULT_BASE_URL);
|
|
@@ -3140,7 +3133,7 @@ async function runLogin(options) {
|
|
|
3140
3133
|
}
|
|
3141
3134
|
say2("Verifying\u2026");
|
|
3142
3135
|
const verify = await verifyAuth(baseUrl, token);
|
|
3143
|
-
const spaceId =
|
|
3136
|
+
const spaceId = pickSpaceId(verify, options.spaceId);
|
|
3144
3137
|
writeDotEnvFile({
|
|
3145
3138
|
BUSABASE_BASE_URL: baseUrl,
|
|
3146
3139
|
BUSABASE_API_KEY: token,
|
|
@@ -3157,6 +3150,9 @@ async function runLogin(options) {
|
|
|
3157
3150
|
credentialType: method === "device" ? "api_key" : expiresAt ? "oauth" : "api_key",
|
|
3158
3151
|
user: verify.user?.email ?? verify.user?.name ?? verify.user?.id ?? "(unknown)",
|
|
3159
3152
|
space: spaceId ?? "(server default)",
|
|
3153
|
+
// Surfaced so a non-interactive caller (typically an agent) can see whether the
|
|
3154
|
+
// fallback default is the space it actually wants, and switch with `space use <id>`.
|
|
3155
|
+
availableSpaces: (verify.spaces ?? []).map((space) => `${space.name} [${space.id}]`).join(", "),
|
|
3160
3156
|
createdSpace: String(Boolean(verify.createdSpace)),
|
|
3161
3157
|
bootstrapRequired: String(Boolean(verify.bootstrapRequired)),
|
|
3162
3158
|
expiresAt: expiresAt ?? apiKeyExpiresAt ?? "(no expiry \u2014 API key)",
|
|
@@ -3642,7 +3638,11 @@ var updateNodeMetadataInputSchema = z8.object({
|
|
|
3642
3638
|
});
|
|
3643
3639
|
var searchNodesByNameInputSchema = z8.object({
|
|
3644
3640
|
query: z8.string().min(1),
|
|
3645
|
-
|
|
3641
|
+
// GET route — query params arrive as strings, and oRPC's OpenAPI handler
|
|
3642
|
+
// does not coerce them. A bare `z.number()` here rejected every real
|
|
3643
|
+
// `?limit=` call with "expected number, received string"; every other
|
|
3644
|
+
// limit/page field on a GET route in this file already uses `z.coerce`.
|
|
3645
|
+
limit: z8.coerce.number().int().min(1).max(50).optional().default(20)
|
|
3646
3646
|
});
|
|
3647
3647
|
var nodeSearchResultSchema = z8.object({
|
|
3648
3648
|
id: z8.string(),
|
|
@@ -4189,11 +4189,13 @@ var viewChangeRequestInputSchema = z9.discriminatedUnion("operation", [
|
|
|
4189
4189
|
var PACKAGE_FORMAT = "busabase-package@1";
|
|
4190
4190
|
var PACKAGE_MANIFEST_FILENAME = "busabase.json";
|
|
4191
4191
|
var PACKAGE_CONTENT_DIRNAME = "content";
|
|
4192
|
+
var PACKAGE_ASSETS_DIRNAME = "assets";
|
|
4192
4193
|
var PACKAGE_NODE_META_FILENAME = "_node.json";
|
|
4193
4194
|
var PACKAGE_FOLDER_META_FILENAME = "_folder.json";
|
|
4194
4195
|
var PACKAGE_BASE_FILENAME = "base.json";
|
|
4195
4196
|
var PACKAGE_RECORDS_FILENAME = "records.ndjson";
|
|
4196
4197
|
var PACKAGE_NODE_META_SUFFIX = ".node.json";
|
|
4198
|
+
var PACKAGE_ASSET_META_SUFFIX = ".asset.json";
|
|
4197
4199
|
var PACKAGE_FILE_TREE_NODE_TYPES = ["skill", "airapp", "drive"];
|
|
4198
4200
|
var PACKAGE_AI_FIELD_TYPES = ["ai_summary", "ai_tags"];
|
|
4199
4201
|
var PACKAGE_COMPUTED_FIELD_TYPES = [
|
|
@@ -4251,6 +4253,14 @@ var PackageFolderMetaSchema = z10.object({
|
|
|
4251
4253
|
position: z10.number().int().optional()
|
|
4252
4254
|
});
|
|
4253
4255
|
var PackageFileNodeMetaSchema = PackageFolderMetaSchema;
|
|
4256
|
+
var PackageDocAssetMetaSchema = z10.object({
|
|
4257
|
+
assetId: z10.string().min(1),
|
|
4258
|
+
/** The asset's original name, restored on the target so its library entry reads the same. */
|
|
4259
|
+
fileName: z10.string().min(1),
|
|
4260
|
+
mimeType: z10.string().min(1),
|
|
4261
|
+
/** `sha256:<hex>` when the source host had one — lets the target dedupe instead of re-storing. */
|
|
4262
|
+
contentHash: z10.string().optional()
|
|
4263
|
+
});
|
|
4254
4264
|
var PackageDocFrontmatterSchema = z10.object({
|
|
4255
4265
|
name: z10.string().min(1),
|
|
4256
4266
|
description: z10.string().default(""),
|
|
@@ -4363,6 +4373,9 @@ var PackageRecordLineSchema = z10.object({
|
|
|
4363
4373
|
// ../../packages/busabase-package/src/apply.ts
|
|
4364
4374
|
import { createHash as createHash5 } from "crypto";
|
|
4365
4375
|
|
|
4376
|
+
// ../../packages/busabase-package/src/doc-asset-refs.ts
|
|
4377
|
+
import { extname as extname2 } from "path";
|
|
4378
|
+
|
|
4366
4379
|
// ../../packages/busabase-package/src/tree.ts
|
|
4367
4380
|
import { extname } from "path";
|
|
4368
4381
|
var NODE_SLUG_PATTERN = /^[a-z0-9-]+$/;
|
|
@@ -4464,6 +4477,39 @@ var walkNodes = function* (nodes) {
|
|
|
4464
4477
|
};
|
|
4465
4478
|
var collectBaseNodes = (nodes) => [...walkNodes(nodes)].filter((node) => node.type === "base");
|
|
4466
4479
|
|
|
4480
|
+
// ../../packages/busabase-package/src/doc-asset-refs.ts
|
|
4481
|
+
var ASSET_CONTENT_URL_PATTERN = /\/api\/assets\/([A-Za-z0-9_-]+)\/raw/g;
|
|
4482
|
+
var buildAssetContentUrl = (assetId) => `/api/assets/${encodeURIComponent(assetId)}/raw`;
|
|
4483
|
+
var extractDocAssetIds = (body) => {
|
|
4484
|
+
const ids = /* @__PURE__ */ new Set();
|
|
4485
|
+
for (const match of body.matchAll(ASSET_CONTENT_URL_PATTERN)) {
|
|
4486
|
+
const assetId = match[1];
|
|
4487
|
+
if (assetId) ids.add(assetId);
|
|
4488
|
+
}
|
|
4489
|
+
return [...ids];
|
|
4490
|
+
};
|
|
4491
|
+
var collectDocAssetIds = (nodes) => {
|
|
4492
|
+
const ids = /* @__PURE__ */ new Set();
|
|
4493
|
+
for (const node of walkNodes(nodes)) {
|
|
4494
|
+
if (node.type !== "doc") continue;
|
|
4495
|
+
for (const assetId of extractDocAssetIds(node.body)) ids.add(assetId);
|
|
4496
|
+
}
|
|
4497
|
+
return [...ids];
|
|
4498
|
+
};
|
|
4499
|
+
var docAssetBytesPath = (asset) => `${PACKAGE_ASSETS_DIRNAME}/${asset.assetId}${extname2(asset.fileName).toLowerCase()}`;
|
|
4500
|
+
var rewriteDocAssetIds = (body, idMap) => {
|
|
4501
|
+
if (idMap.size === 0) return body;
|
|
4502
|
+
return body.replace(ASSET_CONTENT_URL_PATTERN, (whole, assetId) => {
|
|
4503
|
+
const replacement = idMap.get(assetId);
|
|
4504
|
+
return replacement === void 0 ? whole : buildAssetContentUrl(replacement);
|
|
4505
|
+
});
|
|
4506
|
+
};
|
|
4507
|
+
var reportUnresolvableDocAssets = (slug, body, carried = /* @__PURE__ */ new Set()) => {
|
|
4508
|
+
const unresolved = extractDocAssetIds(body).filter((assetId) => !carried.has(assetId));
|
|
4509
|
+
if (unresolved.length === 0) return void 0;
|
|
4510
|
+
return `Doc "${slug}" embeds ${unresolved.length} image(s) that this package does not carry (${unresolved.join(", ")}). Everything else about the doc is intact, but those images will not render on another host. Re-upload them in the Doc editor, or check that the source host can still serve them.`;
|
|
4511
|
+
};
|
|
4512
|
+
|
|
4467
4513
|
// ../../packages/busabase-package/src/apply.ts
|
|
4468
4514
|
var RECORD_BATCH_SIZE = 200;
|
|
4469
4515
|
var fieldKey = (baseSlug, fieldSlug) => `${baseSlug}\0${fieldSlug}`;
|
|
@@ -4478,6 +4524,13 @@ var applyInstall = async (client, plan, options) => {
|
|
|
4478
4524
|
const bases = [];
|
|
4479
4525
|
const progress = options.onProgress ?? (() => {
|
|
4480
4526
|
});
|
|
4527
|
+
const docAssetIds = await uploadDocAssets(
|
|
4528
|
+
client,
|
|
4529
|
+
plan.tree.assets ?? [],
|
|
4530
|
+
state,
|
|
4531
|
+
progress,
|
|
4532
|
+
options.serverUrl
|
|
4533
|
+
);
|
|
4481
4534
|
progress("Pass 1/5: creating the node tree\u2026");
|
|
4482
4535
|
state.targetFolderNodeId = await createFolderNode(client, {
|
|
4483
4536
|
parentNodeId: void 0,
|
|
@@ -4494,7 +4547,8 @@ var applyInstall = async (client, plan, options) => {
|
|
|
4494
4547
|
options,
|
|
4495
4548
|
state,
|
|
4496
4549
|
bases,
|
|
4497
|
-
fieldIds
|
|
4550
|
+
fieldIds,
|
|
4551
|
+
docAssetIds
|
|
4498
4552
|
);
|
|
4499
4553
|
const deferred = bases.filter((base) => base.node.base.fields.some(isDeferredField));
|
|
4500
4554
|
if (deferred.length > 0) {
|
|
@@ -4570,7 +4624,28 @@ var applyInstall = async (client, plan, options) => {
|
|
|
4570
4624
|
}
|
|
4571
4625
|
return state;
|
|
4572
4626
|
};
|
|
4573
|
-
var
|
|
4627
|
+
var uploadDocAssets = async (client, assets, state, progress, serverUrl) => {
|
|
4628
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
4629
|
+
if (assets.length === 0) return idMap;
|
|
4630
|
+
progress(`Pass 1/5: uploading ${assets.length} doc image(s)\u2026`);
|
|
4631
|
+
for (const asset of assets) {
|
|
4632
|
+
try {
|
|
4633
|
+
const uploaded = await uploadAsset(
|
|
4634
|
+
client,
|
|
4635
|
+
asset.fileName,
|
|
4636
|
+
asset.mimeType,
|
|
4637
|
+
asset.bytes,
|
|
4638
|
+
"doc",
|
|
4639
|
+
serverUrl
|
|
4640
|
+
);
|
|
4641
|
+
idMap.set(asset.assetId, uploaded.assetId);
|
|
4642
|
+
} catch (error) {
|
|
4643
|
+
state.warnings.push(asSkippableUpload(error, asset.fileName));
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
return idMap;
|
|
4647
|
+
};
|
|
4648
|
+
var createStructure = async (client, nodes, parentNodeId, options, state, bases, fieldIds, docAssetIds) => {
|
|
4574
4649
|
for (const node of nodes) {
|
|
4575
4650
|
switch (node.type) {
|
|
4576
4651
|
case "folder": {
|
|
@@ -4582,16 +4657,31 @@ var createStructure = async (client, nodes, parentNodeId, options, state, bases,
|
|
|
4582
4657
|
submittedBy: options.submittedBy
|
|
4583
4658
|
});
|
|
4584
4659
|
state.created.folders++;
|
|
4585
|
-
await createStructure(
|
|
4660
|
+
await createStructure(
|
|
4661
|
+
client,
|
|
4662
|
+
node.children,
|
|
4663
|
+
nodeId,
|
|
4664
|
+
options,
|
|
4665
|
+
state,
|
|
4666
|
+
bases,
|
|
4667
|
+
fieldIds,
|
|
4668
|
+
docAssetIds
|
|
4669
|
+
);
|
|
4586
4670
|
break;
|
|
4587
4671
|
}
|
|
4588
4672
|
case "doc": {
|
|
4673
|
+
const warning = reportUnresolvableDocAssets(
|
|
4674
|
+
node.slug,
|
|
4675
|
+
node.body,
|
|
4676
|
+
new Set(docAssetIds.keys())
|
|
4677
|
+
);
|
|
4678
|
+
if (warning) state.warnings.push(warning);
|
|
4589
4679
|
const result = await client.docs.create({
|
|
4590
4680
|
parentNodeId,
|
|
4591
4681
|
slug: node.slug,
|
|
4592
4682
|
name: node.name,
|
|
4593
4683
|
description: node.description,
|
|
4594
|
-
body: node.body,
|
|
4684
|
+
body: rewriteDocAssetIds(node.body, docAssetIds),
|
|
4595
4685
|
autoMerge: options.autoMerge
|
|
4596
4686
|
});
|
|
4597
4687
|
if (result.materialized) state.created.docs++;
|
|
@@ -4611,7 +4701,14 @@ var createStructure = async (client, nodes, parentNodeId, options, state, bases,
|
|
|
4611
4701
|
case "file": {
|
|
4612
4702
|
let asset;
|
|
4613
4703
|
try {
|
|
4614
|
-
asset = await uploadAsset(
|
|
4704
|
+
asset = await uploadAsset(
|
|
4705
|
+
client,
|
|
4706
|
+
node.fileName,
|
|
4707
|
+
node.mimeType,
|
|
4708
|
+
node.bytes,
|
|
4709
|
+
void 0,
|
|
4710
|
+
options.serverUrl
|
|
4711
|
+
);
|
|
4615
4712
|
} catch (error) {
|
|
4616
4713
|
state.warnings.push(asSkippableUpload(error, node.fileName));
|
|
4617
4714
|
break;
|
|
@@ -4722,7 +4819,9 @@ var createFileTreeNode = async (client, node, parentNodeId, options, state) => {
|
|
|
4722
4819
|
client,
|
|
4723
4820
|
entry.path.split("/").at(-1) ?? entry.path,
|
|
4724
4821
|
mimeType,
|
|
4725
|
-
entry.bytes
|
|
4822
|
+
entry.bytes,
|
|
4823
|
+
void 0,
|
|
4824
|
+
options.serverUrl
|
|
4726
4825
|
);
|
|
4727
4826
|
return { path: entry.path, assetId: asset.assetId, mimeType };
|
|
4728
4827
|
} catch (error) {
|
|
@@ -4753,13 +4852,25 @@ var createFileTreeNode = async (client, node, parentNodeId, options, state) => {
|
|
|
4753
4852
|
};
|
|
4754
4853
|
var UploadTargetUnsupportedError = class extends Error {
|
|
4755
4854
|
};
|
|
4756
|
-
var
|
|
4855
|
+
var resolveUploadUrl = (uploadUrl, serverUrl) => {
|
|
4856
|
+
if (/^https?:\/\//.test(uploadUrl)) return uploadUrl;
|
|
4857
|
+
if (serverUrl) {
|
|
4858
|
+
try {
|
|
4859
|
+
return new URL(uploadUrl, serverUrl).toString();
|
|
4860
|
+
} catch {
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
4863
|
+
throw new UploadTargetUnsupportedError(
|
|
4864
|
+
`this server issued a root-relative upload url (${uploadUrl}) and no server origin was supplied to resolve it against`
|
|
4865
|
+
);
|
|
4866
|
+
};
|
|
4867
|
+
var uploadAsset = async (client, fileName, mimeType, bytes, context = "record-field", serverUrl) => {
|
|
4757
4868
|
const contentHash = `sha256:${createHash5("sha256").update(bytes).digest("hex")}`;
|
|
4758
4869
|
const requested = await client.assets.createUploadUrl({
|
|
4759
4870
|
fileName,
|
|
4760
4871
|
mimeType,
|
|
4761
4872
|
sizeBytes: bytes.byteLength,
|
|
4762
|
-
context
|
|
4873
|
+
context,
|
|
4763
4874
|
contentHash
|
|
4764
4875
|
});
|
|
4765
4876
|
if (requested.duplicate) {
|
|
@@ -4768,12 +4879,8 @@ var uploadAsset = async (client, fileName, mimeType, bytes) => {
|
|
|
4768
4879
|
throw new Error(`Server reported "${fileName}" as a duplicate but returned no asset id.`);
|
|
4769
4880
|
return { assetId: assetId2 };
|
|
4770
4881
|
}
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
`this server issued a non-absolute upload url (${requested.uploadUrl}), which only its own web UI knows how to use`
|
|
4774
|
-
);
|
|
4775
|
-
}
|
|
4776
|
-
const uploaded = await fetch(requested.uploadUrl, {
|
|
4882
|
+
const uploadUrl = resolveUploadUrl(requested.uploadUrl, serverUrl);
|
|
4883
|
+
const uploaded = await fetch(uploadUrl, {
|
|
4777
4884
|
method: "PUT",
|
|
4778
4885
|
body: new Uint8Array(bytes),
|
|
4779
4886
|
headers: { "content-type": mimeType }
|
|
@@ -4786,7 +4893,7 @@ var uploadAsset = async (client, fileName, mimeType, bytes) => {
|
|
|
4786
4893
|
fileName,
|
|
4787
4894
|
mimeType,
|
|
4788
4895
|
sizeBytes: bytes.byteLength,
|
|
4789
|
-
context
|
|
4896
|
+
context,
|
|
4790
4897
|
contentHash
|
|
4791
4898
|
});
|
|
4792
4899
|
const assetId = confirmed.assetId ?? confirmed.attachmentId;
|
|
@@ -4979,10 +5086,35 @@ var findSourceNode = (nodes, slugOrId) => {
|
|
|
4979
5086
|
var collectPackageTree = async (client, root, options) => {
|
|
4980
5087
|
const children = root.children ?? [];
|
|
4981
5088
|
const nodes = await collectNodes(client, children, options);
|
|
4982
|
-
const tree = {
|
|
5089
|
+
const tree = {
|
|
5090
|
+
manifest: { ...options.manifest, format: PACKAGE_FORMAT },
|
|
5091
|
+
nodes,
|
|
5092
|
+
assets: await collectDocAssets(client, nodes, options)
|
|
5093
|
+
};
|
|
4983
5094
|
assertSelfContained(tree, options);
|
|
4984
5095
|
return tree;
|
|
4985
5096
|
};
|
|
5097
|
+
var collectDocAssets = async (client, nodes, options) => {
|
|
5098
|
+
const assets = [];
|
|
5099
|
+
for (const assetId of collectDocAssetIds(nodes)) {
|
|
5100
|
+
try {
|
|
5101
|
+
const detail = await client.assets.download({ assetId });
|
|
5102
|
+
assets.push({
|
|
5103
|
+
assetId,
|
|
5104
|
+
fileName: detail.fileName,
|
|
5105
|
+
mimeType: detail.mimeType,
|
|
5106
|
+
contentHash: detail.contentHash ?? void 0,
|
|
5107
|
+
// See CollectOptions.baseUrl — a local server's download url is root-relative.
|
|
5108
|
+
bytes: await downloadBytes(detail.downloadUrl, detail.fileName, options)
|
|
5109
|
+
});
|
|
5110
|
+
} catch (error) {
|
|
5111
|
+
options.warn(
|
|
5112
|
+
`Could not read the image ${assetId} embedded in a doc (${error instanceof Error ? error.message : String(error)}). The package ships without it.`
|
|
5113
|
+
);
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
return assets;
|
|
5117
|
+
};
|
|
4986
5118
|
var collectNodes = async (client, sources, options) => {
|
|
4987
5119
|
const nodes = [];
|
|
4988
5120
|
for (const source of sources) {
|
|
@@ -5221,7 +5353,13 @@ var downloadBytes = async (url, label, options) => {
|
|
|
5221
5353
|
return Buffer.from(await response.arrayBuffer());
|
|
5222
5354
|
};
|
|
5223
5355
|
var assertSelfContained = (tree, options) => {
|
|
5224
|
-
const
|
|
5356
|
+
const carriedAssetIds = new Set((tree.assets ?? []).map((asset) => asset.assetId));
|
|
5357
|
+
for (const node of walkNodes(tree.nodes)) {
|
|
5358
|
+
if (node.type !== "doc") continue;
|
|
5359
|
+
const warning = reportUnresolvableDocAssets(node.slug, node.body, carriedAssetIds);
|
|
5360
|
+
if (warning) options.warn(warning);
|
|
5361
|
+
}
|
|
5362
|
+
const baseNodes = [...walkNodes(tree.nodes)].filter((node) => node.type === "base");
|
|
5225
5363
|
const packagedBaseSlugs = new Set(baseNodes.map((node) => node.slug));
|
|
5226
5364
|
const packagedRecordKeys = new Set(
|
|
5227
5365
|
baseNodes.flatMap(
|
|
@@ -5257,12 +5395,6 @@ var assertSelfContained = (tree, options) => {
|
|
|
5257
5395
|
}
|
|
5258
5396
|
}
|
|
5259
5397
|
};
|
|
5260
|
-
var walk = function* (nodes) {
|
|
5261
|
-
for (const node of nodes) {
|
|
5262
|
-
yield node;
|
|
5263
|
-
if (node.type === "folder") yield* walk(node.children);
|
|
5264
|
-
}
|
|
5265
|
-
};
|
|
5266
5398
|
|
|
5267
5399
|
// ../../packages/busabase-package/src/github.ts
|
|
5268
5400
|
import { Uint8ArrayReader, ZipReader } from "@zip.js/zip.js";
|
|
@@ -5526,7 +5658,24 @@ var readPackageTree = (files, options = {}) => {
|
|
|
5526
5658
|
const manifest = zodParse(PackageManifestSchema, rawManifest, manifestPath);
|
|
5527
5659
|
const contentDir = `${root}${PACKAGE_CONTENT_DIRNAME}/`;
|
|
5528
5660
|
const nodes = readNodes(files, contentDir);
|
|
5529
|
-
return { manifest, nodes };
|
|
5661
|
+
return { manifest, nodes, assets: readDocAssets(files, root) };
|
|
5662
|
+
};
|
|
5663
|
+
var readDocAssets = (files, root) => {
|
|
5664
|
+
const dir = `${root}${PACKAGE_ASSETS_DIRNAME}/`;
|
|
5665
|
+
const assets = [];
|
|
5666
|
+
for (const filePath of [...files.keys()].sort()) {
|
|
5667
|
+
if (!filePath.startsWith(dir) || !filePath.endsWith(PACKAGE_ASSET_META_SUFFIX)) continue;
|
|
5668
|
+
const meta = zodParse(PackageDocAssetMetaSchema, parseJsonFile(files, filePath), filePath);
|
|
5669
|
+
const bytesPath = filePath.slice(0, -PACKAGE_ASSET_META_SUFFIX.length);
|
|
5670
|
+
const bytes = files.get(bytesPath);
|
|
5671
|
+
if (!bytes) {
|
|
5672
|
+
throw new Error(
|
|
5673
|
+
`${filePath} describes asset "${meta.assetId}" but its bytes are missing (expected ${bytesPath}). The package is incomplete \u2014 nothing was installed.`
|
|
5674
|
+
);
|
|
5675
|
+
}
|
|
5676
|
+
assets.push({ ...meta, bytes });
|
|
5677
|
+
}
|
|
5678
|
+
return assets;
|
|
5530
5679
|
};
|
|
5531
5680
|
var assertKnownFormat = (rawManifest, manifestPath) => {
|
|
5532
5681
|
const format = typeof rawManifest === "object" && rawManifest !== null ? rawManifest.format : void 0;
|
|
@@ -5758,8 +5907,29 @@ var renderPackageTree = (tree) => {
|
|
|
5758
5907
|
const files = /* @__PURE__ */ new Map();
|
|
5759
5908
|
files.set(PACKAGE_MANIFEST_FILENAME, serializeManifest(tree.manifest));
|
|
5760
5909
|
renderNodes(tree.nodes, `${PACKAGE_CONTENT_DIRNAME}/`, files);
|
|
5910
|
+
renderDocAssets(tree.assets ?? [], files);
|
|
5761
5911
|
return files;
|
|
5762
5912
|
};
|
|
5913
|
+
var renderDocAssets = (assets, files) => {
|
|
5914
|
+
const byId = /* @__PURE__ */ new Map();
|
|
5915
|
+
for (const asset of assets) if (!byId.has(asset.assetId)) byId.set(asset.assetId, asset);
|
|
5916
|
+
for (const asset of [...byId.values()].sort((a, b) => a.assetId.localeCompare(b.assetId, "en"))) {
|
|
5917
|
+
const bytesPath = docAssetBytesPath(asset);
|
|
5918
|
+
assertSafeFilePath(bytesPath, `doc asset "${asset.assetId}"`);
|
|
5919
|
+
files.set(bytesPath, asset.bytes);
|
|
5920
|
+
files.set(
|
|
5921
|
+
`${bytesPath}${PACKAGE_ASSET_META_SUFFIX}`,
|
|
5922
|
+
toJsonFile(
|
|
5923
|
+
compact({
|
|
5924
|
+
assetId: asset.assetId,
|
|
5925
|
+
fileName: asset.fileName,
|
|
5926
|
+
mimeType: asset.mimeType,
|
|
5927
|
+
contentHash: asset.contentHash
|
|
5928
|
+
})
|
|
5929
|
+
)
|
|
5930
|
+
);
|
|
5931
|
+
}
|
|
5932
|
+
};
|
|
5763
5933
|
var renderNodes = (nodes, dir, files) => {
|
|
5764
5934
|
const ordered = sortNodes(nodes);
|
|
5765
5935
|
assertNoSiblingCaseCollisions(
|
|
@@ -5868,6 +6038,7 @@ var renderNode = (node, dir, files) => {
|
|
|
5868
6038
|
var writePackageFiles = async (files, outDir, options = {}) => {
|
|
5869
6039
|
if (options.clean) {
|
|
5870
6040
|
await rm(join2(outDir, PACKAGE_CONTENT_DIRNAME), { recursive: true, force: true });
|
|
6041
|
+
await rm(join2(outDir, PACKAGE_ASSETS_DIRNAME), { recursive: true, force: true });
|
|
5871
6042
|
await rm(join2(outDir, PACKAGE_MANIFEST_FILENAME), { force: true });
|
|
5872
6043
|
}
|
|
5873
6044
|
for (const [filePath, bytes] of [...files].sort(([a], [b]) => a.localeCompare(b, "en"))) {
|
|
@@ -6044,6 +6215,7 @@ var countTree = (tree) => {
|
|
|
6044
6215
|
break;
|
|
6045
6216
|
}
|
|
6046
6217
|
}
|
|
6218
|
+
counts.files += (tree.assets ?? []).length;
|
|
6047
6219
|
return counts;
|
|
6048
6220
|
};
|
|
6049
6221
|
var renderPlan = (plan) => {
|
|
@@ -6127,7 +6299,8 @@ Dry run \u2014 nothing was created.`;
|
|
|
6127
6299
|
const result = await applyInstall(client, plan, {
|
|
6128
6300
|
autoMerge: Boolean(options.autoMerge),
|
|
6129
6301
|
submittedBy: `busabase-cli install (${tree.manifest.name})`,
|
|
6130
|
-
onProgress: reportProgress2
|
|
6302
|
+
onProgress: reportProgress2,
|
|
6303
|
+
serverUrl: options.serverUrl
|
|
6131
6304
|
});
|
|
6132
6305
|
if (options.json) return { installed: true, ...result };
|
|
6133
6306
|
return renderInstallReport(plan.targetFolderSlug, result);
|
|
@@ -6435,7 +6608,7 @@ var MIME_BY_EXT2 = {
|
|
|
6435
6608
|
".txt": "text/plain",
|
|
6436
6609
|
".webp": "image/webp"
|
|
6437
6610
|
};
|
|
6438
|
-
var guessMimeType2 = (filePath) => MIME_BY_EXT2[
|
|
6611
|
+
var guessMimeType2 = (filePath) => MIME_BY_EXT2[extname3(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
6439
6612
|
function createAttachmentOptions(opts) {
|
|
6440
6613
|
const maxFiles = opts.maxFiles;
|
|
6441
6614
|
const maxFileSize = opts.maxFileSize;
|
|
@@ -6829,7 +7002,7 @@ OpenAPI: ${route.method} ${route.path}`);
|
|
|
6829
7002
|
})
|
|
6830
7003
|
);
|
|
6831
7004
|
};
|
|
6832
|
-
const
|
|
7005
|
+
const walk = (node, navPath) => {
|
|
6833
7006
|
for (const key of Object.keys(node)) {
|
|
6834
7007
|
if (key === "~orpc") continue;
|
|
6835
7008
|
const child = node[key];
|
|
@@ -6839,11 +7012,11 @@ OpenAPI: ${route.method} ${route.path}`);
|
|
|
6839
7012
|
const parent = navPath.length ? getGroup(kebab(navPath[0]), child["~orpc"].route.tags?.[0]) : program;
|
|
6840
7013
|
addLeaf(parent, navPath, key, child);
|
|
6841
7014
|
} else if (child && typeof child === "object") {
|
|
6842
|
-
|
|
7015
|
+
walk(child, [...navPath, key]);
|
|
6843
7016
|
}
|
|
6844
7017
|
}
|
|
6845
7018
|
};
|
|
6846
|
-
|
|
7019
|
+
walk(cloudContract, []);
|
|
6847
7020
|
}
|
|
6848
7021
|
function flagSignature(cmd) {
|
|
6849
7022
|
const parts = [];
|
|
@@ -6915,7 +7088,11 @@ The flags below skip the menu (handy for scripts / CI):
|
|
|
6915
7088
|
busabase-cli login --api-key sk_\u2026 # Cloud API key (headless/CI)
|
|
6916
7089
|
busabase-cli login --base-url http://localhost:15419 # connect to a local server (no auth)
|
|
6917
7090
|
busabase-cli login --refresh # rotate the OAuth token set (auto-runs too)
|
|
6918
|
-
busabase-cli login --profile work # add a SECOND account, then switch to it
|
|
7091
|
+
busabase-cli login --profile work # add a SECOND account, then switch to it
|
|
7092
|
+
|
|
7093
|
+
Login never blocks on a Space question, interactive or not: with more than one Space it always
|
|
7094
|
+
accepts the server-resolved default (the one you were most recently active in) and prints the
|
|
7095
|
+
full list \u2014 switch anytime with \`busabase-cli space use <id>\`.`
|
|
6919
7096
|
).action(async (_opts, cmd) => {
|
|
6920
7097
|
const opts = cmd.optsWithGlobals();
|
|
6921
7098
|
const config = resolveConfig(opts);
|
|
@@ -7303,7 +7480,8 @@ field with nothing linked yet does not trigger this.`
|
|
|
7303
7480
|
autoMerge: Boolean(opts.autoMerge),
|
|
7304
7481
|
rename: Boolean(opts.rename),
|
|
7305
7482
|
json: config.output === "json",
|
|
7306
|
-
githubToken: process.env.GITHUB_TOKEN
|
|
7483
|
+
githubToken: process.env.GITHUB_TOKEN,
|
|
7484
|
+
serverUrl: config.baseUrl
|
|
7307
7485
|
})
|
|
7308
7486
|
)
|
|
7309
7487
|
);
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "busa-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Command-line client for the Busabase OpenAPI REST API. Talks to a local or remote `busabase server`. Short-name alias for busabase-cli.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/busabase/busabase/tree/main/apps/busabase-cli",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@zip.js/zip.js": "^2.8.20",
|
|
35
35
|
"busabase-package": "workspace:*",
|
|
36
|
-
"busabase-sdk": "0.
|
|
36
|
+
"busabase-sdk": "0.13.0",
|
|
37
37
|
"commander": "^14.0.0",
|
|
38
38
|
"tar-stream": "^3.1.7",
|
|
39
39
|
"yaml": "^2.9.0",
|