@devframes/hub-ui-onboard 1.2.4 → 1.2.6
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/dist/client/embedded.js +5 -1
- package/dist/index.d.mts +23 -1
- package/dist/index.mjs +79 -5
- package/package.json +1 -1
package/dist/client/embedded.js
CHANGED
|
@@ -119,10 +119,14 @@ function p(e) {
|
|
|
119
119
|
U(e), e.state === "installing" && setTimeout(() => void W(), n);
|
|
120
120
|
}
|
|
121
121
|
async function G() {
|
|
122
|
+
if (P.disabled = !0, I.textContent = h.confirming, !(await i("install", { method: "POST" })).ok) {
|
|
123
|
+
await W(), N.hidden = !1, N.textContent = h.refused;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
122
126
|
U({
|
|
123
127
|
...e,
|
|
124
128
|
state: "installing"
|
|
125
|
-
}), await
|
|
129
|
+
}), await W();
|
|
126
130
|
}
|
|
127
131
|
async function K() {
|
|
128
132
|
(await i("disable", { method: "POST" })).ok && t.remove();
|
package/dist/index.d.mts
CHANGED
|
@@ -19,6 +19,10 @@ interface OnboardingMessages {
|
|
|
19
19
|
/** Short sentence under the heading. */
|
|
20
20
|
description: string;
|
|
21
21
|
install: string;
|
|
22
|
+
/** Install button label while `onConfirm` waits for the developer. */
|
|
23
|
+
confirming: string;
|
|
24
|
+
/** Shown when `onConfirm` refused the install. */
|
|
25
|
+
refused: string;
|
|
22
26
|
/** Hides the button for this browser tab (`sessionStorage`). */
|
|
23
27
|
hide: string;
|
|
24
28
|
/** Writes the state file; the host stops injecting the button. */
|
|
@@ -30,6 +34,15 @@ interface OnboardingMessages {
|
|
|
30
34
|
}
|
|
31
35
|
/** A web-standard request handler the hub exposes once installed. */
|
|
32
36
|
type OnboardingHandler = (request: Request) => Response | Promise<Response>;
|
|
37
|
+
/** What `onConfirm` receives when the panel asks to install. */
|
|
38
|
+
interface OnboardingConfirmation {
|
|
39
|
+
/** The packages Install adds, as given in `packages`. */
|
|
40
|
+
packages: string[];
|
|
41
|
+
/** The exact command Install runs. */
|
|
42
|
+
command: string[];
|
|
43
|
+
/** The panel's request, for example to read its `Origin` or `User-Agent`. */
|
|
44
|
+
request: Request;
|
|
45
|
+
}
|
|
33
46
|
interface CreateOnboardingOptions {
|
|
34
47
|
/** Packages to install when the user clicks Install. Package specs as the package manager accepts them. */
|
|
35
48
|
packages: string[];
|
|
@@ -50,6 +63,15 @@ interface CreateOnboardingOptions {
|
|
|
50
63
|
* user to restart the dev server.
|
|
51
64
|
*/
|
|
52
65
|
onInstalled?: () => void | OnboardingHandler | Promise<void | OnboardingHandler>;
|
|
66
|
+
/**
|
|
67
|
+
* Ask the developer to allow an install that the panel requested; the
|
|
68
|
+
* dev server can be reachable from other devices. Return `true` to run
|
|
69
|
+
* it. Only one call is open at a time: clicks meanwhile share its answer.
|
|
70
|
+
* Default: a `y/N` question in the terminal. It refuses at once when stdin
|
|
71
|
+
* is not a TTY or another reader holds it in raw mode, and after 5 minutes
|
|
72
|
+
* without an answer.
|
|
73
|
+
*/
|
|
74
|
+
onConfirm?: (confirmation: OnboardingConfirmation) => boolean | Promise<boolean>;
|
|
53
75
|
}
|
|
54
76
|
type OnboardingState = 'idle' | 'installing' | 'installed' | 'ready' | 'error' | 'disabled';
|
|
55
77
|
/** The `GET <base>__onboard/status` body. */
|
|
@@ -90,4 +112,4 @@ export declare function defaultMessages(productName: string): OnboardingMessages
|
|
|
90
112
|
*/
|
|
91
113
|
export declare function createOnboarding(options: CreateOnboardingOptions): Onboarding;
|
|
92
114
|
//#endregion
|
|
93
|
-
export type { CreateOnboardingOptions, Onboarding, OnboardingBranding, OnboardingHandler, OnboardingLogo, OnboardingMessages, OnboardingState, OnboardingStatus };
|
|
115
|
+
export type { CreateOnboardingOptions, Onboarding, OnboardingBranding, OnboardingConfirmation, OnboardingHandler, OnboardingLogo, OnboardingMessages, OnboardingState, OnboardingStatus };
|
package/dist/index.mjs
CHANGED
|
@@ -38,6 +38,22 @@ const diagnostics = defineDiagnostics({
|
|
|
38
38
|
DF9004: {
|
|
39
39
|
why: (p) => `The onboarding state file ${p.file} could not be read or written.`,
|
|
40
40
|
fix: "Make sure that `stateDir` is writable, or point it at another directory."
|
|
41
|
+
},
|
|
42
|
+
DF9005: {
|
|
43
|
+
why: (p) => `A browser asked to run \`${p.command}\`. Type y and press Enter within ${p.minutes} minutes to allow it.`,
|
|
44
|
+
fix: "If you did not click Install, press Enter to refuse. Another device can reach this dev server, so bind it to localhost."
|
|
45
|
+
},
|
|
46
|
+
DF9006: {
|
|
47
|
+
why: (p) => `A browser asked to run \`${p.command}\`, but no interactive terminal can confirm it. The install was refused.`,
|
|
48
|
+
fix: "Run the command yourself, or pass an `onConfirm` function to `createOnboarding()`."
|
|
49
|
+
},
|
|
50
|
+
DF9007: {
|
|
51
|
+
why: (p) => `A browser asked to run \`${p.command}\`, but another program reads this terminal key by key, so it cannot show the question. The install was refused.`,
|
|
52
|
+
fix: "Run the command yourself. If a CLI owns the terminal, pass an `onConfirm` function to `createOnboarding()` that asks through that CLI."
|
|
53
|
+
},
|
|
54
|
+
DF9008: {
|
|
55
|
+
why: (p) => `Nobody allowed \`${p.command}\` within ${p.minutes} minutes. The install was refused.`,
|
|
56
|
+
fix: "Click Install again and answer in the terminal that runs the dev server. If the answer does not arrive, another program reads that terminal: pass an `onConfirm` function to `createOnboarding()`."
|
|
41
57
|
}
|
|
42
58
|
}
|
|
43
59
|
});
|
|
@@ -94,6 +110,46 @@ function packageName(spec) {
|
|
|
94
110
|
return /^((?:@[\w.~-]+\/)?[\w.~-]+)(?:@[^/]*)?$/.exec(spec)?.[1];
|
|
95
111
|
}
|
|
96
112
|
//#endregion
|
|
113
|
+
//#region src/terminal-confirm.ts
|
|
114
|
+
const ANSWER_MINUTES = 5;
|
|
115
|
+
/**
|
|
116
|
+
* The default `onConfirm`: a `y/N` question in the terminal that runs the dev
|
|
117
|
+
* server. When nobody can answer it, it refuses.
|
|
118
|
+
*/
|
|
119
|
+
function confirmInTerminal(command, input = process.stdin) {
|
|
120
|
+
const line = command.join(" ");
|
|
121
|
+
if (!input.isTTY) {
|
|
122
|
+
diagnostics.DF9006({ command: line });
|
|
123
|
+
return Promise.resolve(false);
|
|
124
|
+
}
|
|
125
|
+
if (input.isRaw) {
|
|
126
|
+
diagnostics.DF9007({ command: line });
|
|
127
|
+
return Promise.resolve(false);
|
|
128
|
+
}
|
|
129
|
+
diagnostics.DF9005({
|
|
130
|
+
command: line,
|
|
131
|
+
minutes: ANSWER_MINUTES
|
|
132
|
+
});
|
|
133
|
+
const flowing = input.readableFlowing;
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
const timer = setTimeout(() => {
|
|
136
|
+
diagnostics.DF9008({
|
|
137
|
+
command: line,
|
|
138
|
+
minutes: ANSWER_MINUTES
|
|
139
|
+
});
|
|
140
|
+
settle(false);
|
|
141
|
+
}, ANSWER_MINUTES * 6e4);
|
|
142
|
+
const onData = (chunk) => settle(/^y(?:es)?$/i.test(String(chunk).trim()));
|
|
143
|
+
function settle(allowed) {
|
|
144
|
+
clearTimeout(timer);
|
|
145
|
+
input.off("data", onData);
|
|
146
|
+
if (!flowing) input.pause();
|
|
147
|
+
resolve(allowed);
|
|
148
|
+
}
|
|
149
|
+
input.on("data", onData);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
97
153
|
//#region src/index.ts
|
|
98
154
|
const STATE_FILE = "hub-ui-onboard.json";
|
|
99
155
|
/** `devframes` → `/devframes/`, same rule as `normalizeHubBase` in `@devframes/hub`. */
|
|
@@ -115,6 +171,8 @@ function defaultMessages(productName) {
|
|
|
115
171
|
title: productName,
|
|
116
172
|
description: `${productName} enables development features in your project. Install it with:`,
|
|
117
173
|
install: `Install ${productName}`,
|
|
174
|
+
confirming: "Confirm in your terminal...",
|
|
175
|
+
refused: "The dev server did not allow the install. Check its terminal, or run the command above yourself.",
|
|
118
176
|
hide: "Hide in this session",
|
|
119
177
|
disable: "Disable entirely",
|
|
120
178
|
installing: "Installing...",
|
|
@@ -258,11 +316,29 @@ function createOnboarding(options) {
|
|
|
258
316
|
state = "disabled";
|
|
259
317
|
return new Response(null, { status: 204 });
|
|
260
318
|
}
|
|
319
|
+
const commandLine = async () => {
|
|
320
|
+
const resolved = await getCommand();
|
|
321
|
+
return [resolved.command, ...resolved.args];
|
|
322
|
+
};
|
|
323
|
+
const onConfirm = options.onConfirm ?? (({ command }) => confirmInTerminal(command));
|
|
324
|
+
let asking;
|
|
325
|
+
async function startInstall(request) {
|
|
326
|
+
if (state !== "idle" && state !== "error") return new Response(null, { status: 202 });
|
|
327
|
+
asking ??= commandLine().then((command) => onConfirm({
|
|
328
|
+
packages: plan.packages,
|
|
329
|
+
command,
|
|
330
|
+
request
|
|
331
|
+
})).finally(() => {
|
|
332
|
+
asking = void 0;
|
|
333
|
+
});
|
|
334
|
+
if (!await asking) return new Response(null, { status: 403 });
|
|
335
|
+
if (state === "idle" || state === "error") install();
|
|
336
|
+
return new Response(null, { status: 202 });
|
|
337
|
+
}
|
|
261
338
|
async function status() {
|
|
262
|
-
const command = await getCommand();
|
|
263
339
|
return {
|
|
264
340
|
state,
|
|
265
|
-
command:
|
|
341
|
+
command: await commandLine(),
|
|
266
342
|
branding,
|
|
267
343
|
messages,
|
|
268
344
|
error
|
|
@@ -281,9 +357,7 @@ function createOnboarding(options) {
|
|
|
281
357
|
if (route === "GET __onboard/status") return json(await status());
|
|
282
358
|
if (route === "POST __onboard/install" || route === "POST __onboard/disable") {
|
|
283
359
|
if (!isSameOrigin(request)) return new Response(null, { status: 403 });
|
|
284
|
-
|
|
285
|
-
if (state === "idle" || state === "error") install();
|
|
286
|
-
return new Response(null, { status: 202 });
|
|
360
|
+
return route.endsWith("disable") ? disable() : startInstall(request);
|
|
287
361
|
}
|
|
288
362
|
return new Response(null, { status: 404 });
|
|
289
363
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devframes/hub-ui-onboard",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.6",
|
|
5
5
|
"description": "A tiny stand-in for the devframe hub UI: a floating button that installs it on demand.",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|