@promte/app-upload 0.1.11 → 0.1.12

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/README.md CHANGED
@@ -11,11 +11,12 @@ npx @promte/app-upload push
11
11
  ## Commands
12
12
  ```
13
13
  npx @promte/app-upload config
14
- - interactive setup for host/workspaceId/dist (stored in ./promte-app-upload.config.json)
14
+ - interactive setup for backend host/workspaceId/dist (stored in ./promte-app-upload.config.json)
15
15
 
16
16
  npx @promte/app-upload push [--host ...] [--workspaceId ...] [--dist ...] [--zip ...] [--token ...]
17
17
  - uploads ./dist by default
18
- - host defaults to https://app.promte.dk
18
+ - host is the workspace backend URL used for prepare/finalize
19
+ - assets upload to app.promte.dk (or backend-provided uploadHost)
19
20
  - use --zip <file> to upload an existing zip
20
21
  ```
21
22
 
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
- import os from "node:os";
4
3
  import path from "node:path";
5
4
  import AdmZip from "adm-zip";
6
5
  import undici from "undici";
@@ -10,6 +9,7 @@ const { FormData, Blob, fetch: undiciFetch } = undici;
10
9
  const BlobCtor = globalThis.Blob || Blob;
11
10
 
12
11
  const DEFAULT_HOST = "https://app.promte.dk";
12
+ const DEFAULT_UPLOAD_HOST = "https://app.promte.dk";
13
13
  const DEFAULT_DIST = "./dist";
14
14
  const CONFIG_PATH = path.resolve(process.cwd(), "promte-app-upload.config.json");
15
15
 
@@ -21,7 +21,8 @@ function usage() {
21
21
  " promte-app-upload push [--host <url>] [--workspaceId <id>] [--dist <path>] [--zip <file>] [--token <token>]",
22
22
  "",
23
23
  "Defaults:",
24
- ` host: ${DEFAULT_HOST}`,
24
+ ` host (backend URL): ${DEFAULT_HOST}`,
25
+ ` upload host: ${DEFAULT_UPLOAD_HOST}`,
25
26
  ` dist: ${DEFAULT_DIST}`,
26
27
  ` config: ${CONFIG_PATH}`,
27
28
  ].join("\n"),
