@promte/app-upload 0.1.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/README.md +23 -0
- package/bin/promte-app-upload.js +199 -0
- package/package.json +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# promte-app-upload
|
|
2
|
+
|
|
3
|
+
CLI for uploading Promte app bundles.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
```
|
|
7
|
+
npx promte-app-upload config
|
|
8
|
+
npx promte-app-upload push
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
```
|
|
13
|
+
promte-app-upload config
|
|
14
|
+
- interactive setup for host/workspaceId/dist (stored in ~/.promte-app-upload.json)
|
|
15
|
+
|
|
16
|
+
promte-app-upload push [--host ...] [--workspaceId ...] [--dist ...] [--serviceKey ...]
|
|
17
|
+
- uploads ./dist by default
|
|
18
|
+
- host defaults to https://app.promte.dk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Notes:
|
|
22
|
+
- Service key is never stored in the config file.
|
|
23
|
+
- If no service key is provided, the CLI will prompt for it.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import AdmZip from "adm-zip";
|
|
6
|
+
import { FormData, File } from "undici";
|
|
7
|
+
import readline from "node:readline/promises";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_HOST = "https://app.promte.dk";
|
|
10
|
+
const DEFAULT_DIST = "./dist";
|
|
11
|
+
const CONFIG_PATH = path.join(os.homedir(), ".promte-app-upload.json");
|
|
12
|
+
|
|
13
|
+
function usage() {
|
|
14
|
+
console.log(
|
|
15
|
+
[
|
|
16
|
+
"Usage:",
|
|
17
|
+
" promte-app-upload config",
|
|
18
|
+
" promte-app-upload push [--host <url>] [--workspaceId <id>] [--dist <path>] [--serviceKey <key>]",
|
|
19
|
+
"",
|
|
20
|
+
"Defaults:",
|
|
21
|
+
` host: ${DEFAULT_HOST}`,
|
|
22
|
+
` dist: ${DEFAULT_DIST}`,
|
|
23
|
+
` config: ${CONFIG_PATH}`,
|
|
24
|
+
].join("\n"),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseArgs(argv) {
|
|
29
|
+
const args = { _: [] };
|
|
30
|
+
for (let i = 2; i < argv.length; i += 1) {
|
|
31
|
+
const arg = argv[i];
|
|
32
|
+
if (arg.startsWith("--")) {
|
|
33
|
+
const key = arg.slice(2);
|
|
34
|
+
args[key] = argv[i + 1];
|
|
35
|
+
i += 1;
|
|
36
|
+
} else {
|
|
37
|
+
args._.push(arg);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return args;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function loadConfig() {
|
|
44
|
+
if (!fs.existsSync(CONFIG_PATH)) return {};
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8")) || {};
|
|
47
|
+
} catch {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function saveConfig(next) {
|
|
53
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(next, null, 2));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function prompt(question, defaultValue = "") {
|
|
57
|
+
const rl = readline.createInterface({
|
|
58
|
+
input: process.stdin,
|
|
59
|
+
output: process.stdout,
|
|
60
|
+
});
|
|
61
|
+
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
62
|
+
const answer = await rl.question(`${question}${suffix}: `);
|
|
63
|
+
rl.close();
|
|
64
|
+
return answer.trim() || defaultValue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function promptSecret(question) {
|
|
68
|
+
process.stdout.write(`${question}: `);
|
|
69
|
+
process.stdin.setRawMode(true);
|
|
70
|
+
process.stdin.resume();
|
|
71
|
+
process.stdin.setEncoding("utf8");
|
|
72
|
+
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
let value = "";
|
|
75
|
+
function onData(char) {
|
|
76
|
+
if (char === "\r" || char === "\n") {
|
|
77
|
+
process.stdin.setRawMode(false);
|
|
78
|
+
process.stdin.pause();
|
|
79
|
+
process.stdin.removeListener("data", onData);
|
|
80
|
+
process.stdout.write("\n");
|
|
81
|
+
resolve(value);
|
|
82
|
+
} else if (char === "\u0003") {
|
|
83
|
+
process.exit(1);
|
|
84
|
+
} else if (char === "\u007f") {
|
|
85
|
+
value = value.slice(0, -1);
|
|
86
|
+
} else {
|
|
87
|
+
value += char;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
process.stdin.on("data", onData);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function assert(condition, message) {
|
|
95
|
+
if (!condition) {
|
|
96
|
+
console.error(message);
|
|
97
|
+
usage();
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function runConfig() {
|
|
103
|
+
const current = loadConfig();
|
|
104
|
+
const host = await prompt("Host", current.host || DEFAULT_HOST);
|
|
105
|
+
const workspaceId = await prompt("Workspace ID", current.workspaceId || "");
|
|
106
|
+
const dist = await prompt("Dist path", current.dist || DEFAULT_DIST);
|
|
107
|
+
assert(workspaceId, "workspaceId is required");
|
|
108
|
+
saveConfig({ host, workspaceId, dist });
|
|
109
|
+
console.log(`Saved config to ${CONFIG_PATH}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function ensureDist(distPath) {
|
|
113
|
+
const abs = path.resolve(process.cwd(), distPath);
|
|
114
|
+
assert(fs.existsSync(abs), `Dist folder not found: ${abs}`);
|
|
115
|
+
assert(
|
|
116
|
+
fs.statSync(abs).isDirectory(),
|
|
117
|
+
`Dist path is not a directory: ${abs}`,
|
|
118
|
+
);
|
|
119
|
+
const indexPath = path.join(abs, "index.html");
|
|
120
|
+
assert(
|
|
121
|
+
fs.existsSync(indexPath),
|
|
122
|
+
`index.html not found in dist folder: ${indexPath}`,
|
|
123
|
+
);
|
|
124
|
+
return abs;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function runPush(args) {
|
|
128
|
+
const config = loadConfig();
|
|
129
|
+
const host = args.host || config.host || DEFAULT_HOST;
|
|
130
|
+
const workspaceId = args.workspaceId || config.workspaceId;
|
|
131
|
+
const dist = args.dist || config.dist || DEFAULT_DIST;
|
|
132
|
+
let serviceKey = args.serviceKey;
|
|
133
|
+
|
|
134
|
+
assert(workspaceId, "workspaceId is required (config or --workspaceId)");
|
|
135
|
+
const absDist = ensureDist(dist);
|
|
136
|
+
|
|
137
|
+
if (!serviceKey) {
|
|
138
|
+
serviceKey = await promptSecret("Service key");
|
|
139
|
+
}
|
|
140
|
+
assert(serviceKey, "serviceKey is required");
|
|
141
|
+
|
|
142
|
+
const zip = new AdmZip();
|
|
143
|
+
zip.addLocalFolder(absDist, "");
|
|
144
|
+
const zipBuffer = zip.toBuffer();
|
|
145
|
+
|
|
146
|
+
const form = new FormData();
|
|
147
|
+
const file = new File([zipBuffer], "dist.zip", {
|
|
148
|
+
type: "application/zip",
|
|
149
|
+
});
|
|
150
|
+
form.append("app", file);
|
|
151
|
+
|
|
152
|
+
const url = new URL("/api/upload-app-version", host);
|
|
153
|
+
url.searchParams.set("workspaceId", workspaceId);
|
|
154
|
+
if (args.version) url.searchParams.set("version", args.version);
|
|
155
|
+
|
|
156
|
+
const res = await fetch(url.toString(), {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: {
|
|
159
|
+
"service-key": serviceKey,
|
|
160
|
+
},
|
|
161
|
+
body: form,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const text = await res.text();
|
|
165
|
+
if (!res.ok) {
|
|
166
|
+
console.error(`Upload failed (${res.status}): ${text}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
console.log(text);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function main() {
|
|
173
|
+
const args = parseArgs(process.argv);
|
|
174
|
+
const cmd = args._[0];
|
|
175
|
+
|
|
176
|
+
if (!cmd || cmd === "--help" || cmd === "-h") {
|
|
177
|
+
usage();
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (cmd === "config") {
|
|
182
|
+
await runConfig();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (cmd === "push") {
|
|
187
|
+
await runPush(args);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.error(`Unknown command: ${cmd}`);
|
|
192
|
+
usage();
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
main().catch((err) => {
|
|
197
|
+
console.error(err?.message || err);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@promte/app-upload",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to upload Promte app bundles",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"promte-app-upload": "bin/promte-app-upload.js"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18.0.0"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"adm-zip": "^0.5.16",
|
|
17
|
+
"undici": "^7.8.0"
|
|
18
|
+
}
|
|
19
|
+
}
|