@devframes/hub-ui-onboard 1.3.2 → 1.3.3

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/index.d.mts CHANGED
@@ -53,6 +53,12 @@ interface CreateOnboardingOptions {
53
53
  stateDir?: string;
54
54
  /** Install as a devDependency. Default: `true`. */
55
55
  dev?: boolean;
56
+ /**
57
+ * Stop the install when it runs longer than this many milliseconds, and
58
+ * show an error that asks the user to run the command in a terminal.
59
+ * Default: `300_000` (5 minutes).
60
+ */
61
+ installTimeout?: number;
56
62
  branding?: OnboardingBranding;
57
63
  messages?: Partial<OnboardingMessages>;
58
64
  /**
package/dist/index.mjs CHANGED
@@ -54,6 +54,10 @@ const diagnostics = defineDiagnostics({
54
54
  DF9008: {
55
55
  why: (p) => `Nobody allowed \`${p.command}\` within ${p.minutes} minutes. The install was refused.`,
56
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()`."
57
+ },
58
+ DF9009: {
59
+ why: (p) => `\`${p.command}\` did not finish within ${p.seconds} seconds, so it was stopped.`,
60
+ fix: "Run the command in a terminal to see where the package manager stops, then click Install again. If the install is only slow, raise `installTimeout`."
57
61
  }
58
62
  }
59
63
  });
@@ -66,18 +70,35 @@ async function resolveInstallCommand(plan) {
66
70
  }
67
71
  /**
68
72
  * Run the install and make sure that each named package landed in
69
- * `<cwd>/node_modules`. Throws a `DF9001` / `DF9002` diagnostic on failure.
73
+ * `<cwd>/node_modules`. Throws a `DF9001` / `DF9002` / `DF9009` diagnostic on failure.
70
74
  */
71
75
  async function runInstall(plan, command) {
72
- const result = await exec(command.command, command.args, { nodeOptions: {
76
+ const commandLine = [command.command, ...command.args].join(" ");
77
+ const child = exec(command.command, command.args, { nodeOptions: {
73
78
  cwd: plan.cwd,
74
79
  env: {
75
80
  ...process.env,
76
81
  CI: "1"
77
82
  }
78
83
  } });
84
+ let timer;
85
+ let killed;
86
+ const timedOut = new Promise((resolve) => {
87
+ timer = setTimeout(() => {
88
+ killed = killTree(child);
89
+ killed.then(() => resolve(void 0));
90
+ }, plan.timeout);
91
+ });
92
+ const result = await Promise.race([child, timedOut]).finally(() => clearTimeout(timer));
93
+ if (killed || !result) {
94
+ await killed;
95
+ throw diagnostics.DF9009({
96
+ command: commandLine,
97
+ seconds: Math.round(plan.timeout / 1e3)
98
+ });
99
+ }
79
100
  if (result.exitCode !== 0) throw diagnostics.DF9001({
80
- command: [command.command, ...command.args].join(" "),
101
+ command: commandLine,
81
102
  exitCode: result.exitCode,
82
103
  stderr: result.stderr.trim().slice(-2048)
83
104
  });
@@ -87,6 +108,23 @@ async function runInstall(plan, command) {
87
108
  });
88
109
  }
89
110
  /**
111
+ * Kill the install with everything it started. On Windows, tinyexec runs a
112
+ * package manager's `.cmd` shim through `cmd.exe`, and a kill does not reach
113
+ * child processes, so `taskkill /T /F` ends the whole tree.
114
+ */
115
+ async function killTree(child) {
116
+ const pid = child.process?.pid;
117
+ if (process.platform === "win32" && pid !== void 0) {
118
+ if ((await exec("taskkill", [
119
+ "/pid",
120
+ String(pid),
121
+ "/T",
122
+ "/F"
123
+ ]).then((r) => r, () => void 0))?.exitCode === 0) return;
124
+ }
125
+ child.kill();
126
+ }
127
+ /**
90
128
  * `true` when every named package is already in `<cwd>/node_modules`, so the
91
129
  * onboarding has nothing to install. Path, URL and alias specs cannot be
92
130
  * checked by name, so a plan made only of those is never "already installed".
@@ -149,6 +187,7 @@ function confirmInTerminal(command, input = process.stdin) {
149
187
  }
150
188
  // src/index.ts
151
189
  const STATE_FILE = "hub-ui-onboard.json";
190
+ const DEFAULT_INSTALL_TIMEOUT = 3e5;
152
191
  /** `devframes` → `/devframes/`, same rule as `normalizeHubBase` in `@devframes/hub`. */
153
192
  function normalizeBase(base) {
154
193
  return `/${base}/`.replace(/\/{2,}/g, "/");
@@ -242,7 +281,8 @@ function createOnboarding(options) {
242
281
  const plan = {
243
282
  cwd,
244
283
  packages: options.packages,
245
- dev: options.dev ?? true
284
+ dev: options.dev ?? true,
285
+ timeout: options.installTimeout ?? DEFAULT_INSTALL_TIMEOUT
246
286
  };
247
287
  const branding = options.branding ?? {};
248
288
  const messages = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@devframes/hub-ui-onboard",
3
3
  "type": "module",
4
- "version": "1.3.2",
4
+ "version": "1.3.3",
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",
@@ -34,11 +34,11 @@
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^26.6.6",
37
- "tsdown": "^0.23.4",
37
+ "tsdown": "^0.23.7",
38
38
  "tsx": "^4.23.15",
39
39
  "unocss": "^66.12.0",
40
- "vite": "^8.3.5",
41
- "vitest": "^5.0.5"
40
+ "vite": "^8.3.6",
41
+ "vitest": "^5.1.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "pnpm run build:css && tsdown && vite build --config vite.client.config.ts",