@neta-art/cohub-cli 5.0.0 → 6.0.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/app-download.d.ts +11 -0
- package/dist/{work-download.js → app-download.js} +23 -23
- package/dist/app-ref.d.ts +5 -0
- package/dist/app-ref.js +8 -0
- package/dist/commands/app-commerce.d.ts +2 -0
- package/dist/commands/{work-commerce.js → app-commerce.js} +27 -27
- package/dist/commands/apps.d.ts +4 -0
- package/dist/commands/{works.js → apps.js} +136 -127
- package/dist/commands/desktop.d.ts +3 -0
- package/dist/commands/desktop.js +219 -0
- package/dist/commands/profile.js +5 -5
- package/dist/index.js +7 -6
- package/package.json +2 -2
- package/dist/commands/ui.d.ts +0 -2
- package/dist/commands/ui.js +0 -199
- package/dist/commands/work-commerce.d.ts +0 -2
- package/dist/commands/works.d.ts +0 -4
- package/dist/work-download.d.ts +0 -11
- package/dist/work-ref.d.ts +0 -5
- package/dist/work-ref.js +0 -8
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AppGetResponse } from "@neta-art/cohub";
|
|
2
|
+
type DownloadResult = {
|
|
3
|
+
appId: string;
|
|
4
|
+
version: number;
|
|
5
|
+
kind: "file" | "directory";
|
|
6
|
+
output: string;
|
|
7
|
+
files: number;
|
|
8
|
+
bytes: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function downloadApp(detail: AppGetResponse, outputOption?: string, fetcher?: typeof fetch): Promise<DownloadResult>;
|
|
11
|
+
export {};
|
|
@@ -9,11 +9,11 @@ const MAX_CONTENT_FILE_BYTES = 1024 * 1024 * 1024;
|
|
|
9
9
|
const DOWNLOAD_CONCURRENCY = 4;
|
|
10
10
|
function safeRelativePath(value, label) {
|
|
11
11
|
if (!value || value.includes("\\") || value.includes("\0") || posix.isAbsolute(value)) {
|
|
12
|
-
throw new Error(`Invalid ${label} in
|
|
12
|
+
throw new Error(`Invalid ${label} in app manifest`);
|
|
13
13
|
}
|
|
14
14
|
const segments = value.split("/");
|
|
15
15
|
if (segments.some((segment) => !segment || segment === "." || segment === "..")) {
|
|
16
|
-
throw new Error(`Invalid ${label} in
|
|
16
|
+
throw new Error(`Invalid ${label} in app manifest`);
|
|
17
17
|
}
|
|
18
18
|
return posix.normalize(value);
|
|
19
19
|
}
|
|
@@ -31,7 +31,7 @@ function isManifestFile(value) {
|
|
|
31
31
|
}
|
|
32
32
|
function parseManifest(value) {
|
|
33
33
|
if (!value || typeof value !== "object")
|
|
34
|
-
throw new Error("
|
|
34
|
+
throw new Error("App download manifest is invalid");
|
|
35
35
|
const manifest = value;
|
|
36
36
|
if (manifest.kind !== "cohub.work.artifact-manifest"
|
|
37
37
|
|| manifest.version !== 1
|
|
@@ -45,7 +45,7 @@ function parseManifest(value) {
|
|
|
45
45
|
|| !Array.isArray(manifest.files)
|
|
46
46
|
|| !manifest.files.every(isManifestFile)
|
|
47
47
|
|| manifest.files.length !== manifest.fileCount) {
|
|
48
|
-
throw new Error("
|
|
48
|
+
throw new Error("App download manifest is invalid");
|
|
49
49
|
}
|
|
50
50
|
const seenArtifactPaths = new Set();
|
|
51
51
|
const seenOutputPaths = new Set();
|
|
@@ -54,26 +54,26 @@ function parseManifest(value) {
|
|
|
54
54
|
file.artifactPath = safeRelativePath(file.artifactPath, "artifact path");
|
|
55
55
|
file.outputPath = safeRelativePath(file.outputPath, "output path");
|
|
56
56
|
if (seenArtifactPaths.has(file.artifactPath) || seenOutputPaths.has(file.outputPath)) {
|
|
57
|
-
throw new Error("
|
|
57
|
+
throw new Error("App download manifest contains duplicate paths");
|
|
58
58
|
}
|
|
59
59
|
seenArtifactPaths.add(file.artifactPath);
|
|
60
60
|
seenOutputPaths.add(file.outputPath);
|
|
61
61
|
sizeBytes += file.sizeBytes;
|
|
62
62
|
}
|
|
63
63
|
if (sizeBytes !== manifest.sizeBytes)
|
|
64
|
-
throw new Error("
|
|
64
|
+
throw new Error("App download manifest size is invalid");
|
|
65
65
|
safeRelativePath(manifest.entrypoint, "entrypoint");
|
|
66
66
|
return manifest;
|
|
67
67
|
}
|
|
68
68
|
async function readManifest(url, expectedSha256, fetcher) {
|
|
69
69
|
const response = await fetcher(url);
|
|
70
70
|
if (!response.ok)
|
|
71
|
-
throw new Error(`Failed to download
|
|
71
|
+
throw new Error(`Failed to download app manifest (${response.status})`);
|
|
72
72
|
if (!response.body)
|
|
73
|
-
throw new Error("
|
|
73
|
+
throw new Error("App download manifest is invalid");
|
|
74
74
|
const contentLength = Number(response.headers.get("content-length") ?? 0);
|
|
75
75
|
if (Number.isFinite(contentLength) && contentLength > MANIFEST_MAX_BYTES) {
|
|
76
|
-
throw new Error("
|
|
76
|
+
throw new Error("App download manifest is too large");
|
|
77
77
|
}
|
|
78
78
|
const chunks = [];
|
|
79
79
|
let sizeBytes = 0;
|
|
@@ -81,19 +81,19 @@ async function readManifest(url, expectedSha256, fetcher) {
|
|
|
81
81
|
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
82
82
|
sizeBytes += bytes.byteLength;
|
|
83
83
|
if (sizeBytes > MANIFEST_MAX_BYTES)
|
|
84
|
-
throw new Error("
|
|
84
|
+
throw new Error("App download manifest is too large");
|
|
85
85
|
chunks.push(bytes);
|
|
86
86
|
}
|
|
87
87
|
const bytes = Buffer.concat(chunks, sizeBytes);
|
|
88
88
|
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
89
89
|
if (sha256 !== expectedSha256)
|
|
90
|
-
throw new Error("
|
|
90
|
+
throw new Error("App download manifest checksum mismatch");
|
|
91
91
|
let parsed;
|
|
92
92
|
try {
|
|
93
93
|
parsed = JSON.parse(bytes.toString("utf8"));
|
|
94
94
|
}
|
|
95
95
|
catch {
|
|
96
|
-
throw new Error("
|
|
96
|
+
throw new Error("App download manifest is invalid");
|
|
97
97
|
}
|
|
98
98
|
return parseManifest(parsed);
|
|
99
99
|
}
|
|
@@ -203,25 +203,25 @@ async function downloadFiles(input) {
|
|
|
203
203
|
throw failure;
|
|
204
204
|
return downloadedBytes.reduce((sum, size) => sum + size, 0);
|
|
205
205
|
}
|
|
206
|
-
export async function
|
|
207
|
-
const {
|
|
206
|
+
export async function downloadApp(detail, outputOption, fetcher = fetch) {
|
|
207
|
+
const { app, content } = detail;
|
|
208
208
|
if (!content)
|
|
209
|
-
throw new Error("This
|
|
209
|
+
throw new Error("This app has no published downloadable artifact");
|
|
210
210
|
if (content.kind === "port")
|
|
211
|
-
throw new Error("Port
|
|
211
|
+
throw new Error("Port apps do not have a downloadable artifact");
|
|
212
212
|
if (content.kind === "board")
|
|
213
|
-
throw new Error("Board
|
|
213
|
+
throw new Error("Board apps do not have a restorable file or directory artifact");
|
|
214
214
|
if (!content.download)
|
|
215
|
-
throw new Error("This
|
|
215
|
+
throw new Error("This app version does not support download");
|
|
216
216
|
const manifest = await readManifest(content.download.manifestUrl, content.download.manifestSha256, fetcher);
|
|
217
217
|
if (manifest.targetType !== content.targetType || manifest.targetRef !== content.path) {
|
|
218
|
-
throw new Error("
|
|
218
|
+
throw new Error("App download manifest does not match the published artifact");
|
|
219
219
|
}
|
|
220
220
|
const entry = manifest.files.find((file) => file.artifactPath === manifest.entrypoint);
|
|
221
221
|
if (!entry)
|
|
222
|
-
throw new Error("
|
|
222
|
+
throw new Error("App download manifest entrypoint is missing");
|
|
223
223
|
const hasDirectoryOutput = manifest.targetType === "directory" || manifest.files.length > 1;
|
|
224
|
-
const output = resolve(outputOption ?? (hasDirectoryOutput ?
|
|
224
|
+
const output = resolve(outputOption ?? (hasDirectoryOutput ? app.slug : basename(manifest.targetRef)));
|
|
225
225
|
await assertOutputMissing(output);
|
|
226
226
|
await mkdir(dirname(output), { recursive: true });
|
|
227
227
|
const stage = await mkdtemp(join(dirname(output), `.${basename(output)}.cohub-download-`));
|
|
@@ -237,8 +237,8 @@ export async function downloadWork(detail, outputOption, fetcher = fetch) {
|
|
|
237
237
|
await rm(stage, { recursive: true, force: true });
|
|
238
238
|
}
|
|
239
239
|
return {
|
|
240
|
-
|
|
241
|
-
version:
|
|
240
|
+
appId: app.id,
|
|
241
|
+
version: app.latestVersion,
|
|
242
242
|
kind: hasDirectoryOutput ? "directory" : "file",
|
|
243
243
|
output,
|
|
244
244
|
files: files.length,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CohubHttpClient, ParsedAppRef, AppGetResponse } from "@neta-art/cohub";
|
|
2
|
+
import { formatAppRef, parseAppRef } from "@neta-art/cohub";
|
|
3
|
+
export type { ParsedAppRef };
|
|
4
|
+
export { formatAppRef, parseAppRef };
|
|
5
|
+
export declare function getAppByRef(client: CohubHttpClient, input: string): Promise<AppGetResponse>;
|
package/dist/app-ref.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { formatAppRef, parseAppRef } from "@neta-art/cohub";
|
|
2
|
+
export { formatAppRef, parseAppRef };
|
|
3
|
+
export function getAppByRef(client, input) {
|
|
4
|
+
const ref = parseAppRef(input);
|
|
5
|
+
return "id" in ref
|
|
6
|
+
? client.apps.get(ref.id)
|
|
7
|
+
: client.apps.getBySlug(ref.username, ref.spaceSlug, ref.appSlug);
|
|
8
|
+
}
|
|
@@ -69,31 +69,31 @@ function printOrder(order) {
|
|
|
69
69
|
{ key: "paidAt", label: "Paid At" },
|
|
70
70
|
]);
|
|
71
71
|
}
|
|
72
|
-
export function
|
|
73
|
-
const commerceCmd =
|
|
72
|
+
export function registerAppCommerce(appsCmd) {
|
|
73
|
+
const commerceCmd = appsCmd
|
|
74
74
|
.command("commerce")
|
|
75
|
-
.description("
|
|
75
|
+
.description("App commerce operations")
|
|
76
76
|
.addHelpText("after", `
|
|
77
77
|
Examples:
|
|
78
|
-
cohub
|
|
79
|
-
cohub
|
|
80
|
-
cohub
|
|
78
|
+
cohub apps commerce products resolve --app-id <app-id> --product-key pro_pack
|
|
79
|
+
cohub apps commerce entitlements --app-id <app-id>
|
|
80
|
+
cohub apps commerce credits consume --app-id <app-id> --amount 100
|
|
81
81
|
`);
|
|
82
82
|
const productsCmd = commerceCmd
|
|
83
83
|
.command("products")
|
|
84
84
|
.description("Resolve commerce products");
|
|
85
85
|
productsCmd
|
|
86
86
|
.command("resolve")
|
|
87
|
-
.description("Resolve public products for
|
|
88
|
-
.option("--
|
|
87
|
+
.description("Resolve public products for an app")
|
|
88
|
+
.option("--app-id <id>", "App ID")
|
|
89
89
|
.option("--product-key <key>", "Product key", collectOption)
|
|
90
90
|
.option("--json", "Output as JSON")
|
|
91
91
|
.action(async (opts) => {
|
|
92
|
-
const
|
|
92
|
+
const appId = requireText(opts.appId, "app ID", "--app-id <id>");
|
|
93
93
|
const productKeys = requireList(opts.productKey, "product key", "--product-key <key>");
|
|
94
94
|
const client = createClient();
|
|
95
95
|
try {
|
|
96
|
-
const result = await client.
|
|
96
|
+
const result = await client.appCommerce.resolveProducts(appId, { productKeys });
|
|
97
97
|
if (jsonRequested(opts))
|
|
98
98
|
return outJson(result);
|
|
99
99
|
printProducts(result.products);
|
|
@@ -104,14 +104,14 @@ Examples:
|
|
|
104
104
|
});
|
|
105
105
|
commerceCmd
|
|
106
106
|
.command("entitlements")
|
|
107
|
-
.description("Show viewer entitlements and credit balance for
|
|
108
|
-
.option("--
|
|
107
|
+
.description("Show viewer entitlements and credit balance for an app")
|
|
108
|
+
.option("--app-id <id>", "App ID")
|
|
109
109
|
.option("--json", "Output as JSON")
|
|
110
110
|
.action(async (opts) => {
|
|
111
|
-
const
|
|
111
|
+
const appId = requireText(opts.appId, "app ID", "--app-id <id>");
|
|
112
112
|
const client = createClient();
|
|
113
113
|
try {
|
|
114
|
-
const result = await client.
|
|
114
|
+
const result = await client.appCommerce.getEntitlements(appId);
|
|
115
115
|
if (jsonRequested(opts))
|
|
116
116
|
return outJson(result);
|
|
117
117
|
printEntitlements(result);
|
|
@@ -122,17 +122,17 @@ Examples:
|
|
|
122
122
|
});
|
|
123
123
|
const creditsCmd = commerceCmd
|
|
124
124
|
.command("credits")
|
|
125
|
-
.description("Consume credits for
|
|
125
|
+
.description("Consume credits for an app");
|
|
126
126
|
creditsCmd
|
|
127
127
|
.command("consume")
|
|
128
|
-
.description("Consume credits for
|
|
129
|
-
.option("--
|
|
128
|
+
.description("Consume credits for an app (self by default)")
|
|
129
|
+
.option("--app-id <id>", "App ID")
|
|
130
130
|
.option("--amount <n>", "Positive integer credit amount")
|
|
131
131
|
.option("--operation-id <id>", "Idempotency key (generated when omitted)")
|
|
132
132
|
.option("--reason <text>", "Reason for the consumption")
|
|
133
133
|
.option("--json", "Output as JSON")
|
|
134
134
|
.action(async (opts) => {
|
|
135
|
-
const
|
|
135
|
+
const appId = requireText(opts.appId, "app ID", "--app-id <id>");
|
|
136
136
|
const amountText = requireText(opts.amount, "amount", "--amount <n>");
|
|
137
137
|
const amount = Number.parseInt(amountText, 10);
|
|
138
138
|
if (!Number.isSafeInteger(amount) || amount <= 0) {
|
|
@@ -141,7 +141,7 @@ Examples:
|
|
|
141
141
|
const operationId = opts.operationId?.trim() || crypto.randomUUID();
|
|
142
142
|
const client = createClient();
|
|
143
143
|
try {
|
|
144
|
-
const result = await client.
|
|
144
|
+
const result = await client.appCommerce.consumeCredits(appId, {
|
|
145
145
|
amount,
|
|
146
146
|
operationId,
|
|
147
147
|
reason: opts.reason,
|
|
@@ -167,16 +167,16 @@ Examples:
|
|
|
167
167
|
});
|
|
168
168
|
commerceCmd
|
|
169
169
|
.command("purchase")
|
|
170
|
-
.description("Create
|
|
171
|
-
.option("--
|
|
170
|
+
.description("Create an app purchase checkout")
|
|
171
|
+
.option("--app-id <id>", "App ID")
|
|
172
172
|
.option("--product-key <key>", "Product key")
|
|
173
173
|
.option("--json", "Output as JSON")
|
|
174
174
|
.action(async (opts) => {
|
|
175
|
-
const
|
|
175
|
+
const appId = requireText(opts.appId, "app ID", "--app-id <id>");
|
|
176
176
|
const productKey = requireText(opts.productKey, "product key", "--product-key <key>");
|
|
177
177
|
const client = createClient();
|
|
178
178
|
try {
|
|
179
|
-
const result = await client.
|
|
179
|
+
const result = await client.appCommerce.purchase(appId, { productKey });
|
|
180
180
|
if (jsonRequested(opts))
|
|
181
181
|
return outJson(result);
|
|
182
182
|
ok(`Checkout created: ${result.checkout.orderId}`);
|
|
@@ -205,16 +205,16 @@ Examples:
|
|
|
205
205
|
.description("Look up commerce orders");
|
|
206
206
|
ordersCmd
|
|
207
207
|
.command("get")
|
|
208
|
-
.description("Show
|
|
209
|
-
.option("--
|
|
208
|
+
.description("Show an app commerce order")
|
|
209
|
+
.option("--app-id <id>", "App ID")
|
|
210
210
|
.option("--order-id <id>", "Order ID")
|
|
211
211
|
.option("--json", "Output as JSON")
|
|
212
212
|
.action(async (opts) => {
|
|
213
|
-
const
|
|
213
|
+
const appId = requireText(opts.appId, "app ID", "--app-id <id>");
|
|
214
214
|
const orderId = requireText(opts.orderId, "order ID", "--order-id <id>");
|
|
215
215
|
const client = createClient();
|
|
216
216
|
try {
|
|
217
|
-
const result = await client.
|
|
217
|
+
const result = await client.appCommerce.getOrder(appId, orderId);
|
|
218
218
|
if (jsonRequested(opts))
|
|
219
219
|
return outJson(result);
|
|
220
220
|
printOrder(result.order);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type CohubHttpClient, type AppViewStatsResponse } from "@neta-art/cohub";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
export declare function getAppStatsByRef(client: CohubHttpClient, app: string): Promise<AppViewStatsResponse>;
|
|
4
|
+
export declare function registerApps(program: Command): void;
|