@moonbase.sh/cli 0.0.0-next-20261001173449 → 0.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.
Files changed (3) hide show
  1. package/package.json +1 -36
  2. package/README.md +0 -119
  3. package/dist/cli.js +0 -448
package/package.json CHANGED
@@ -1,36 +1 @@
1
- {
2
- "name": "@moonbase.sh/cli",
3
- "type": "module",
4
- "version": "0.0.0-next-20261001173449",
5
- "description": "Publish product releases to Moonbase.sh from the command line or a CI job",
6
- "author": "Tobias Lønnerød Madsen <m@dsen.tv>",
7
- "license": "MIT",
8
- "bin": {
9
- "moonbase": "./dist/cli.js"
10
- },
11
- "files": [
12
- "dist/**"
13
- ],
14
- "engines": {
15
- "node": ">=20.0.0"
16
- },
17
- "dependencies": {
18
- "@moonbase.sh/api": "0.0.0-next-20261001173449"
19
- },
20
- "devDependencies": {
21
- "@types/node": "^20.19.33",
22
- "tsup": "^8.5.0",
23
- "typescript": "~5.8.3",
24
- "vitest": "^2.1.4"
25
- },
26
- "scripts": {
27
- "build": "tsup src/cli.ts --format esm --target node20",
28
- "dev": "tsup src/cli.ts --format esm --target node20 --watch",
29
- "type-check": "tsc --noEmit -p tsconfig.typecheck.json",
30
- "lint": "eslint . --cache --cache-location node_modules/.cache/eslint/ --cache-strategy content",
31
- "lint:fix": "eslint . --cache --cache-location node_modules/.cache/eslint/ --cache-strategy content --fix",
32
- "test": "vitest run",
33
- "test:watch": "vitest",
34
- "test:integration": "TEST_INTEGRATION=true vitest run"
35
- }
36
- }
1
+ { "name": "@moonbase.sh/cli", "version": "0.0.0", "description": "Placeholder for the Moonbase CLI. The first release is 0.1.0.", "license": "MIT" }
package/README.md DELETED
@@ -1,119 +0,0 @@
1
- # @moonbase.sh/cli
2
-
3
- Publish product releases to Moonbase from the command line or a CI job, in one command.
4
-
5
- ```bash
6
- export MOONBASE_ENDPOINT=https://acme.moonbase.sh
7
- export MOONBASE_API_KEY=mb_...
8
-
9
- npx @moonbase.sh/cli release moon-terminal v1.4.0 "dist/Moon Terminal.dmg" "dist/Moon Terminal Setup.exe" --notes-file NOTES.md --publish
10
- ```
11
-
12
- That uploads both files, creates release 1.4.0 of the product `moon-terminal` from them, and
13
- publishes it. It needs Node.js 20 or later.
14
-
15
- `MOONBASE_ENDPOINT` is your account's address and `MOONBASE_API_KEY` an API key. Both are under
16
- Account settings > Developer in Moonbase. Every command also takes them as `--endpoint` and
17
- `--api-key`, which win over the variables. Keep the key secret: in CI, put it in a secret rather
18
- than in the workflow file. Prefer the variable to `--api-key` on a machine you share, since other
19
- users can see a command's options while it runs, and your shell history keeps them.
20
-
21
- ## Commands
22
-
23
- ### `release <product-id> <version> <file...>`
24
-
25
- Uploads the files and creates the release from them. Before it uploads anything, it checks that the
26
- product exists, that the release does not (unless you pass `--add`) and that every file is there.
27
-
28
- - The version is `MAJOR.MINOR.PATCH`. A leading `v` is dropped, so a Git tag like `v1.4.0` works as
29
- it is.
30
- - Each download is named after its file.
31
- - A file's platform is guessed from its extension (`.dmg` and `.pkg` are Mac, `.exe` and `.msi`
32
- Windows, `.deb`, `.rpm` and `.AppImage` Linux), then from a word in its name (`mac`, `win`,
33
- `linux`), and is Universal otherwise. Its architecture is guessed from a word in its name (`arm64`,
34
- `x64`, `x86_64`, `universal` and the like) and is left out otherwise. The command lists what it
35
- guessed before it uploads. `--platform` and `--arch` set them for every file instead.
36
- - A file larger than 64 MiB is sent in parts, four at a time, and a part that fails is retried.
37
-
38
- | Option | |
39
- |---|---|
40
- | `--notes <text>` | The release notes |
41
- | `--notes-file <path>` | Read the release notes from a file |
42
- | `--platform <name>` | `Universal`, `Windows`, `Linux`, `Mac`, `iOS` or `Android` |
43
- | `--arch <name>` | `Universal`, `X86`, `X64`, `Arm` or `Arm64` |
44
- | `--publish` | Publish the release, so customers can download it. Without it the release stays unpublished. |
45
- | `--add` | If the release exists, add the files to it. A file with the same name, platform and architecture as one already there replaces it. |
46
- | `--json` | Print the release as JSON |
47
- | `--endpoint <url>` | Your account's address, instead of `MOONBASE_ENDPOINT` |
48
- | `--api-key <key>` | An API key, instead of `MOONBASE_API_KEY` |
49
-
50
- ### `publish <product-id> <version>`
51
-
52
- Publishes a release, so customers can download it. Takes `--json`, `--endpoint` and `--api-key`.
53
-
54
- ### `upload <file>`
55
-
56
- Uploads one file and prints its key, for adding it to a release through the API or
57
- [`@moonbase.sh/api`](https://www.npmjs.com/package/@moonbase.sh/api). Takes `--endpoint` and
58
- `--api-key`.
59
-
60
- Progress and messages go to stderr, so stdout holds only the key or, with `--json`, the release.
61
- The exit code is 0 when the command worked, 1 when it failed and 2 when it was run the wrong way.
62
-
63
- ## GitHub Actions
64
-
65
- Release when a version tag is pushed:
66
-
67
- ```yaml
68
- on:
69
- push:
70
- tags: ['v*']
71
-
72
- jobs:
73
- release:
74
- runs-on: ubuntu-latest
75
- steps:
76
- - uses: actions/checkout@v4
77
- - run: ./build.sh # puts the installers in dist/
78
- - run: npx @moonbase.sh/cli release moon-terminal ${{ github.ref_name }} dist/* --publish
79
- env:
80
- MOONBASE_ENDPOINT: https://acme.moonbase.sh
81
- MOONBASE_API_KEY: ${{ secrets.MOONBASE_API_KEY }}
82
- ```
83
-
84
- When each platform builds on its own runner, let each job add its files with `--add`, and publish
85
- once they all have:
86
-
87
- ```yaml
88
- jobs:
89
- build:
90
- strategy:
91
- matrix:
92
- os: [macos-latest, windows-latest, ubuntu-latest]
93
- runs-on: ${{ matrix.os }}
94
- steps:
95
- - uses: actions/checkout@v4
96
- - run: ./build.sh
97
- shell: bash
98
- # bash, so `dist/*` is expanded on Windows too.
99
- - run: npx @moonbase.sh/cli release moon-terminal ${{ github.ref_name }} dist/* --add
100
- shell: bash
101
- env:
102
- MOONBASE_ENDPOINT: https://acme.moonbase.sh
103
- MOONBASE_API_KEY: ${{ secrets.MOONBASE_API_KEY }}
104
-
105
- publish:
106
- needs: build
107
- runs-on: ubuntu-latest
108
- steps:
109
- - run: npx @moonbase.sh/cli publish moon-terminal ${{ github.ref_name }}
110
- env:
111
- MOONBASE_ENDPOINT: https://acme.moonbase.sh
112
- MOONBASE_API_KEY: ${{ secrets.MOONBASE_API_KEY }}
113
- ```
114
-
115
- Jobs that finish at the same moment are fine: when Moonbase turns one job's change away because
116
- another's landed first, the command sends it again. A rerun of a job replaces the files it added
117
- before, since they have the same names.
118
-
119
- To keep a workflow on a version you have tried, pin it: `npx @moonbase.sh/cli@0.1 release ...`.
package/dist/cli.js DELETED
@@ -1,448 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/cli.ts
4
- import process2 from "process";
5
-
6
- // src/main.ts
7
- import fs from "fs";
8
- import process from "process";
9
- import { parseArgs } from "util";
10
- import { ConflictError, MoonbaseClient, MoonbaseError, NotAuthenticatedError, NotFoundError } from "@moonbase.sh/api";
11
-
12
- // package.json
13
- var version = "0.0.0-next-20261001173449";
14
-
15
- // src/files.ts
16
- import path from "path";
17
- import { Architecture, Platform } from "@moonbase.sh/api";
18
- var platformByExtension = {
19
- exe: Platform.Windows,
20
- msi: Platform.Windows,
21
- msix: Platform.Windows,
22
- appx: Platform.Windows,
23
- dmg: Platform.Mac,
24
- pkg: Platform.Mac,
25
- deb: Platform.Linux,
26
- rpm: Platform.Linux,
27
- appimage: Platform.Linux,
28
- snap: Platform.Linux,
29
- flatpak: Platform.Linux
30
- };
31
- var platformByWord = [
32
- [["mac", "macos", "osx", "darwin"], Platform.Mac],
33
- [["win", "windows", "win32", "win64"], Platform.Windows],
34
- [["linux"], Platform.Linux]
35
- ];
36
- var archByWord = [
37
- [["arm64", "aarch64"], Architecture.Arm64],
38
- [["x86_64", "x86-64", "x64", "amd64"], Architecture.X64],
39
- [["x86", "i386", "i686", "ia32"], Architecture.X86],
40
- [["armv7", "armv7l", "armhf"], Architecture.Arm],
41
- [["universal"], Architecture.Universal]
42
- ];
43
- var contentTypeByExtension = {
44
- "7z": "application/x-7z-compressed",
45
- "appimage": "application/vnd.appimage",
46
- "deb": "application/vnd.debian.binary-package",
47
- "dmg": "application/x-apple-diskimage",
48
- "exe": "application/vnd.microsoft.portable-executable",
49
- "gz": "application/gzip",
50
- "msi": "application/x-msi",
51
- "pdf": "application/pdf",
52
- "rpm": "application/x-rpm",
53
- "tar": "application/x-tar",
54
- "tgz": "application/gzip",
55
- "zip": "application/zip"
56
- };
57
- function describeFile(filePath, size, overrides = {}) {
58
- const name = path.basename(filePath);
59
- const extension = path.extname(name).slice(1).toLowerCase();
60
- const words = name.slice(0, name.length - path.extname(name).length).toLowerCase();
61
- return {
62
- path: filePath,
63
- name,
64
- size,
65
- platform: overrides.platform ?? platformByExtension[extension] ?? findWord(words, platformByWord) ?? Platform.Universal,
66
- arch: overrides.arch ?? findWord(words, archByWord),
67
- contentType: contentTypeByExtension[extension] ?? "application/octet-stream"
68
- };
69
- }
70
- function findWord(text, table) {
71
- return table.find(([words]) => words.some((word) => new RegExp(`(^|[^a-z0-9])${word}($|[^a-z0-9])`).test(text)))?.[1];
72
- }
73
- function parseVersion(input) {
74
- const match = /^v?(\d+)\.(\d+)\.(\d+)$/i.exec(input.trim());
75
- return match ? match.slice(1).map(Number).join(".") : void 0;
76
- }
77
- function parsePlatform(input) {
78
- return Object.values(Platform).find((platform) => platform.toLowerCase() === input.toLowerCase());
79
- }
80
- function parseArch(input) {
81
- return Object.values(Architecture).find((arch) => arch !== Architecture.Unknown && arch.toLowerCase() === input.toLowerCase());
82
- }
83
- function formatBytes(bytes) {
84
- const units = ["bytes", "KB", "MB", "GB", "TB"];
85
- let value = bytes;
86
- let unit = 0;
87
- while (value >= 1e3 && unit < units.length - 1) {
88
- value /= 1e3;
89
- unit++;
90
- }
91
- return unit === 0 ? `${bytes} bytes` : `${value.toFixed(1)} ${units[unit]}`;
92
- }
93
-
94
- // src/main.ts
95
- var UsageError = class extends Error {
96
- constructor(message, command) {
97
- super(message);
98
- this.command = command;
99
- }
100
- };
101
- var silentLogger = { debug: () => {
102
- }, log: () => {
103
- }, warn: () => {
104
- }, error: () => {
105
- } };
106
- var raceAttempts = 6;
107
- var help = {
108
- main: `Usage: moonbase <command> [options]
109
-
110
- Publish product releases to Moonbase from the command line or a CI job.
111
-
112
- Commands:
113
- release <product-id> <version> <file...> Upload files and create a release from them
114
- upload <file> Upload one file and print its key
115
- publish <product-id> <version> Publish a release
116
-
117
- Environment:
118
- MOONBASE_ENDPOINT Your account's address, like https://acme.moonbase.sh
119
- MOONBASE_API_KEY An API key
120
- Both are under Account settings > Developer in Moonbase. Every command also takes them as
121
- --endpoint and --api-key.
122
-
123
- Run "moonbase help <command>" for a command's options.
124
- `,
125
- release: `Usage: moonbase release <product-id> <version> <file...> [options]
126
-
127
- Uploads the files and creates the release from them. The version is MAJOR.MINOR.PATCH, and a
128
- leading "v" is dropped, so a Git tag like v1.4.0 works as it is. The release stays unpublished
129
- unless you pass --publish.
130
-
131
- A file's platform is guessed from its extension (.dmg and .pkg are Mac, .exe and .msi Windows,
132
- .deb, .rpm and .AppImage Linux), then from a word in its name (mac, win, linux), and is Universal
133
- otherwise. Its architecture is guessed from a word in its name (arm64, x64, x86_64, universal) and
134
- is left out otherwise. --platform and --arch set them for every file instead.
135
-
136
- Options:
137
- --notes <text> The release notes
138
- --notes-file <path> Read the release notes from a file
139
- --platform <name> Universal, Windows, Linux, Mac, iOS or Android
140
- --arch <name> Universal, X86, X64, Arm or Arm64
141
- --publish Publish the release, so customers can download it
142
- --add If the release exists, add the files to it. A file with the same name,
143
- platform and architecture as one already there replaces it.
144
- --json Print the release as JSON
145
- --endpoint <url> Your account's address, instead of MOONBASE_ENDPOINT
146
- --api-key <key> An API key, instead of MOONBASE_API_KEY. Other users of the machine
147
- can see it while the command runs, so prefer the variable.
148
- `,
149
- upload: `Usage: moonbase upload <file> [options]
150
-
151
- Uploads a file and prints its key, for adding it to a release with the API.
152
-
153
- Options:
154
- --endpoint <url> Your account's address, instead of MOONBASE_ENDPOINT
155
- --api-key <key> An API key, instead of MOONBASE_API_KEY. Other users of the machine
156
- can see it while the command runs, so prefer the variable.
157
- `,
158
- publish: `Usage: moonbase publish <product-id> <version> [options]
159
-
160
- Publishes a release, so customers can download it.
161
-
162
- Options:
163
- --json Print the release as JSON
164
- --endpoint <url> Your account's address, instead of MOONBASE_ENDPOINT
165
- --api-key <key> An API key, instead of MOONBASE_API_KEY. Other users of the machine
166
- can see it while the command runs, so prefer the variable.
167
- `
168
- };
169
- async function main(argv, io) {
170
- const [command, ...args] = argv;
171
- try {
172
- switch (command) {
173
- case "release":
174
- return await release(args, io);
175
- case "upload":
176
- return await upload(args, io);
177
- case "publish":
178
- return await publish(args, io);
179
- case "help":
180
- case "--help":
181
- case "-h":
182
- io.stdout.write(help[args[0]] ?? help.main);
183
- return 0;
184
- case "--version":
185
- io.stdout.write(`${version}
186
- `);
187
- return 0;
188
- case void 0:
189
- io.stderr.write(help.main);
190
- return 2;
191
- default:
192
- throw new UsageError(`There is no command "${command}".`, "main");
193
- }
194
- } catch (error) {
195
- if (error instanceof UsageError) {
196
- io.stderr.write(`${error.message}
197
- `);
198
- if (error.command)
199
- io.stderr.write(`Run "moonbase help${error.command === "main" ? "" : ` ${error.command}`}" for how to use it.
200
- `);
201
- return 2;
202
- }
203
- io.stderr.write(`Error: ${describeError(error)}
204
- `);
205
- return 1;
206
- }
207
- }
208
- async function release(args, io) {
209
- const { values, positionals } = parse("release", () => parseArgs({
210
- args,
211
- allowPositionals: true,
212
- options: {
213
- "notes": { type: "string" },
214
- "notes-file": { type: "string" },
215
- "platform": { type: "string" },
216
- "arch": { type: "string" },
217
- "publish": { type: "boolean", default: false },
218
- "add": { type: "boolean", default: false },
219
- "json": { type: "boolean", default: false },
220
- "endpoint": { type: "string" },
221
- "api-key": { type: "string" },
222
- "help": { type: "boolean", short: "h", default: false }
223
- }
224
- }));
225
- if (values.help) {
226
- io.stdout.write(help.release);
227
- return 0;
228
- }
229
- const [productId, versionArgument, ...paths] = positionals;
230
- if (!productId || !versionArgument || paths.length === 0)
231
- throw new UsageError("release takes a product ID, a version and at least one file.", "release");
232
- const version2 = readVersion(versionArgument, "release");
233
- if (values.notes !== void 0 && values["notes-file"] !== void 0)
234
- throw new UsageError("Pass --notes or --notes-file, not both.", "release");
235
- const platform = values.platform === void 0 ? void 0 : parsePlatform(values.platform);
236
- if (values.platform !== void 0 && !platform)
237
- throw new UsageError(`There is no platform "${values.platform}".`, "release");
238
- const arch = values.arch === void 0 ? void 0 : parseArch(values.arch);
239
- if (values.arch !== void 0 && !arch)
240
- throw new UsageError(`There is no architecture "${values.arch}".`, "release");
241
- const description = values["notes-file"] !== void 0 ? await fs.promises.readFile(values["notes-file"], "utf8") : values.notes;
242
- const files = await inspectFiles(paths, { platform, arch });
243
- const client = connect(values.endpoint, values["api-key"], io);
244
- try {
245
- await client.products.get(productId);
246
- } catch (error) {
247
- if (error instanceof NotFoundError)
248
- throw new Error(`There is no product with the ID "${productId}".`);
249
- throw error;
250
- }
251
- let existing = await findRelease(client, productId, version2);
252
- if (existing && !values.add)
253
- throw new Error(`Release ${version2} of ${productId} already exists. Pass --add to add these files to it.`);
254
- io.stderr.write(`${existing ? "Adding to" : "Creating"} release ${version2} of ${productId}:
255
- `);
256
- const nameWidth = Math.max(...files.map((file) => file.name.length));
257
- for (const file of files)
258
- io.stderr.write(` ${file.name.padEnd(nameWidth)} ${[file.platform, file.arch].filter(Boolean).join(", ")}, ${formatBytes(file.size)}
259
- `);
260
- const downloads = [];
261
- for (const file of files)
262
- downloads.push({ name: file.name, platform: file.platform, arch: file.arch, key: await uploadFile(client, file, io) });
263
- let result;
264
- if (!existing) {
265
- try {
266
- result = await retryRace(io, () => client.products.createRelease(productId, { version: version2, description, downloads }, { publishImmediately: values.publish }));
267
- io.stderr.write(`Created release ${version2} of ${productId}${values.publish ? " and published it" : ""}.
268
- `);
269
- } catch (error) {
270
- if (!(error instanceof ConflictError && values.add))
271
- throw error;
272
- existing = await findRelease(client, productId, version2);
273
- }
274
- }
275
- if (!result) {
276
- if (description !== void 0)
277
- io.stderr.write(`Release ${version2} already existed, so its notes were left as they were.
278
- `);
279
- for (const download of downloads)
280
- result = await retryRace(io, () => client.products.addReleaseDownload(productId, version2, download));
281
- io.stderr.write(`Added ${downloads.length === 1 ? "the file" : `${downloads.length} files`} to release ${version2} of ${productId}.
282
- `);
283
- if (existing?.publishedAt)
284
- io.stderr.write(`The release was already published, so customers can download them now.
285
- `);
286
- if (values.publish && !existing?.publishedAt) {
287
- result = await retryRace(io, () => client.products.publishRelease(productId, version2));
288
- io.stderr.write(`Published it.
289
- `);
290
- }
291
- }
292
- if (!result.publishedAt)
293
- io.stderr.write(`It is not published yet. Publish it with: moonbase publish ${productId} ${version2}
294
- `);
295
- if (values.json)
296
- io.stdout.write(`${JSON.stringify(result, void 0, 2)}
297
- `);
298
- return 0;
299
- }
300
- async function upload(args, io) {
301
- const { values, positionals } = parse("upload", () => parseArgs({
302
- args,
303
- allowPositionals: true,
304
- options: {
305
- "endpoint": { type: "string" },
306
- "api-key": { type: "string" },
307
- "help": { type: "boolean", short: "h", default: false }
308
- }
309
- }));
310
- if (values.help) {
311
- io.stdout.write(help.upload);
312
- return 0;
313
- }
314
- if (positionals.length !== 1)
315
- throw new UsageError("upload takes one file.", "upload");
316
- const [file] = await inspectFiles(positionals);
317
- const client = connect(values.endpoint, values["api-key"], io);
318
- io.stdout.write(`${await uploadFile(client, file, io)}
319
- `);
320
- return 0;
321
- }
322
- async function publish(args, io) {
323
- const { values, positionals } = parse("publish", () => parseArgs({
324
- args,
325
- allowPositionals: true,
326
- options: {
327
- "json": { type: "boolean", default: false },
328
- "endpoint": { type: "string" },
329
- "api-key": { type: "string" },
330
- "help": { type: "boolean", short: "h", default: false }
331
- }
332
- }));
333
- if (values.help) {
334
- io.stdout.write(help.publish);
335
- return 0;
336
- }
337
- const [productId, versionArgument, ...rest] = positionals;
338
- if (!productId || !versionArgument || rest.length > 0)
339
- throw new UsageError("publish takes a product ID and a version.", "publish");
340
- const version2 = readVersion(versionArgument, "publish");
341
- const client = connect(values.endpoint, values["api-key"], io);
342
- let result;
343
- try {
344
- result = await retryRace(io, () => client.products.publishRelease(productId, version2));
345
- } catch (error) {
346
- if (error instanceof NotFoundError)
347
- throw new Error(`There is no release ${version2} of a product with the ID "${productId}".`);
348
- throw error;
349
- }
350
- io.stderr.write(`Published release ${version2} of ${productId}.
351
- `);
352
- if (values.json)
353
- io.stdout.write(`${JSON.stringify(result, void 0, 2)}
354
- `);
355
- return 0;
356
- }
357
- function parse(command, read) {
358
- try {
359
- return read();
360
- } catch (error) {
361
- throw new UsageError(error instanceof Error ? error.message : String(error), command);
362
- }
363
- }
364
- function readVersion(argument, command) {
365
- const version2 = parseVersion(argument);
366
- if (!version2)
367
- throw new UsageError(`"${argument}" is not a version. Use MAJOR.MINOR.PATCH, like 1.4.0.`, command);
368
- return version2;
369
- }
370
- function connect(endpointOption, apiKeyOption, io) {
371
- let endpoint = endpointOption ?? io.env.MOONBASE_ENDPOINT;
372
- const apiKey = apiKeyOption ?? io.env.MOONBASE_API_KEY;
373
- if (!endpoint)
374
- throw new UsageError("Set MOONBASE_ENDPOINT, or pass --endpoint, to your account's address, like https://acme.moonbase.sh. It is under Account settings > Developer in Moonbase.");
375
- if (!apiKey)
376
- throw new UsageError("Set MOONBASE_API_KEY, or pass --api-key, to an API key from Account settings > Developer in Moonbase.");
377
- if (!/^https?:\/\//i.test(endpoint))
378
- endpoint = `https://${endpoint}`;
379
- return io.createClient?.(endpoint, apiKey) ?? new MoonbaseClient({ endpoint, apiKey, logger: silentLogger });
380
- }
381
- async function inspectFiles(paths, overrides) {
382
- if (typeof fs.openAsBlob !== "function")
383
- throw new Error(`Uploading needs Node.js 20 or later, and this is ${process.version}.`);
384
- const files = [];
385
- for (const filePath of paths) {
386
- const stats = await fs.promises.stat(filePath).catch(() => void 0);
387
- if (!stats)
388
- throw new Error(`There is no file at ${filePath}.`);
389
- if (!stats.isFile())
390
- throw new Error(`${filePath} is not a file. Archive a folder, like a .app bundle, before uploading it.`);
391
- files.push(describeFile(filePath, stats.size, overrides));
392
- }
393
- const seen = /* @__PURE__ */ new Set();
394
- for (const file of files) {
395
- const identity = [file.name, file.platform, file.arch].join("/");
396
- if (seen.has(identity))
397
- throw new Error(`Two of the files are ${file.name} for ${[file.platform, file.arch].filter(Boolean).join(", ")}, and a release holds only one.`);
398
- seen.add(identity);
399
- }
400
- return files;
401
- }
402
- async function uploadFile(client, file, io) {
403
- io.stderr.write(`Uploading ${file.name}
404
- `);
405
- const { key } = await client.downloads.upload(await fs.openAsBlob(file.path, { type: file.contentType }), {
406
- contentType: file.contentType,
407
- onProgress: ({ uploadedBytes, totalBytes }) => {
408
- if (uploadedBytes < totalBytes)
409
- io.stderr.write(` ${formatBytes(uploadedBytes)} of ${formatBytes(totalBytes)} (${Math.floor(uploadedBytes / totalBytes * 100)}%)
410
- `);
411
- }
412
- });
413
- return key;
414
- }
415
- async function retryRace(io, write) {
416
- for (let attempt = 1; ; attempt++) {
417
- try {
418
- return await write();
419
- } catch (error) {
420
- const status = error instanceof MoonbaseError ? error.problemDetails?.status : void 0;
421
- if (attempt >= raceAttempts || status === void 0 || status < 500)
422
- throw error;
423
- const pause = 500 + Math.floor(Math.random() * 2500);
424
- await (io.wait?.(pause) ?? new Promise((resolve) => setTimeout(resolve, pause)));
425
- }
426
- }
427
- }
428
- async function findRelease(client, productId, version2) {
429
- let page = await client.products.queryReleases(productId, { pageSize: 100 });
430
- while (true) {
431
- const found = page.items.find((release2) => release2.version === version2);
432
- if (found || !page.hasMore)
433
- return found;
434
- page = await page.getNext();
435
- }
436
- }
437
- function describeError(error) {
438
- if (error instanceof NotAuthenticatedError)
439
- return "Moonbase did not accept the API key.";
440
- if (error instanceof MoonbaseError && error.problemDetails)
441
- return error.problemDetails.detail ?? `${error.problemDetails.title} (HTTP ${error.problemDetails.status})`;
442
- return error instanceof Error ? error.message : String(error);
443
- }
444
-
445
- // src/cli.ts
446
- main(process2.argv.slice(2), { env: process2.env, stdout: process2.stdout, stderr: process2.stderr }).then((exitCode) => {
447
- process2.exitCode = exitCode;
448
- });