@@ -104,7 +105,7 @@ function assert(condition, message) {
104
105
 
105
106
  async function runConfig() {
106
107
  const current = loadConfig();
107
- const host = await prompt("Host", current.host || DEFAULT_HOST);
108
+ const host = await prompt("Backend host", current.host || DEFAULT_HOST);
108
109
  const workspaceId = await prompt("Workspace ID", current.workspaceId || "");
109
110
  const dist = await prompt("Dist path", current.dist || DEFAULT_DIST);
110
111
  assert(workspaceId, "workspaceId is required");
@@ -172,6 +173,15 @@ function ensureZipHasIndex(zipPath) {
172
173
  return { abs, name: path.basename(abs) };
173
174
  }
174
175
 
176
+ async function parseResponseBody(res) {
177
+ const text = await res.text();
178
+ try {
179
+ return { text, json: JSON.parse(text) };
180
+ } catch {
181
+ return { text, json: null };
182
+ }
183
+ }
184
+
175
185
  async function runPush(args) {
176
186
  const config = loadConfig();
177
187
  const host = args.host || config.host || DEFAULT_HOST;
@@ -179,6 +189,13 @@ async function runPush(args) {
179
189
  const dist = args.dist || config.dist || DEFAULT_DIST;
180
190
  const zipPath = args.zip;
181
191
  let token = args.token;
192
+ const requestedVersion = args.version ? Number(args.version) : null;
193
+ if (args.version) {
194
+ assert(
195
+ Number.isFinite(requestedVersion) && requestedVersion > 0,
196
+ "--version must be a positive number",
197
+ );
198
+ }
182
199
 
183
200
  assert(workspaceId, "workspaceId is required (config or --workspaceId)");
184
201
  const absDist = zipPath ? null : ensureDist(dist);
@@ -205,40 +222,91 @@ async function runPush(args) {
205
222
  const blob = new BlobCtor([zipBuffer], { type: "application/zip" });
206
223
  form.append("app", blob, zipName);
207
224
 
208
- const url = new URL("/api/upload-app-version", host);
209
- url.searchParams.set("workspaceId", workspaceId);
210
- if (args.version) url.searchParams.set("version", args.version);
211
-
212
- const res = await undiciFetch(url.toString(), {
225
+ const prepareUrl = new URL("/api/app-upload/prepare", host);
226
+ const prepareRes = await undiciFetch(prepareUrl.toString(), {
213
227
  method: "POST",
214
228
  headers: {
215
229
  Authorization: `Bearer ${token}`,
230
+ "Content-Type": "application/json",
216
231
  },
217
- body: form,
232
+ body: JSON.stringify({
233
+ workspaceId,
234
+ ...(Number.isFinite(requestedVersion) && requestedVersion > 0
235
+ ? { version: requestedVersion }
236
+ : {}),
237
+ }),
218
238
  });
219
-
220
- const text = await res.text();
221
- if (!res.ok) {
222
- console.error(`Upload failed (${res.status}): ${text}`);
239
+ const prepareBody = await parseResponseBody(prepareRes);
240
+ if (!prepareRes.ok) {
241
+ if (prepareRes.status === 404) {
242
+ console.error(
243
+ "Prepare endpoint not found on backend host. Deploy backend with /api/app-upload/prepare support.",
244
+ );
245
+ }
246
+ console.error(
247
+ `Prepare failed (${prepareRes.status}): ${prepareBody.text || "Unknown error"}`,
248
+ );
223
249
  process.exit(1);
224
250
  }
225
- let payload = null;
226
- try {
227
- payload = JSON.parse(text);
228
- } catch {
229
- console.log(text);
230
- return;
251
+
252
+ const uploadToken = prepareBody.json?.uploadToken;
253
+ const version = Number(prepareBody.json?.version);
254
+ const uploadHost =
255
+ String(prepareBody.json?.uploadHost || "").trim() || DEFAULT_UPLOAD_HOST;
256
+
257
+ assert(uploadToken, "Prepare response is missing uploadToken");
258
+ assert(
259
+ Number.isFinite(version) && version > 0,
260
+ "Prepare response has invalid version",
261
+ );
262
+
263
+ const uploadUrl = new URL("/api/upload-app", uploadHost);
264
+ uploadUrl.searchParams.set("workspaceId", workspaceId);
265
+ uploadUrl.searchParams.set("version", String(version));
266
+
267
+ const uploadRes = await undiciFetch(uploadUrl.toString(), {
268
+ method: "POST",
269
+ headers: {
270
+ Authorization: `Bearer ${uploadToken}`,
271
+ },
272
+ body: form,
273
+ });
274
+ const uploadBody = await parseResponseBody(uploadRes);
275
+ if (!uploadRes.ok) {
276
+ console.error(
277
+ `Upload failed (${uploadRes.status}): ${uploadBody.text || "Unknown error"}`,
278
+ );
279
+ process.exit(1);
231
280
  }
232
281
 
233
- if (payload?.ok) {
234
- console.log("Upload complete ✅");
235
- console.log(`Workspace: ${payload.workspaceId}`);
236
- console.log(`Version: ${payload.version}`);
237
- console.log(`Files: ${payload.filesWritten}`);
238
- return;
282
+ const finalizeUrl = new URL("/api/app-upload/finalize", host);
283
+ const finalizeRes = await undiciFetch(finalizeUrl.toString(), {
284
+ method: "POST",
285
+ headers: {
286
+ Authorization: `Bearer ${token}`,
287
+ "Content-Type": "application/json",
288
+ },
289
+ body: JSON.stringify({
290
+ workspaceId,
291
+ version,
292
+ zipName,
293
+ zipSize: zipBuffer.length,
294
+ }),
295
+ });
296
+ const finalizeBody = await parseResponseBody(finalizeRes);
297
+ if (!finalizeRes.ok) {
298
+ console.error(
299
+ `Finalize failed (${finalizeRes.status}): ${finalizeBody.text || "Unknown error"}`,
300
+ );
301
+ process.exit(1);
239
302
  }
240
303
 
241
- console.log(text);
304
+ console.log("Upload complete ✅");
305
+ console.log(`Workspace: ${workspaceId}`);
306
+ console.log(`Version: ${version}`);
307
+ if (uploadBody.json?.filesWritten != null) {
308
+ console.log(`Files: ${uploadBody.json.filesWritten}`);
309
+ }
242
310
  }
243
311
 
244
312
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promte/app-upload",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "CLI to upload Promte app bundles",
5
5
  "type": "module",
6
6
  "bin": {