pocketbomb-detonator 0.2.0 → 0.3.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 +8 -3
- package/dist/cli/index.cjs +115 -16
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +12 -2
- package/dist/cli/index.d.ts +12 -2
- package/dist/cli/index.js +114 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/manifest.cjs +2 -36
- package/dist/cli/manifest.cjs.map +1 -1
- package/dist/cli/manifest.d.cts +2 -14
- package/dist/cli/manifest.d.ts +2 -14
- package/dist/cli/manifest.js +1 -35
- package/dist/cli/manifest.js.map +1 -1
- package/dist/scopes.cjs +39 -0
- package/dist/scopes.cjs.map +1 -0
- package/dist/scopes.d.cts +15 -0
- package/dist/scopes.d.ts +15 -0
- package/dist/scopes.js +39 -0
- package/dist/scopes.js.map +1 -0
- package/dist/server/detonator.cjs +19 -0
- package/dist/server/detonator.cjs.map +1 -1
- package/dist/server/detonator.d.cts +7 -0
- package/dist/server/detonator.d.ts +7 -0
- package/dist/server/detonator.js +19 -1
- package/dist/server/detonator.js.map +1 -1
- package/dist/server/index.cjs +3 -0
- package/dist/server/index.d.cts +3 -2
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.js +3 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -30,6 +30,8 @@ export const { authenticate, unauthenticated } = app;
|
|
|
30
30
|
token, reload with `id_token`, stripped with `history.replaceState`); outside of an iframe a page links back to the dashboard.
|
|
31
31
|
- `addDocumentResponseHeaders(request, headers)`: `Content-Security-Policy: frame-ancestors {dashboard origin}` and
|
|
32
32
|
`Referrer-Policy: no-referrer`; `handleEmbeddedRequest(request, {paths})`: the launches + the token checks before the router.
|
|
33
|
+
- `withoutIdToken(request)`: after `handleEmbeddedRequest()` and before an SSR router: moves the `id_token` of the bounce to the
|
|
34
|
+
`Authorization` header, so that the token doesn't end up in the rendered HTML (e.g. the form actions).
|
|
33
35
|
- `authenticate.public.appProxy(request)`: a request forwarded by the app proxy -> verifies the signature over the
|
|
34
36
|
raw query (Shopify algorithm with the webhook secret), `body_sha256` and `timestamp` (±90s) ->
|
|
35
37
|
`{installation, customerId, pathPrefix, admin}`.
|
|
@@ -44,7 +46,10 @@ path; any other path falls through to the `GET /{path...}` static route if there
|
|
|
44
46
|
without being more specific or more general (e.g. `/{a}/foo/bar`) makes the server panic at startup (intentionally loud).
|
|
45
47
|
|
|
46
48
|
Entries: `.` (verifyApplicationJWT, verifyWebhookSignature, createAdminClient, exchangeLaunchToken),
|
|
47
|
-
`./server
|
|
49
|
+
`./server` (also `scopesToPermissions`), `./react` (AppProvider/useApp), `./adapters/node`, `./session-storage` (Memory, `node:sqlite`).
|
|
48
50
|
|
|
49
|
-
CLI: `detonator dev` (fuse -> import `pocketbomb.app.toml` -> run the app -> install with the manifest scopes
|
|
50
|
-
`
|
|
51
|
+
CLI: `detonator dev` (fuse -> import `pocketbomb.app.toml` -> run the app -> install with the manifest scopes; `--data-dir` and
|
|
52
|
+
`--fuse-port` for fuse, or an existing server with `--url` + `--token` / `$POCKETBOMB_URL` + `$POCKETBOMB_TOKEN`: dev versions
|
|
53
|
+
derived from the manifest content, an existing installation is reused with new credentials, and the server keeps running),
|
|
54
|
+
`detonator deploy --url --token`, `detonator install --url --token [--config] [--dotenv]` (the manifest scopes; idempotent;
|
|
55
|
+
without an `install_url` the credentials go to the env file, never the output), `detonator info`.
|
package/dist/cli/index.cjs
CHANGED
|
@@ -1,24 +1,36 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_scopes = require("../scopes.cjs");
|
|
3
4
|
const require_manifest = require("./manifest.cjs");
|
|
5
|
+
let pocketbomb_primer = require("pocketbomb-primer");
|
|
4
6
|
let node_child_process = require("node:child_process");
|
|
7
|
+
let node_crypto = require("node:crypto");
|
|
5
8
|
let node_fs = require("node:fs");
|
|
6
9
|
node_fs = require_runtime.__toESM(node_fs, 1);
|
|
7
10
|
let node_path = require("node:path");
|
|
8
11
|
node_path = require_runtime.__toESM(node_path, 1);
|
|
9
12
|
let node_util = require("node:util");
|
|
13
|
+
let pocketbomb_primer_internal = require("pocketbomb-primer/internal");
|
|
10
14
|
// src/cli/index.ts
|
|
11
15
|
const HELP = `detonator <command> [options]
|
|
12
16
|
|
|
13
17
|
Commands:
|
|
14
|
-
dev Start fuse, import pocketbomb.app.toml as a custom app,
|
|
18
|
+
dev Start fuse (unless --url/$POCKETBOMB_URL is set), import pocketbomb.app.toml as a custom app,
|
|
19
|
+
run the app and install it
|
|
15
20
|
--config <file> (default pocketbomb.app.toml)
|
|
21
|
+
--url <url> --token <staff access token>
|
|
22
|
+
use an existing server instead of fuse (or $POCKETBOMB_URL, $POCKETBOMB_TOKEN)
|
|
16
23
|
--seed <module> the fuse seed (e.g. the collections of the scopes)
|
|
24
|
+
--data-dir <dir> a persistent fuse data dir (default a temporary one)
|
|
25
|
+
--fuse-port <port> the fuse server port (default a free port)
|
|
17
26
|
--entry <file> the app server entry run with Node.js (default server.ts)
|
|
18
27
|
--port <port> the app port (default a free port)
|
|
19
28
|
--dotenv <file> the env file to update (default .env)
|
|
20
29
|
deploy Deploy (import) the manifest to a PocketBomb instance
|
|
21
30
|
--url <url> --token <staff access token> [--config <file>] [--app-url <url>]
|
|
31
|
+
install Install the deployed application with the scopes of the manifest (idempotent)
|
|
32
|
+
--url <url> --token <staff access token> [--config <file>]
|
|
33
|
+
--dotenv <file> where the credentials go without an install_url (default .env)
|
|
22
34
|
info Print the manifest summary and the environment
|
|
23
35
|
`;
|
|
24
36
|
const defaultIo = {
|
|
@@ -43,6 +55,22 @@ async function waitForApp(url, child, timeout = 6e4) {
|
|
|
43
55
|
}
|
|
44
56
|
throw new Error("The app didn't start in " + timeout + "ms.");
|
|
45
57
|
}
|
|
58
|
+
function staffClient(url, token) {
|
|
59
|
+
const client = new pocketbomb_primer_internal.DashboardClient(url, { authStore: new pocketbomb_primer.AuthStore() });
|
|
60
|
+
client.authStore.save(token.replace(/^Bearer /, ""), null);
|
|
61
|
+
return client;
|
|
62
|
+
}
|
|
63
|
+
function devVersion(manifest) {
|
|
64
|
+
const digest = (0, node_crypto.createHash)("sha256").update(JSON.stringify(manifest)).digest("hex").substring(0, 12);
|
|
65
|
+
return (manifest.version || "0.0.0").split("-")[0] + "-dev." + digest;
|
|
66
|
+
}
|
|
67
|
+
function isLoopback(url) {
|
|
68
|
+
return [
|
|
69
|
+
"localhost",
|
|
70
|
+
"127.0.0.1",
|
|
71
|
+
"[::1]"
|
|
72
|
+
].includes(new URL(url).hostname);
|
|
73
|
+
}
|
|
46
74
|
/**
|
|
47
75
|
* `detonator dev`: fuse -> import the manifest (with the dev app URL) as a custom application ->
|
|
48
76
|
* run the app -> install it with the manifest scopes (the credentials are delivered to the running
|
|
@@ -55,14 +83,20 @@ async function dev(options = {}) {
|
|
|
55
83
|
const envFile = node_path.default.resolve(cwd, options.envFile || ".env");
|
|
56
84
|
const entry = node_path.default.resolve(cwd, options.entry || "server.ts");
|
|
57
85
|
const { freePort, signInStaff, startFuse } = await import("pocketbomb-fuse/node");
|
|
86
|
+
const serverUrl = (options.url || process.env.POCKETBOMB_URL || "").replace(/\/+$/, "");
|
|
87
|
+
const token = options.token || process.env.POCKETBOMB_TOKEN;
|
|
88
|
+
if (serverUrl && !token) throw new Error("detonator dev: --token (or $POCKETBOMB_TOKEN) is required with an existing server.");
|
|
58
89
|
const port = options.port || await freePort("localhost");
|
|
59
90
|
const appUrl = "http://localhost:" + port;
|
|
60
91
|
const manifest = require_manifest.resolveManifest(require_manifest.loadManifest(manifestFile), appUrl);
|
|
61
|
-
const fuse = await startFuse({
|
|
92
|
+
const fuse = serverUrl ? null : await startFuse({
|
|
62
93
|
seed: options.seed ? node_path.default.resolve(cwd, options.seed) : void 0,
|
|
94
|
+
dataDir: options.dataDir ? node_path.default.resolve(cwd, options.dataDir) : void 0,
|
|
95
|
+
port: options.fusePort,
|
|
63
96
|
log: (line) => io.error("[fuse] " + line)
|
|
64
97
|
});
|
|
65
|
-
|
|
98
|
+
const pocketbombUrl = fuse ? fuse.url : serverUrl;
|
|
99
|
+
io.error(fuse ? "[detonator] PocketBomb (fuse) running on " + fuse.url : "[detonator] using the PocketBomb server " + serverUrl);
|
|
66
100
|
let app = null;
|
|
67
101
|
const stop = async () => {
|
|
68
102
|
if (app && app.exitCode === null) {
|
|
@@ -70,22 +104,24 @@ async function dev(options = {}) {
|
|
|
70
104
|
app.kill("SIGTERM");
|
|
71
105
|
await exited;
|
|
72
106
|
}
|
|
73
|
-
await fuse
|
|
107
|
+
await fuse?.stop();
|
|
74
108
|
};
|
|
75
109
|
try {
|
|
76
|
-
const owner = await signInStaff(fuse.url, fuse.owner.email, fuse.owner.password);
|
|
77
|
-
await owner.settings.update({
|
|
110
|
+
const owner = fuse ? await signInStaff(fuse.url, fuse.owner.email, fuse.owner.password) : staffClient(serverUrl, token);
|
|
111
|
+
if (fuse) await owner.settings.update({
|
|
78
112
|
meta: { appURL: fuse.url },
|
|
79
113
|
webhooks: { allowPrivateTargets: true }
|
|
80
114
|
});
|
|
115
|
+
else if (isLoopback(serverUrl)) await owner.settings.update({ webhooks: { allowPrivateTargets: true } });
|
|
116
|
+
if (!fuse) manifest.version = devVersion(manifest);
|
|
81
117
|
const imported = await owner.applications.importManifest(manifest, "json");
|
|
82
118
|
io.error("[detonator] imported " + manifest.handle + " " + imported.version.version);
|
|
83
119
|
const collections = await owner.collections.getFullList({ fields: "id,name" });
|
|
84
|
-
const permissions =
|
|
85
|
-
const issuer =
|
|
120
|
+
const permissions = require_scopes.scopesToPermissions(manifest.access?.scopes || [], collections);
|
|
121
|
+
const issuer = pocketbombUrl + "/oauth2/_applicationInstallations";
|
|
86
122
|
const baseEnv = {
|
|
87
|
-
...fuse
|
|
88
|
-
POCKETBOMB_URL:
|
|
123
|
+
...fuse?.env,
|
|
124
|
+
POCKETBOMB_URL: pocketbombUrl,
|
|
89
125
|
POCKETBOMB_ISSUER: issuer,
|
|
90
126
|
POCKETBOMB_APP_HANDLE: manifest.handle,
|
|
91
127
|
APP_URL: appUrl,
|
|
@@ -104,17 +140,26 @@ async function dev(options = {}) {
|
|
|
104
140
|
await waitForApp(appUrl + "/", app);
|
|
105
141
|
io.error("[detonator] app running on " + appUrl);
|
|
106
142
|
};
|
|
143
|
+
const existing = fuse ? null : (await owner.applications.getList()).items.find((a) => a.id === imported.application.id)?.installation;
|
|
144
|
+
const installApp = async () => {
|
|
145
|
+
if (!existing) return owner.applications.install(imported.application.id, permissions);
|
|
146
|
+
await owner.applications.update(existing.id, {
|
|
147
|
+
permissions,
|
|
148
|
+
versionRef: imported.version.id
|
|
149
|
+
});
|
|
150
|
+
return owner.applications.rotateSecret(existing.id);
|
|
151
|
+
};
|
|
107
152
|
let installed;
|
|
108
153
|
let envValues;
|
|
109
154
|
if (manifest.install_url) {
|
|
110
155
|
await startApp(baseEnv);
|
|
111
|
-
installed = await
|
|
156
|
+
installed = await installApp();
|
|
112
157
|
envValues = {
|
|
113
158
|
...baseEnv,
|
|
114
159
|
POCKETBOMB_INSTALLATION_ID: installed.installation.id
|
|
115
160
|
};
|
|
116
161
|
} else {
|
|
117
|
-
installed = await
|
|
162
|
+
installed = await installApp();
|
|
118
163
|
envValues = {
|
|
119
164
|
...baseEnv,
|
|
120
165
|
POCKETBOMB_INSTALLATION_ID: installed.installation.id,
|
|
@@ -127,14 +172,14 @@ async function dev(options = {}) {
|
|
|
127
172
|
require_manifest.updateEnvFile(envFile, envValues);
|
|
128
173
|
io.error("[detonator] installed " + manifest.handle + " (installation " + installed.installation.id + (installed.notified ? ", credentials delivered to the install_url" : ", credentials in " + node_path.default.basename(envFile)) + ")");
|
|
129
174
|
const state = {
|
|
130
|
-
pocketbombUrl
|
|
175
|
+
pocketbombUrl,
|
|
131
176
|
appUrl,
|
|
132
177
|
issuer,
|
|
133
178
|
installationId: installed.installation.id,
|
|
134
179
|
applicationId: imported.application.id,
|
|
135
180
|
notified: installed.notified,
|
|
136
181
|
app,
|
|
137
|
-
owner: fuse
|
|
182
|
+
owner: fuse?.owner ?? null,
|
|
138
183
|
stop
|
|
139
184
|
};
|
|
140
185
|
await options.onReady?.(state);
|
|
@@ -149,7 +194,11 @@ async function runDev(argv, io) {
|
|
|
149
194
|
args: argv,
|
|
150
195
|
options: {
|
|
151
196
|
config: { type: "string" },
|
|
197
|
+
url: { type: "string" },
|
|
198
|
+
token: { type: "string" },
|
|
152
199
|
seed: { type: "string" },
|
|
200
|
+
"data-dir": { type: "string" },
|
|
201
|
+
"fuse-port": { type: "string" },
|
|
153
202
|
entry: { type: "string" },
|
|
154
203
|
port: { type: "string" },
|
|
155
204
|
dotenv: { type: "string" }
|
|
@@ -157,13 +206,17 @@ async function runDev(argv, io) {
|
|
|
157
206
|
});
|
|
158
207
|
const state = await dev({
|
|
159
208
|
config: values.config,
|
|
209
|
+
url: values.url,
|
|
210
|
+
token: values.token,
|
|
160
211
|
seed: values.seed,
|
|
212
|
+
dataDir: values["data-dir"],
|
|
213
|
+
fusePort: values["fuse-port"] ? parseInt(values["fuse-port"], 10) : void 0,
|
|
161
214
|
entry: values.entry,
|
|
162
215
|
port: values.port ? parseInt(values.port, 10) : void 0,
|
|
163
216
|
envFile: values.dotenv,
|
|
164
217
|
io
|
|
165
218
|
});
|
|
166
|
-
io.log("Dashboard: " + state.pocketbombUrl + "/_/ (" + state.owner.email + " / " + state.owner.password + ")");
|
|
219
|
+
io.log("Dashboard: " + state.pocketbombUrl + "/_/" + (state.owner ? " (" + state.owner.email + " / " + state.owner.password + ")" : ""));
|
|
167
220
|
io.log("App: " + state.appUrl);
|
|
168
221
|
const code = await new Promise((resolve) => {
|
|
169
222
|
state.app.once("exit", (c) => resolve(c ?? 0));
|
|
@@ -199,6 +252,51 @@ async function runDeploy(argv, io) {
|
|
|
199
252
|
io.log((result.created ? "created " : result.unchanged ? "unchanged " : "new version of ") + manifest.handle + " " + (result.version?.version || "") + " (application " + result.application?.id + ")");
|
|
200
253
|
return 0;
|
|
201
254
|
}
|
|
255
|
+
async function runInstall(argv, io) {
|
|
256
|
+
const { values } = (0, node_util.parseArgs)({
|
|
257
|
+
args: argv,
|
|
258
|
+
options: {
|
|
259
|
+
url: { type: "string" },
|
|
260
|
+
token: { type: "string" },
|
|
261
|
+
config: { type: "string" },
|
|
262
|
+
dotenv: { type: "string" }
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
const url = values.url || process.env.POCKETBOMB_URL;
|
|
266
|
+
const token = values.token || process.env.POCKETBOMB_TOKEN;
|
|
267
|
+
if (!url || !token) {
|
|
268
|
+
io.error("detonator install: --url and --token are required (or $POCKETBOMB_URL / $POCKETBOMB_TOKEN)");
|
|
269
|
+
return 2;
|
|
270
|
+
}
|
|
271
|
+
const manifest = require_manifest.loadManifest(node_path.default.resolve(values.config || "pocketbomb.app.toml"));
|
|
272
|
+
const owner = staffClient(url.replace(/\/+$/, ""), token);
|
|
273
|
+
const application = (await owner.applications.getList()).items.find((a) => a.handle === manifest.handle);
|
|
274
|
+
if (!application) {
|
|
275
|
+
io.error("detonator install: " + manifest.handle + " isn't deployed (run detonator deploy).");
|
|
276
|
+
return 1;
|
|
277
|
+
}
|
|
278
|
+
if (application.installation) {
|
|
279
|
+
io.log(manifest.handle + " is already installed (installation " + application.installation.id + ")");
|
|
280
|
+
return 0;
|
|
281
|
+
}
|
|
282
|
+
const collections = await owner.collections.getFullList({ fields: "id,name" });
|
|
283
|
+
const permissions = require_scopes.scopesToPermissions(manifest.access?.scopes || [], collections);
|
|
284
|
+
const installed = await owner.applications.install(application.id, permissions);
|
|
285
|
+
if (installed.notified) {
|
|
286
|
+
io.log("installed " + manifest.handle + " (installation " + installed.installation.id + ", credentials delivered to the install_url)");
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
const envFile = node_path.default.resolve(values.dotenv || ".env");
|
|
290
|
+
require_manifest.updateEnvFile(envFile, {
|
|
291
|
+
POCKETBOMB_URL: url.replace(/\/+$/, ""),
|
|
292
|
+
POCKETBOMB_INSTALLATION_ID: installed.installation.id,
|
|
293
|
+
POCKETBOMB_CLIENT_ID: installed.clientId,
|
|
294
|
+
POCKETBOMB_CLIENT_SECRET: installed.clientSecret,
|
|
295
|
+
POCKETBOMB_WEBHOOK_SECRET: installed.webhookSecret
|
|
296
|
+
});
|
|
297
|
+
io.log("installed " + manifest.handle + " (installation " + installed.installation.id + ", credentials in " + node_path.default.basename(envFile) + ")");
|
|
298
|
+
return 0;
|
|
299
|
+
}
|
|
202
300
|
function runInfo(argv, io) {
|
|
203
301
|
const { values } = (0, node_util.parseArgs)({
|
|
204
302
|
args: argv,
|
|
@@ -232,6 +330,7 @@ async function run(argv, io = defaultIo) {
|
|
|
232
330
|
switch (command) {
|
|
233
331
|
case "dev": return runDev(rest, io);
|
|
234
332
|
case "deploy": return runDeploy(rest, io);
|
|
333
|
+
case "install": return runInstall(rest, io);
|
|
235
334
|
case "info": return runInfo(rest, io);
|
|
236
335
|
case "--version":
|
|
237
336
|
case "-v":
|
|
@@ -254,7 +353,7 @@ exports.formatEnv = require_manifest.formatEnv;
|
|
|
254
353
|
exports.loadManifest = require_manifest.loadManifest;
|
|
255
354
|
exports.resolveManifest = require_manifest.resolveManifest;
|
|
256
355
|
exports.run = run;
|
|
257
|
-
exports.scopesToPermissions =
|
|
356
|
+
exports.scopesToPermissions = require_scopes.scopesToPermissions;
|
|
258
357
|
exports.updateEnvFile = require_manifest.updateEnvFile;
|
|
259
358
|
|
|
260
359
|
//# sourceMappingURL=index.cjs.map
|
package/dist/cli/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["fs","path","resolveManifest","loadManifest","scopesToPermissions","spawn","parseArgs","deployManifest"],"sources":["../../src/cli/index.ts"],"sourcesContent":["// The `detonator` CLI (Node.js only): dev, deploy, info.\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport {\n deployManifest,\n loadManifest,\n resolveManifest,\n scopesToPermissions,\n updateEnvFile,\n type AppManifest,\n} from \"./manifest\";\n\nexport * from \"./manifest\";\n\nconst HELP = `detonator <command> [options]\n\nCommands:\n dev Start fuse, import pocketbomb.app.toml as a custom app, run the app and install it\n --config <file> (default pocketbomb.app.toml)\n --seed <module> the fuse seed (e.g. the collections of the scopes)\n --entry <file> the app server entry run with Node.js (default server.ts)\n --port <port> the app port (default a free port)\n --dotenv <file> the env file to update (default .env)\n deploy Deploy (import) the manifest to a PocketBomb instance\n --url <url> --token <staff access token> [--config <file>] [--app-url <url>]\n info Print the manifest summary and the environment\n`;\n\ntype Io = { log: (line: string) => void; error: (line: string) => void };\n\nconst defaultIo: Io = {\n log: (line) => process.stdout.write(line + \"\\n\"),\n error: (line) => process.stderr.write(line + \"\\n\"),\n};\n\nfunction readPackageVersion(): string {\n try {\n return JSON.parse(fs.readFileSync(new URL(\"../../package.json\", import.meta.url), \"utf8\"))\n .version;\n } catch {\n return \"unknown\";\n }\n}\n\nasync function waitForApp(url: string, child: ChildProcess, timeout = 60000): Promise<void> {\n const deadline = Date.now() + timeout;\n while (Date.now() < deadline) {\n if (child.exitCode !== null) {\n throw new Error(\"The app exited early (code \" + child.exitCode + \").\");\n }\n try {\n const res = await fetch(url, { redirect: \"manual\" });\n if (res.status < 500) {\n return;\n }\n } catch {\n // not listening yet\n }\n // poll interval of the app startup readiness check\n await new Promise((r) => setTimeout(r, 150));\n }\n throw new Error(\"The app didn't start in \" + timeout + \"ms.\");\n}\n\nexport interface DevOptions {\n cwd?: string;\n config?: string;\n seed?: string;\n entry?: string;\n port?: number;\n envFile?: string;\n io?: Io;\n /** Called once the app is installed (the CLI waits for the app process otherwise). */\n onReady?: (state: DevState) => void | Promise<void>;\n}\n\nexport interface DevState {\n pocketbombUrl: string;\n appUrl: string;\n issuer: string;\n installationId: string;\n applicationId: string;\n notified: boolean;\n app: ChildProcess;\n owner: { email: string; password: string };\n stop(): Promise<void>;\n}\n\n/**\n * `detonator dev`: fuse -> import the manifest (with the dev app URL) as a custom application ->\n * run the app -> install it with the manifest scopes (the credentials are delivered to the running\n * app install_url, or written to the env file for a manifest without install_url).\n */\nexport async function dev(options: DevOptions = {}): Promise<DevState> {\n const io = options.io || defaultIo;\n const cwd = path.resolve(options.cwd || process.cwd());\n const manifestFile = path.resolve(cwd, options.config || \"pocketbomb.app.toml\");\n const envFile = path.resolve(cwd, options.envFile || \".env\");\n const entry = path.resolve(cwd, options.entry || \"server.ts\");\n\n const { freePort, signInStaff, startFuse } = await import(\"pocketbomb-fuse/node\");\n\n const port = options.port || (await freePort(\"localhost\"));\n const appUrl = \"http://localhost:\" + port;\n const manifest = resolveManifest(loadManifest(manifestFile), appUrl);\n\n const fuse = await startFuse({\n seed: options.seed ? path.resolve(cwd, options.seed) : undefined,\n log: (line) => io.error(\"[fuse] \" + line),\n });\n io.error(\"[detonator] PocketBomb (fuse) running on \" + fuse.url);\n\n let app: ChildProcess | null = null;\n const stop = async () => {\n if (app && app.exitCode === null) {\n const exited = new Promise((r) => app!.once(\"exit\", r));\n app.kill(\"SIGTERM\");\n await exited;\n }\n await fuse.stop();\n };\n\n try {\n const owner = await signInStaff(fuse.url, fuse.owner.email, fuse.owner.password);\n // the dev app (and its webhooks) are on a loopback host\n await owner.settings.update({\n meta: { appURL: fuse.url },\n webhooks: { allowPrivateTargets: true },\n });\n\n const imported = await owner.applications.importManifest(manifest as any, \"json\");\n io.error(\"[detonator] imported \" + manifest.handle + \" \" + imported.version.version);\n\n const collections = await owner.collections.getFullList({ fields: \"id,name\" });\n const permissions = scopesToPermissions(manifest.access?.scopes || [], collections);\n const issuer = fuse.url + \"/oauth2/_applicationInstallations\";\n\n const baseEnv: Record<string, string> = {\n ...fuse.env,\n POCKETBOMB_URL: fuse.url,\n POCKETBOMB_ISSUER: issuer,\n POCKETBOMB_APP_HANDLE: manifest.handle,\n APP_URL: appUrl,\n PORT: String(port),\n };\n\n const startApp = async (env: Record<string, string>) => {\n app = spawn(process.execPath, [entry], {\n cwd,\n stdio: \"inherit\",\n env: { ...process.env, ...env, NODE_ENV: process.env.NODE_ENV || \"development\" },\n });\n await waitForApp(appUrl + \"/\", app);\n io.error(\"[detonator] app running on \" + appUrl);\n };\n\n let installed;\n let envValues: Record<string, string>;\n if (manifest.install_url) {\n // the credentials are delivered to the running app (install notification)\n await startApp(baseEnv);\n installed = await owner.applications.install(imported.application.id, permissions as any);\n envValues = { ...baseEnv, POCKETBOMB_INSTALLATION_ID: installed.installation.id };\n } else {\n // a custom app without install_url gets the credentials once: env file + app env\n installed = await owner.applications.install(imported.application.id, permissions as any);\n envValues = {\n ...baseEnv,\n POCKETBOMB_INSTALLATION_ID: installed.installation.id,\n POCKETBOMB_CLIENT_ID: installed.clientId,\n POCKETBOMB_CLIENT_SECRET: installed.clientSecret,\n POCKETBOMB_WEBHOOK_SECRET: installed.webhookSecret,\n };\n await startApp(envValues);\n }\n\n updateEnvFile(envFile, envValues);\n io.error(\n \"[detonator] installed \" +\n manifest.handle +\n \" (installation \" +\n installed.installation.id +\n (installed.notified\n ? \", credentials delivered to the install_url\"\n : \", credentials in \" + path.basename(envFile)) +\n \")\",\n );\n\n const state: DevState = {\n pocketbombUrl: fuse.url,\n appUrl,\n issuer,\n installationId: installed.installation.id,\n applicationId: imported.application.id,\n notified: installed.notified,\n app: app!,\n owner: fuse.owner,\n stop,\n };\n\n await options.onReady?.(state);\n\n return state;\n } catch (err) {\n await stop();\n throw err;\n }\n}\n\nasync function runDev(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\" },\n seed: { type: \"string\" },\n entry: { type: \"string\" },\n port: { type: \"string\" },\n dotenv: { type: \"string\" },\n },\n });\n\n const state = await dev({\n config: values.config,\n seed: values.seed,\n entry: values.entry,\n port: values.port ? parseInt(values.port, 10) : undefined,\n envFile: values.dotenv,\n io,\n });\n io.log(\n \"Dashboard: \" +\n state.pocketbombUrl +\n \"/_/ (\" +\n state.owner.email +\n \" / \" +\n state.owner.password +\n \")\",\n );\n io.log(\"App: \" + state.appUrl);\n\n const code: number = await new Promise((resolve) => {\n state.app.once(\"exit\", (c) => resolve(c ?? 0));\n const shutdown = () => void state.stop().then(() => resolve(0));\n process.once(\"SIGINT\", shutdown);\n process.once(\"SIGTERM\", shutdown);\n });\n await state.stop();\n return code;\n}\n\nasync function runDeploy(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n url: { type: \"string\" },\n token: { type: \"string\" },\n config: { type: \"string\" },\n \"app-url\": { type: \"string\" },\n },\n });\n\n const url = values.url || process.env.POCKETBOMB_URL;\n const token = values.token || process.env.POCKETBOMB_TOKEN;\n if (!url || !token) {\n io.error(\n \"detonator deploy: --url and --token are required (or $POCKETBOMB_URL / $POCKETBOMB_TOKEN)\",\n );\n return 2;\n }\n\n const manifest = resolveManifest(\n loadManifest(path.resolve(values.config || \"pocketbomb.app.toml\")),\n values[\"app-url\"],\n );\n\n const result = await deployManifest({ url, token, manifest });\n io.log(\n (result.created ? \"created \" : result.unchanged ? \"unchanged \" : \"new version of \") +\n manifest.handle +\n \" \" +\n (result.version?.version || \"\") +\n \" (application \" +\n result.application?.id +\n \")\",\n );\n return 0;\n}\n\nfunction runInfo(argv: Array<string>, io: Io): number {\n const { values } = parseArgs({ args: argv, options: { config: { type: \"string\" } } });\n const file = path.resolve(values.config || \"pocketbomb.app.toml\");\n\n io.log(\"pocketbomb-detonator \" + readPackageVersion());\n io.log(\"node \" + process.version);\n\n let manifest: AppManifest;\n try {\n manifest = loadManifest(file);\n } catch (err) {\n io.log(\"manifest \" + String((err as Error).message));\n return 1;\n }\n\n io.log(\"manifest \" + path.basename(file));\n io.log(\"handle \" + manifest.handle + \" \" + (manifest.version || \"\"));\n io.log(\"application_url \" + (manifest.application_url || \"(not set)\"));\n io.log(\"scopes \" + (manifest.access?.scopes || []).join(\", \"));\n io.log(\n \"webhooks \" +\n (manifest.webhooks?.subscriptions || [])\n .map((s) => s.topics.join(\",\") + \" -> \" + s.uri)\n .join(\"; \"),\n );\n io.log(\n \"extensions \" +\n (manifest.extensions || []).map((e) => e.type + \":\" + e.handle + \" \" + e.path).join(\"; \"),\n );\n io.log(\"POCKETBOMB_URL \" + (process.env.POCKETBOMB_URL || \"(not set)\"));\n io.log(\n \"POCKETBOMB_CLIENT_SECRET \" + (process.env.POCKETBOMB_CLIENT_SECRET ? \"(set)\" : \"(not set)\"),\n );\n return 0;\n}\n\n/**\n * Runs the CLI with the provided arguments (without the node and script paths).\n */\nexport async function run(argv: Array<string>, io: Io = defaultIo): Promise<number> {\n const [command, ...rest] = argv;\n\n switch (command) {\n case \"dev\":\n return runDev(rest, io);\n case \"deploy\":\n return runDeploy(rest, io);\n case \"info\":\n return runInfo(rest, io);\n case \"--version\":\n case \"-v\":\n io.log(readPackageVersion());\n return 0;\n case undefined:\n case \"help\":\n case \"--help\":\n case \"-h\":\n io.log(HELP);\n return 0;\n default:\n io.error(\"Unknown command: \" + command + \"\\n\\n\" + HELP);\n return 2;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["fs","DashboardClient","AuthStore","createHash","path","resolveManifest","loadManifest","scopesToPermissions","spawn","parseArgs","deployManifest"],"sources":["../../src/cli/index.ts"],"sourcesContent":["// The `detonator` CLI (Node.js only): dev, deploy, info.\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { AuthStore } from \"pocketbomb-primer\";\nimport { DashboardClient } from \"pocketbomb-primer/internal\";\nimport {\n deployManifest,\n loadManifest,\n resolveManifest,\n scopesToPermissions,\n updateEnvFile,\n type AppManifest,\n} from \"./manifest\";\n\nexport * from \"./manifest\";\n\nconst HELP = `detonator <command> [options]\n\nCommands:\n dev Start fuse (unless --url/$POCKETBOMB_URL is set), import pocketbomb.app.toml as a custom app,\n run the app and install it\n --config <file> (default pocketbomb.app.toml)\n --url <url> --token <staff access token>\n use an existing server instead of fuse (or $POCKETBOMB_URL, $POCKETBOMB_TOKEN)\n --seed <module> the fuse seed (e.g. the collections of the scopes)\n --data-dir <dir> a persistent fuse data dir (default a temporary one)\n --fuse-port <port> the fuse server port (default a free port)\n --entry <file> the app server entry run with Node.js (default server.ts)\n --port <port> the app port (default a free port)\n --dotenv <file> the env file to update (default .env)\n deploy Deploy (import) the manifest to a PocketBomb instance\n --url <url> --token <staff access token> [--config <file>] [--app-url <url>]\n install Install the deployed application with the scopes of the manifest (idempotent)\n --url <url> --token <staff access token> [--config <file>]\n --dotenv <file> where the credentials go without an install_url (default .env)\n info Print the manifest summary and the environment\n`;\n\ntype Io = { log: (line: string) => void; error: (line: string) => void };\n\nconst defaultIo: Io = {\n log: (line) => process.stdout.write(line + \"\\n\"),\n error: (line) => process.stderr.write(line + \"\\n\"),\n};\n\nfunction readPackageVersion(): string {\n try {\n return JSON.parse(fs.readFileSync(new URL(\"../../package.json\", import.meta.url), \"utf8\"))\n .version;\n } catch {\n return \"unknown\";\n }\n}\n\nasync function waitForApp(url: string, child: ChildProcess, timeout = 60000): Promise<void> {\n const deadline = Date.now() + timeout;\n while (Date.now() < deadline) {\n if (child.exitCode !== null) {\n throw new Error(\"The app exited early (code \" + child.exitCode + \").\");\n }\n try {\n const res = await fetch(url, { redirect: \"manual\" });\n if (res.status < 500) {\n return;\n }\n } catch {\n // not listening yet\n }\n // poll interval of the app startup readiness check\n await new Promise((r) => setTimeout(r, 150));\n }\n throw new Error(\"The app didn't start in \" + timeout + \"ms.\");\n}\n\nfunction staffClient(url: string, token: string): DashboardClient {\n const client = new DashboardClient(url, { authStore: new AuthStore() });\n client.authStore.save(token.replace(/^Bearer /, \"\"), null);\n return client;\n}\n\nfunction devVersion(manifest: AppManifest): string {\n const digest = createHash(\"sha256\")\n .update(JSON.stringify(manifest))\n .digest(\"hex\")\n .substring(0, 12);\n return (manifest.version || \"0.0.0\").split(\"-\")[0] + \"-dev.\" + digest;\n}\n\nfunction isLoopback(url: string): boolean {\n return [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(new URL(url).hostname);\n}\n\nexport interface DevOptions {\n cwd?: string;\n config?: string;\n seed?: string;\n entry?: string;\n port?: number;\n envFile?: string;\n /** An existing server to use instead of fuse (default to $POCKETBOMB_URL). */\n url?: string;\n /** The staff access token of the existing server (default to $POCKETBOMB_TOKEN). */\n token?: string;\n /** A persistent data dir of fuse (the seed runs only once per data dir). */\n dataDir?: string;\n /** The port of fuse (default to a free one). */\n fusePort?: number;\n io?: Io;\n /** Called once the app is installed (the CLI waits for the app process otherwise). */\n onReady?: (state: DevState) => void | Promise<void>;\n}\n\nexport interface DevState {\n pocketbombUrl: string;\n appUrl: string;\n issuer: string;\n installationId: string;\n applicationId: string;\n notified: boolean;\n app: ChildProcess;\n /** The fuse Owner (null with an existing server). */\n owner: { email: string; password: string } | null;\n stop(): Promise<void>;\n}\n\n/**\n * `detonator dev`: fuse -> import the manifest (with the dev app URL) as a custom application ->\n * run the app -> install it with the manifest scopes (the credentials are delivered to the running\n * app install_url, or written to the env file for a manifest without install_url).\n */\nexport async function dev(options: DevOptions = {}): Promise<DevState> {\n const io = options.io || defaultIo;\n const cwd = path.resolve(options.cwd || process.cwd());\n const manifestFile = path.resolve(cwd, options.config || \"pocketbomb.app.toml\");\n const envFile = path.resolve(cwd, options.envFile || \".env\");\n const entry = path.resolve(cwd, options.entry || \"server.ts\");\n\n const { freePort, signInStaff, startFuse } = await import(\"pocketbomb-fuse/node\");\n\n const serverUrl = (options.url || process.env.POCKETBOMB_URL || \"\").replace(/\\/+$/, \"\");\n const token = options.token || process.env.POCKETBOMB_TOKEN;\n if (serverUrl && !token) {\n throw new Error(\n \"detonator dev: --token (or $POCKETBOMB_TOKEN) is required with an existing server.\",\n );\n }\n\n const port = options.port || (await freePort(\"localhost\"));\n const appUrl = \"http://localhost:\" + port;\n const manifest = resolveManifest(loadManifest(manifestFile), appUrl);\n\n const fuse = serverUrl\n ? null\n : await startFuse({\n seed: options.seed ? path.resolve(cwd, options.seed) : undefined,\n dataDir: options.dataDir ? path.resolve(cwd, options.dataDir) : undefined,\n port: options.fusePort,\n log: (line) => io.error(\"[fuse] \" + line),\n });\n const pocketbombUrl = fuse ? fuse.url : serverUrl;\n io.error(\n fuse\n ? \"[detonator] PocketBomb (fuse) running on \" + fuse.url\n : \"[detonator] using the PocketBomb server \" + serverUrl,\n );\n\n let app: ChildProcess | null = null;\n const stop = async () => {\n if (app && app.exitCode === null) {\n const exited = new Promise((r) => app!.once(\"exit\", r));\n app.kill(\"SIGTERM\");\n await exited;\n }\n await fuse?.stop();\n };\n\n try {\n const owner = fuse\n ? await signInStaff(fuse.url, fuse.owner.email, fuse.owner.password)\n : staffClient(serverUrl, token!);\n\n // the dev app (and its webhooks) are on a loopback host; an existing server keeps its\n // settings unless it is a local one, which couldn't reach the dev app otherwise\n if (fuse) {\n await owner.settings.update({\n meta: { appURL: fuse.url },\n webhooks: { allowPrivateTargets: true },\n });\n } else if (isLoopback(serverUrl)) {\n await owner.settings.update({ webhooks: { allowPrivateTargets: true } });\n }\n\n // the versions are immutable and a dev manifest changes with the port: on a server\n // that outlives this run the dev version is derived from the manifest content\n if (!fuse) {\n manifest.version = devVersion(manifest);\n }\n\n const imported = await owner.applications.importManifest(manifest as any, \"json\");\n io.error(\"[detonator] imported \" + manifest.handle + \" \" + imported.version.version);\n\n const collections = await owner.collections.getFullList({ fields: \"id,name\" });\n const permissions = scopesToPermissions(manifest.access?.scopes || [], collections);\n const issuer = pocketbombUrl + \"/oauth2/_applicationInstallations\";\n\n const baseEnv: Record<string, string> = {\n ...fuse?.env,\n POCKETBOMB_URL: pocketbombUrl,\n POCKETBOMB_ISSUER: issuer,\n POCKETBOMB_APP_HANDLE: manifest.handle,\n APP_URL: appUrl,\n PORT: String(port),\n };\n\n const startApp = async (env: Record<string, string>) => {\n app = spawn(process.execPath, [entry], {\n cwd,\n stdio: \"inherit\",\n env: { ...process.env, ...env, NODE_ENV: process.env.NODE_ENV || \"development\" },\n });\n await waitForApp(appUrl + \"/\", app);\n io.error(\"[detonator] app running on \" + appUrl);\n };\n\n // an existing server may already have it installed (a previous `detonator dev`): new\n // credentials, because the secrets are only available when they are issued\n const existing = fuse\n ? null\n : (await owner.applications.getList()).items.find((a) => a.id === imported.application.id)\n ?.installation;\n const installApp = async () => {\n if (!existing) {\n return owner.applications.install(imported.application.id, permissions as any);\n }\n await owner.applications.update(existing.id, {\n permissions: permissions as any,\n versionRef: imported.version.id,\n });\n return owner.applications.rotateSecret(existing.id);\n };\n\n let installed;\n let envValues: Record<string, string>;\n if (manifest.install_url) {\n // the credentials are delivered to the running app (install notification)\n await startApp(baseEnv);\n installed = await installApp();\n envValues = { ...baseEnv, POCKETBOMB_INSTALLATION_ID: installed.installation.id };\n } else {\n // a custom app without install_url gets the credentials once: env file + app env\n installed = await installApp();\n envValues = {\n ...baseEnv,\n POCKETBOMB_INSTALLATION_ID: installed.installation.id,\n POCKETBOMB_CLIENT_ID: installed.clientId,\n POCKETBOMB_CLIENT_SECRET: installed.clientSecret,\n POCKETBOMB_WEBHOOK_SECRET: installed.webhookSecret,\n };\n await startApp(envValues);\n }\n\n updateEnvFile(envFile, envValues);\n io.error(\n \"[detonator] installed \" +\n manifest.handle +\n \" (installation \" +\n installed.installation.id +\n (installed.notified\n ? \", credentials delivered to the install_url\"\n : \", credentials in \" + path.basename(envFile)) +\n \")\",\n );\n\n const state: DevState = {\n pocketbombUrl,\n appUrl,\n issuer,\n installationId: installed.installation.id,\n applicationId: imported.application.id,\n notified: installed.notified,\n app: app!,\n owner: fuse?.owner ?? null,\n stop,\n };\n\n await options.onReady?.(state);\n\n return state;\n } catch (err) {\n await stop();\n throw err;\n }\n}\n\nasync function runDev(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\" },\n url: { type: \"string\" },\n token: { type: \"string\" },\n seed: { type: \"string\" },\n \"data-dir\": { type: \"string\" },\n \"fuse-port\": { type: \"string\" },\n entry: { type: \"string\" },\n port: { type: \"string\" },\n dotenv: { type: \"string\" },\n },\n });\n\n const state = await dev({\n config: values.config,\n url: values.url,\n token: values.token,\n seed: values.seed,\n dataDir: values[\"data-dir\"],\n fusePort: values[\"fuse-port\"] ? parseInt(values[\"fuse-port\"], 10) : undefined,\n entry: values.entry,\n port: values.port ? parseInt(values.port, 10) : undefined,\n envFile: values.dotenv,\n io,\n });\n io.log(\n \"Dashboard: \" +\n state.pocketbombUrl +\n \"/_/\" +\n (state.owner ? \" (\" + state.owner.email + \" / \" + state.owner.password + \")\" : \"\"),\n );\n io.log(\"App: \" + state.appUrl);\n\n const code: number = await new Promise((resolve) => {\n state.app.once(\"exit\", (c) => resolve(c ?? 0));\n const shutdown = () => void state.stop().then(() => resolve(0));\n process.once(\"SIGINT\", shutdown);\n process.once(\"SIGTERM\", shutdown);\n });\n await state.stop();\n return code;\n}\n\nasync function runDeploy(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n url: { type: \"string\" },\n token: { type: \"string\" },\n config: { type: \"string\" },\n \"app-url\": { type: \"string\" },\n },\n });\n\n const url = values.url || process.env.POCKETBOMB_URL;\n const token = values.token || process.env.POCKETBOMB_TOKEN;\n if (!url || !token) {\n io.error(\n \"detonator deploy: --url and --token are required (or $POCKETBOMB_URL / $POCKETBOMB_TOKEN)\",\n );\n return 2;\n }\n\n const manifest = resolveManifest(\n loadManifest(path.resolve(values.config || \"pocketbomb.app.toml\")),\n values[\"app-url\"],\n );\n\n const result = await deployManifest({ url, token, manifest });\n io.log(\n (result.created ? \"created \" : result.unchanged ? \"unchanged \" : \"new version of \") +\n manifest.handle +\n \" \" +\n (result.version?.version || \"\") +\n \" (application \" +\n result.application?.id +\n \")\",\n );\n return 0;\n}\n\nasync function runInstall(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n url: { type: \"string\" },\n token: { type: \"string\" },\n config: { type: \"string\" },\n dotenv: { type: \"string\" },\n },\n });\n\n const url = values.url || process.env.POCKETBOMB_URL;\n const token = values.token || process.env.POCKETBOMB_TOKEN;\n if (!url || !token) {\n io.error(\n \"detonator install: --url and --token are required (or $POCKETBOMB_URL / $POCKETBOMB_TOKEN)\",\n );\n return 2;\n }\n\n const manifest = loadManifest(path.resolve(values.config || \"pocketbomb.app.toml\"));\n const owner = staffClient(url.replace(/\\/+$/, \"\"), token);\n\n const application = (await owner.applications.getList()).items.find(\n (a) => a.handle === manifest.handle,\n );\n if (!application) {\n io.error(\"detonator install: \" + manifest.handle + \" isn't deployed (run detonator deploy).\");\n return 1;\n }\n if (application.installation) {\n io.log(\n manifest.handle + \" is already installed (installation \" + application.installation.id + \")\",\n );\n return 0;\n }\n\n const collections = await owner.collections.getFullList({ fields: \"id,name\" });\n const permissions = scopesToPermissions(manifest.access?.scopes || [], collections);\n const installed = await owner.applications.install(application.id, permissions as any);\n\n if (installed.notified) {\n io.log(\n \"installed \" +\n manifest.handle +\n \" (installation \" +\n installed.installation.id +\n \", credentials delivered to the install_url)\",\n );\n return 0;\n }\n\n // returned only this once: never printed\n const envFile = path.resolve(values.dotenv || \".env\");\n updateEnvFile(envFile, {\n POCKETBOMB_URL: url.replace(/\\/+$/, \"\"),\n POCKETBOMB_INSTALLATION_ID: installed.installation.id,\n POCKETBOMB_CLIENT_ID: installed.clientId,\n POCKETBOMB_CLIENT_SECRET: installed.clientSecret,\n POCKETBOMB_WEBHOOK_SECRET: installed.webhookSecret,\n });\n io.log(\n \"installed \" +\n manifest.handle +\n \" (installation \" +\n installed.installation.id +\n \", credentials in \" +\n path.basename(envFile) +\n \")\",\n );\n return 0;\n}\n\nfunction runInfo(argv: Array<string>, io: Io): number {\n const { values } = parseArgs({ args: argv, options: { config: { type: \"string\" } } });\n const file = path.resolve(values.config || \"pocketbomb.app.toml\");\n\n io.log(\"pocketbomb-detonator \" + readPackageVersion());\n io.log(\"node \" + process.version);\n\n let manifest: AppManifest;\n try {\n manifest = loadManifest(file);\n } catch (err) {\n io.log(\"manifest \" + String((err as Error).message));\n return 1;\n }\n\n io.log(\"manifest \" + path.basename(file));\n io.log(\"handle \" + manifest.handle + \" \" + (manifest.version || \"\"));\n io.log(\"application_url \" + (manifest.application_url || \"(not set)\"));\n io.log(\"scopes \" + (manifest.access?.scopes || []).join(\", \"));\n io.log(\n \"webhooks \" +\n (manifest.webhooks?.subscriptions || [])\n .map((s) => s.topics.join(\",\") + \" -> \" + s.uri)\n .join(\"; \"),\n );\n io.log(\n \"extensions \" +\n (manifest.extensions || []).map((e) => e.type + \":\" + e.handle + \" \" + e.path).join(\"; \"),\n );\n io.log(\"POCKETBOMB_URL \" + (process.env.POCKETBOMB_URL || \"(not set)\"));\n io.log(\n \"POCKETBOMB_CLIENT_SECRET \" + (process.env.POCKETBOMB_CLIENT_SECRET ? \"(set)\" : \"(not set)\"),\n );\n return 0;\n}\n\n/**\n * Runs the CLI with the provided arguments (without the node and script paths).\n */\nexport async function run(argv: Array<string>, io: Io = defaultIo): Promise<number> {\n const [command, ...rest] = argv;\n\n switch (command) {\n case \"dev\":\n return runDev(rest, io);\n case \"deploy\":\n return runDeploy(rest, io);\n case \"install\":\n return runInstall(rest, io);\n case \"info\":\n return runInfo(rest, io);\n case \"--version\":\n case \"-v\":\n io.log(readPackageVersion());\n return 0;\n case undefined:\n case \"help\":\n case \"--help\":\n case \"-h\":\n io.log(HELP);\n return 0;\n default:\n io.error(\"Unknown command: \" + command + \"\\n\\n\" + HELP);\n return 2;\n }\n}\n"],"mappings":"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"}
|
package/dist/cli/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { scopesToPermissions } from "../scopes.cjs";
|
|
2
|
+
import { AppManifest, deployManifest, formatEnv, loadManifest, resolveManifest, updateEnvFile } from "./manifest.cjs";
|
|
2
3
|
import { ChildProcess } from "node:child_process";
|
|
3
4
|
type Io = {
|
|
4
5
|
log: (line: string) => void;
|
|
@@ -11,6 +12,14 @@ export interface DevOptions {
|
|
|
11
12
|
entry?: string;
|
|
12
13
|
port?: number;
|
|
13
14
|
envFile?: string;
|
|
15
|
+
/** An existing server to use instead of fuse (default to $POCKETBOMB_URL). */
|
|
16
|
+
url?: string;
|
|
17
|
+
/** The staff access token of the existing server (default to $POCKETBOMB_TOKEN). */
|
|
18
|
+
token?: string;
|
|
19
|
+
/** A persistent data dir of fuse (the seed runs only once per data dir). */
|
|
20
|
+
dataDir?: string;
|
|
21
|
+
/** The port of fuse (default to a free one). */
|
|
22
|
+
fusePort?: number;
|
|
14
23
|
io?: Io;
|
|
15
24
|
/** Called once the app is installed (the CLI waits for the app process otherwise). */
|
|
16
25
|
onReady?: (state: DevState) => void | Promise<void>;
|
|
@@ -23,10 +32,11 @@ export interface DevState {
|
|
|
23
32
|
applicationId: string;
|
|
24
33
|
notified: boolean;
|
|
25
34
|
app: ChildProcess;
|
|
35
|
+
/** The fuse Owner (null with an existing server). */
|
|
26
36
|
owner: {
|
|
27
37
|
email: string;
|
|
28
38
|
password: string;
|
|
29
|
-
};
|
|
39
|
+
} | null;
|
|
30
40
|
stop(): Promise<void>;
|
|
31
41
|
}
|
|
32
42
|
/**
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { scopesToPermissions } from "../scopes.js";
|
|
2
|
+
import { AppManifest, deployManifest, formatEnv, loadManifest, resolveManifest, updateEnvFile } from "./manifest.js";
|
|
2
3
|
import { ChildProcess } from "node:child_process";
|
|
3
4
|
type Io = {
|
|
4
5
|
log: (line: string) => void;
|
|
@@ -11,6 +12,14 @@ export interface DevOptions {
|
|
|
11
12
|
entry?: string;
|
|
12
13
|
port?: number;
|
|
13
14
|
envFile?: string;
|
|
15
|
+
/** An existing server to use instead of fuse (default to $POCKETBOMB_URL). */
|
|
16
|
+
url?: string;
|
|
17
|
+
/** The staff access token of the existing server (default to $POCKETBOMB_TOKEN). */
|
|
18
|
+
token?: string;
|
|
19
|
+
/** A persistent data dir of fuse (the seed runs only once per data dir). */
|
|
20
|
+
dataDir?: string;
|
|
21
|
+
/** The port of fuse (default to a free one). */
|
|
22
|
+
fusePort?: number;
|
|
14
23
|
io?: Io;
|
|
15
24
|
/** Called once the app is installed (the CLI waits for the app process otherwise). */
|
|
16
25
|
onReady?: (state: DevState) => void | Promise<void>;
|
|
@@ -23,10 +32,11 @@ export interface DevState {
|
|
|
23
32
|
applicationId: string;
|
|
24
33
|
notified: boolean;
|
|
25
34
|
app: ChildProcess;
|
|
35
|
+
/** The fuse Owner (null with an existing server). */
|
|
26
36
|
owner: {
|
|
27
37
|
email: string;
|
|
28
38
|
password: string;
|
|
29
|
-
};
|
|
39
|
+
} | null;
|
|
30
40
|
stop(): Promise<void>;
|
|
31
41
|
}
|
|
32
42
|
/**
|