@neta-art/cohub-cli 6.6.0 → 6.7.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/commands/apps.js +91 -8
- package/package.json +2 -2
package/dist/commands/apps.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { basename } from "node:path";
|
|
1
4
|
import { HttpError } from "@neta-art/cohub";
|
|
2
5
|
import { createClient, createClientWithAccessToken } from "../client.js";
|
|
3
6
|
import { error, handleHttp, json as outJson, jsonRequested, ok, table } from "../output.js";
|
|
@@ -6,6 +9,7 @@ import { downloadApp } from "../app-download.js";
|
|
|
6
9
|
import { getAppByRef, parseAppRef } from "../app-ref.js";
|
|
7
10
|
import { checkAppTarget } from "../app-target.js";
|
|
8
11
|
import { registerAppCommerce } from "./app-commerce.js";
|
|
12
|
+
import { collectPublicUpload } from "./public.js";
|
|
9
13
|
const APP_STATUSES = ["published", "disabled"];
|
|
10
14
|
const APP_VISIBILITIES = ["public", "space"];
|
|
11
15
|
const collectOption = (value, previous = []) => [...previous, value];
|
|
@@ -57,6 +61,69 @@ function withCohubBarMeta(input) {
|
|
|
57
61
|
delete meta.presentation;
|
|
58
62
|
return Object.keys(meta).length > 0 ? meta : null;
|
|
59
63
|
}
|
|
64
|
+
const MAX_APP_SOURCE_FILES = 1000;
|
|
65
|
+
const MAX_APP_SOURCE_BYTES = 1024 * 1024 * 1024;
|
|
66
|
+
const APP_SOURCE_UPLOAD_CONCURRENCY = 4;
|
|
67
|
+
async function uploadLocalAppSource(client, spaceId, source) {
|
|
68
|
+
if (source.targetType === "port")
|
|
69
|
+
return null;
|
|
70
|
+
const upload = await collectPublicUpload(source.targetRef);
|
|
71
|
+
const totalBytes = upload.files.reduce((sum, file) => sum + file.size, 0);
|
|
72
|
+
if (upload.files.length > MAX_APP_SOURCE_FILES)
|
|
73
|
+
return error("App source is too large", `Use no more than ${MAX_APP_SOURCE_FILES} files.`);
|
|
74
|
+
if (totalBytes > MAX_APP_SOURCE_BYTES)
|
|
75
|
+
return error("App source is too large", "The total source size must not exceed 1 GiB.");
|
|
76
|
+
const uploadId = randomUUID();
|
|
77
|
+
const directoryPrefix = source.targetType === "directory" ? upload.destination.replace(/\/$/, "") : "";
|
|
78
|
+
const files = new Array(upload.files.length);
|
|
79
|
+
let nextIndex = 0;
|
|
80
|
+
const workers = Array.from({ length: Math.min(APP_SOURCE_UPLOAD_CONCURRENCY, upload.files.length) }, async () => {
|
|
81
|
+
while (nextIndex < upload.files.length) {
|
|
82
|
+
const index = nextIndex++;
|
|
83
|
+
const file = upload.files[index];
|
|
84
|
+
if (!file)
|
|
85
|
+
return;
|
|
86
|
+
const plan = await client.publicAssets.createUpload({
|
|
87
|
+
purpose: "app_source",
|
|
88
|
+
uploadProtocol: "presigned_put_v1",
|
|
89
|
+
spaceId,
|
|
90
|
+
sessionId: uploadId,
|
|
91
|
+
file: { size: file.size, mimeType: file.mimeType, filename: basename(file.publicPath) },
|
|
92
|
+
});
|
|
93
|
+
const response = await fetch(plan.asset.uploadUrl, {
|
|
94
|
+
method: "PUT",
|
|
95
|
+
headers: plan.asset.uploadHeaders,
|
|
96
|
+
body: createReadStream(file.localPath),
|
|
97
|
+
duplex: "half",
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok)
|
|
100
|
+
throw new Error(`Failed to upload ${file.publicPath}: HTTP ${response.status}`);
|
|
101
|
+
const path = directoryPrefix && file.publicPath.startsWith(`${directoryPrefix}/`)
|
|
102
|
+
? file.publicPath.slice(directoryPrefix.length + 1)
|
|
103
|
+
: basename(file.publicPath);
|
|
104
|
+
files[index] = { path, objectKey: plan.asset.objectKey, size: file.size, mimeType: file.mimeType };
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
await Promise.all(workers);
|
|
108
|
+
const manifest = JSON.stringify({ kind: "cohub.app-source", version: 1, targetType: source.targetType, files });
|
|
109
|
+
const manifestBlob = new Blob([manifest], { type: "application/json" });
|
|
110
|
+
const manifestPlan = await client.publicAssets.createUpload({
|
|
111
|
+
purpose: "app_source",
|
|
112
|
+
uploadProtocol: "presigned_put_v1",
|
|
113
|
+
spaceId,
|
|
114
|
+
sessionId: uploadId,
|
|
115
|
+
file: { size: manifestBlob.size, mimeType: "application/json", filename: "manifest.json" },
|
|
116
|
+
});
|
|
117
|
+
const manifestResponse = await fetch(manifestPlan.asset.uploadUrl, {
|
|
118
|
+
method: "PUT",
|
|
119
|
+
headers: manifestPlan.asset.uploadHeaders,
|
|
120
|
+
body: manifestBlob,
|
|
121
|
+
});
|
|
122
|
+
if (!manifestResponse.ok)
|
|
123
|
+
throw new Error(`Failed to upload app source manifest: HTTP ${manifestResponse.status}`);
|
|
124
|
+
const manifestAsset = manifestPlan.asset;
|
|
125
|
+
return { sourceRef: manifestAsset.objectKey, targetRef: source.targetType === "file" ? files[0]?.path ?? "" : "." };
|
|
126
|
+
}
|
|
60
127
|
function resolveTarget(opts) {
|
|
61
128
|
const targets = [
|
|
62
129
|
opts.file ? { targetType: "file", targetRef: opts.file } : null,
|
|
@@ -321,8 +388,9 @@ export function registerApps(program) {
|
|
|
321
388
|
appsCmd
|
|
322
389
|
.command("publish <slug>")
|
|
323
390
|
.description("Create or publish an app in the target space")
|
|
324
|
-
.option("--
|
|
325
|
-
.option("--
|
|
391
|
+
.option("--source <source>", "Source: workspace (default) or local")
|
|
392
|
+
.option("--file <path>", "Publish a file from the selected source")
|
|
393
|
+
.option("--dir <path>", "Publish a directory site from the selected source")
|
|
326
394
|
.option("--port <port>", "Publish a public sandbox port")
|
|
327
395
|
.option("--disabled", "Create as disabled")
|
|
328
396
|
.option("--status <status>", "App status: published, disabled")
|
|
@@ -339,27 +407,42 @@ export function registerApps(program) {
|
|
|
339
407
|
const target = resolveTarget(opts);
|
|
340
408
|
if (!target)
|
|
341
409
|
return error("Missing target", "Use one of --file, --dir, or --port.");
|
|
410
|
+
const source = opts.source ? parseChoice(opts.source, "source", ["workspace", "local"]) : "workspace";
|
|
411
|
+
if (target.targetType === "port" && opts.source)
|
|
412
|
+
return error("Invalid source", "--source applies only to --file and --dir.");
|
|
342
413
|
const spaceId = resolveSpace(appsCmd);
|
|
343
414
|
const client = createClient();
|
|
344
|
-
|
|
345
|
-
|
|
415
|
+
let { targetType, targetRef } = target;
|
|
416
|
+
let sourceRef = null;
|
|
417
|
+
if (source === "local") {
|
|
418
|
+
const uploaded = await uploadLocalAppSource(client, spaceId, target);
|
|
419
|
+
if (!uploaded)
|
|
420
|
+
return error("Invalid source", "--source local applies only to --file and --dir.");
|
|
421
|
+
targetRef = uploaded.targetRef;
|
|
422
|
+
sourceRef = uploaded.sourceRef;
|
|
423
|
+
}
|
|
424
|
+
else if (targetType !== "port") {
|
|
346
425
|
await guardAppTarget(client, spaceId, { targetType, targetRef });
|
|
426
|
+
}
|
|
347
427
|
const status = resolveStatus(opts);
|
|
348
428
|
const meta = withCohubBarMeta({
|
|
349
429
|
meta: parseJsonObject(opts.meta, "meta"),
|
|
350
430
|
hideCohubBar: opts.hideCohubBar,
|
|
351
431
|
showCohubBar: opts.showCohubBar,
|
|
352
432
|
});
|
|
433
|
+
const publishMeta = source === "local"
|
|
434
|
+
? { ...(meta ?? {}), runtime: { source: { type: "upload", ref: sourceRef } } }
|
|
435
|
+
: meta;
|
|
353
436
|
const input = {
|
|
354
437
|
spaceId,
|
|
355
438
|
slug,
|
|
356
439
|
status,
|
|
357
440
|
visibility: resolveVisibility(opts.visibility),
|
|
358
441
|
targetType: target.targetType,
|
|
359
|
-
targetRef
|
|
442
|
+
targetRef,
|
|
360
443
|
appScopes: opts.appScope,
|
|
361
444
|
allowedViewerScopes: opts.viewerScope,
|
|
362
|
-
meta,
|
|
445
|
+
meta: publishMeta,
|
|
363
446
|
};
|
|
364
447
|
try {
|
|
365
448
|
const result = await client.apps.create(input);
|
|
@@ -381,10 +464,10 @@ export function registerApps(program) {
|
|
|
381
464
|
status: status === "published" && existingApp.status !== "published" ? existingApp.status : status,
|
|
382
465
|
visibility: resolveVisibility(opts.visibility),
|
|
383
466
|
targetType: target.targetType,
|
|
384
|
-
targetRef
|
|
467
|
+
targetRef,
|
|
385
468
|
appScopes: opts.appScope,
|
|
386
469
|
allowedViewerScopes: opts.viewerScope,
|
|
387
|
-
meta,
|
|
470
|
+
meta: publishMeta,
|
|
388
471
|
});
|
|
389
472
|
const publishedVersion = status === "published"
|
|
390
473
|
? await client.apps.publishVersion(app.id)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.7.0",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"commander": "^15.0.0",
|
|
20
20
|
"pixi.js": "^8.20.1",
|
|
21
21
|
"sharp": "^0.35.4",
|
|
22
|
-
"@neta-art/cohub": "8.
|
|
22
|
+
"@neta-art/cohub": "8.10.0"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|