@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 +3 -2
- package/bin/promte-app-upload.js +94 -26
- package/package.json +1 -1
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
|
|
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
|
|
package/bin/promte-app-upload.js
CHANGED
|
@@ -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("
|
|
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
|
|
209
|
-
|
|
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:
|
|
232
|
+
body: JSON.stringify({
|
|
233
|
+
workspaceId,
|
|
234
|
+
...(Number.isFinite(requestedVersion) && requestedVersion > 0
|
|
235
|
+
? { version: requestedVersion }
|
|
236
|
+
: {}),
|
|
237
|
+
}),
|
|
218
238
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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(
|
|
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() {
|