@promte/app-upload 0.1.10 → 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 +6 -5
- package/bin/promte-app-upload.js +99 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,14 +11,15 @@ 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
|
-
npx @promte/app-upload push [--host ...] [--workspaceId ...] [--dist ...] [--zip ...] [--
|
|
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
|
|
|
22
23
|
Notes:
|
|
23
|
-
-
|
|
24
|
-
- If no
|
|
24
|
+
- Tokens are never stored in the config file.
|
|
25
|
+
- If no token is provided, the CLI will prompt for it.
|
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
|
|
|
@@ -18,10 +18,11 @@ function usage() {
|
|
|
18
18
|
[
|
|
19
19
|
"Usage:",
|
|
20
20
|
" promte-app-upload config",
|
|
21
|
-
" promte-app-upload push [--host <url>] [--workspaceId <id>] [--dist <path>] [--zip <file>] [--
|
|
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,21 +173,37 @@ 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;
|
|
178
188
|
const workspaceId = args.workspaceId || config.workspaceId;
|
|
179
189
|
const dist = args.dist || config.dist || DEFAULT_DIST;
|
|
180
190
|
const zipPath = args.zip;
|
|
181
|
-
let
|
|
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);
|
|
185
202
|
|
|
186
|
-
if (!
|
|
187
|
-
|
|
203
|
+
if (!token) {
|
|
204
|
+
token = await promptSecret("Workspace token");
|
|
188
205
|
}
|
|
189
|
-
assert(
|
|
206
|
+
assert(token, "token is required");
|
|
190
207
|
|
|
191
208
|
let zipBuffer;
|
|
192
209
|
let zipName = "dist.zip";
|
|
@@ -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
|
-
|
|
225
|
+
const prepareUrl = new URL("/api/app-upload/prepare", host);
|
|
226
|
+
const prepareRes = await undiciFetch(prepareUrl.toString(), {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers: {
|
|
229
|
+
Authorization: `Bearer ${token}`,
|
|
230
|
+
"Content-Type": "application/json",
|
|
231
|
+
},
|
|
232
|
+
body: JSON.stringify({
|
|
233
|
+
workspaceId,
|
|
234
|
+
...(Number.isFinite(requestedVersion) && requestedVersion > 0
|
|
235
|
+
? { version: requestedVersion }
|
|
236
|
+
: {}),
|
|
237
|
+
}),
|
|
238
|
+
});
|
|
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
|
+
);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
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
|
+
);
|
|
211
262
|
|
|
212
|
-
const
|
|
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(), {
|
|
213
268
|
method: "POST",
|
|
214
269
|
headers: {
|
|
215
|
-
|
|
270
|
+
Authorization: `Bearer ${uploadToken}`,
|
|
216
271
|
},
|
|
217
272
|
body: form,
|
|
218
273
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
274
|
+
const uploadBody = await parseResponseBody(uploadRes);
|
|
275
|
+
if (!uploadRes.ok) {
|
|
276
|
+
console.error(
|
|
277
|
+
`Upload failed (${uploadRes.status}): ${uploadBody.text || "Unknown error"}`,
|
|
278
|
+
);
|
|
223
279
|
process.exit(1);
|
|
224
280
|
}
|
|
225
|
-
let payload = null;
|
|
226
|
-
try {
|
|
227
|
-
payload = JSON.parse(text);
|
|
228
|
-
} catch {
|
|
229
|
-
console.log(text);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
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() {
|