@fedify/init 2.5.0-dev.2516 → 2.5.0-dev.2541

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.
Files changed (40) hide show
  1. package/README.md +17 -7
  2. package/dist/_virtual/_rolldown/runtime.js +26 -0
  3. package/dist/action/configs.js +23 -11
  4. package/dist/ask/kv.js +10 -10
  5. package/dist/ask/mq.js +10 -10
  6. package/dist/ask/pm.js +8 -5
  7. package/dist/command.d.ts +21 -1
  8. package/dist/json/kv.js +1 -0
  9. package/dist/json/kv.json +1 -0
  10. package/dist/json/mq.js +1 -0
  11. package/dist/json/mq.json +1 -0
  12. package/dist/lib.js +2 -2
  13. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.js +29 -0
  14. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js +42 -0
  15. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js +67 -0
  16. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js +23 -0
  17. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js +22 -0
  18. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js +36 -0
  19. package/dist/node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js +47 -0
  20. package/dist/node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js +33 -0
  21. package/dist/node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js +33 -0
  22. package/dist/node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.js +14 -0
  23. package/dist/node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.js +17 -0
  24. package/dist/node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.js +8 -0
  25. package/dist/node_modules/.pnpm/which@2.0.2/node_modules/which/which.js +77 -0
  26. package/dist/{deno.js → packages/init/deno.js} +1 -1
  27. package/dist/scripts/tooling_main.js +17 -0
  28. package/dist/test/action.d.ts +6 -0
  29. package/dist/test/action.js +15 -5
  30. package/dist/test/cancel.js +14 -0
  31. package/dist/test/create.js +10 -5
  32. package/dist/test/lookup.js +17 -7
  33. package/dist/test/mod.d.ts +6 -1
  34. package/dist/test/mod.js +13 -6
  35. package/dist/test/node.js +6 -0
  36. package/dist/test/port.js +34 -13
  37. package/dist/test/run.js +15 -9
  38. package/dist/test/server.js +136 -108
  39. package/dist/types.d.ts +4 -0
  40. package/package.json +4 -3
@@ -1,32 +1,39 @@
1
+ import { __toESM } from "../_virtual/_rolldown/runtime.js";
1
2
  import { printErrorMessage } from "../utils.js";
2
- import { ensurePortReleased, killProcessOnPort } from "./port.js";
3
+ import { TestInterruptedError } from "./cancel.js";
4
+ import { ensurePortReleased } from "./port.js";
5
+ import { require_cross_spawn } from "../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.js";
3
6
  import process from "node:process";
4
7
  import { createWriteStream } from "node:fs";
5
8
  import { join } from "node:path";
6
- import { spawn } from "node:child_process";
7
- import { Readable } from "node:stream";
9
+ import { execFile } from "node:child_process";
10
+ import { finished } from "node:stream/promises";
11
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
8
12
  //#region src/test/server.ts
13
+ var import_cross_spawn = /* @__PURE__ */ __toESM(require_cross_spawn(), 1);
9
14
  const STARTUP_TIMEOUT = 1e4;
10
- /**
11
- * Wait for the server to be ready by checking if it responds to requests.
12
- */
13
- async function waitForServer(url, timeout = STARTUP_TIMEOUT) {
15
+ /** Wait for the generated application to respond successfully. */
16
+ async function waitForServer(url, timeout = STARTUP_TIMEOUT, signal) {
14
17
  const startTime = Date.now();
15
18
  while (Date.now() - startTime < timeout) {
19
+ signal?.throwIfAborted();
16
20
  try {
17
- const response = await fetch(url, { signal: AbortSignal.timeout(1e3) });
21
+ const timeoutSignal = AbortSignal.timeout(1e3);
22
+ const response = await fetch(url, { signal: signal == null ? timeoutSignal : AbortSignal.any([signal, timeoutSignal]) });
18
23
  const ok = response.ok;
19
24
  await response.body?.cancel();
20
25
  if (ok) return true;
21
26
  } catch {}
22
- await new Promise((resolve) => setTimeout(resolve, 500));
27
+ await setTimeout$1(500, void 0, { signal });
23
28
  }
24
29
  return false;
25
30
  }
26
- async function serverClosure(dir, cmd, defaultPort, callback, releasePort) {
31
+ /** Run a generated application's server and always stop its process tree. */
32
+ async function serverClosure(dir, cmd, defaultPort, callback, releasePort, signal) {
27
33
  await releasePort?.();
28
- const devCommand = cmd.split(" ");
29
- const child = spawn(devCommand[0], devCommand.slice(1), {
34
+ signal?.throwIfAborted();
35
+ const command = typeof cmd === "string" ? cmd.split(" ") : cmd;
36
+ const child = (0, import_cross_spawn.default)(command[0], [...command.slice(1)], {
30
37
  cwd: dir,
31
38
  env: {
32
39
  ...process.env,
@@ -37,121 +44,142 @@ async function serverClosure(dir, cmd, defaultPort, callback, releasePort) {
37
44
  "pipe",
38
45
  "pipe"
39
46
  ],
40
- detached: true
47
+ detached: process.platform !== "win32"
41
48
  });
42
- child.on("error", () => {});
43
- const stdoutWeb = Readable.toWeb(child.stdout);
44
- const stderrWeb = Readable.toWeb(child.stderr);
45
- const [stdoutForFile, stdoutForPort] = stdoutWeb.tee();
46
- const [stderrForFile, stderrForPort] = stderrWeb.tee();
47
- const cleanup = new AbortController();
48
49
  const outFile = createWriteStream(join(dir, "out.txt"), { flags: "a" });
49
50
  const errFile = createWriteStream(join(dir, "err.txt"), { flags: "a" });
50
- const pipeOutDone = pipeStream(stdoutForFile, outFile, cleanup.signal);
51
- const pipeErrDone = pipeStream(stderrForFile, errFile, cleanup.signal);
51
+ const filesDone = Promise.all([finished(outFile), finished(errFile)]);
52
+ filesDone.catch(() => {});
53
+ child.stdout.pipe(outFile, { end: false });
54
+ child.stderr.pipe(errFile, { end: false });
55
+ let rejectFailure;
56
+ const failure = new Promise((_, reject) => rejectFailure = reject);
57
+ failure.catch(() => {});
58
+ const closed = new Promise((resolve) => child.once("close", () => resolve()));
59
+ child.once("error", (error) => rejectFailure(error));
60
+ child.once("exit", (code, exitSignal) => {
61
+ rejectFailure(/* @__PURE__ */ new Error(`Server exited (${exitSignal ?? code})`));
62
+ });
63
+ outFile.once("error", (error) => rejectFailure(error));
64
+ errFile.once("error", (error) => rejectFailure(error));
65
+ const onAbort = () => rejectFailure(signal?.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("Server test aborted"));
66
+ const onInterrupt = () => rejectFailure(new TestInterruptedError("SIGINT"));
67
+ const onTerminate = () => rejectFailure(new TestInterruptedError("SIGTERM"));
68
+ signal?.addEventListener("abort", onAbort, { once: true });
69
+ process.once("SIGINT", onInterrupt);
70
+ process.once("SIGTERM", onTerminate);
71
+ if (signal?.aborted) onAbort();
52
72
  let port = defaultPort;
73
+ const detection = detectPort(child);
74
+ let operationError;
75
+ let failed = false;
76
+ let result;
77
+ const shutdown = new AbortController();
53
78
  try {
54
- port = await determinePort(stdoutForPort, stderrForPort, cleanup.signal).catch((err) => {
55
- printErrorMessage`Failed to determine server port: ${err.message}`;
79
+ port = await Promise.race([detection.result.catch((error) => {
80
+ printErrorMessage`Failed to determine server port: ${error.message}`;
56
81
  printErrorMessage`Use default port ${String(defaultPort)} for lookup.`;
57
82
  return defaultPort;
58
- });
59
- return await callback(port);
83
+ }), failure]);
84
+ result = await Promise.race([callback(port, shutdown.signal), failure]);
85
+ } catch (error) {
86
+ operationError = error;
87
+ failed = true;
60
88
  } finally {
89
+ shutdown.abort(/* @__PURE__ */ new Error("Server test finished"));
90
+ detection.stop();
91
+ signal?.removeEventListener("abort", onAbort);
92
+ process.removeListener("SIGINT", onInterrupt);
93
+ process.removeListener("SIGTERM", onTerminate);
61
94
  try {
62
- if (child.pid != null) process.kill(-child.pid, "SIGKILL");
63
- } catch {}
95
+ await stopProcessTree(child, closed);
96
+ } catch (error) {
97
+ if (!failed) operationError = error;
98
+ failed = true;
99
+ printErrorMessage`Failed to stop server: ${String(error)}`;
100
+ } finally {
101
+ child.stdout.destroy();
102
+ child.stderr.destroy();
103
+ outFile.end();
104
+ errFile.end();
105
+ }
64
106
  try {
65
- child.kill("SIGKILL");
66
- } catch {}
67
- cleanup.abort();
68
- await Promise.all([pipeOutDone, pipeErrDone]).catch(() => {});
69
- await killProcessOnPort(port);
70
- outFile.end();
71
- errFile.end();
72
- await ensurePortReleased(port);
107
+ await filesDone;
108
+ if (child.pid != null) await ensurePortReleased(port);
109
+ } catch (error) {
110
+ if (!failed) operationError = error;
111
+ failed = true;
112
+ printErrorMessage`Failed to clean up server: ${String(error)}`;
113
+ }
73
114
  }
115
+ if (failed) throw operationError;
116
+ return result;
74
117
  }
75
- function determinePort(stdout, stderr, signal) {
76
- return new Promise((resolve, reject) => {
77
- const timeout = setTimeout(() => {
78
- reject(/* @__PURE__ */ new Error("Timeout: Could not determine port from server output"));
79
- }, STARTUP_TIMEOUT);
80
- let stdoutData = "";
81
- let stderrData = "";
82
- let streamsEnded = 0;
83
- const portPatterns = [
84
- /listening on.*:(\d+)/i,
85
- /server.*:(\d+)/i,
86
- /port\s*:?\s*(\d+)/i,
87
- /https?:\/\/localhost:(\d+)/i,
88
- /https?:\/\/0\.0\.0\.0:(\d+)/i,
89
- /https?:\/\/127\.0\.0\.1:(\d+)/i,
90
- /https?:\/\/[^:]+:(\d+)/i
91
- ];
92
- const checkForPort = (data) => {
93
- for (const pattern of portPatterns) {
94
- const match = data.match(pattern);
95
- if (match && match[1]) {
96
- const port = Number.parseInt(match[1], 10);
118
+ function detectPort(child) {
119
+ let output = "";
120
+ let timer;
121
+ let onData;
122
+ const stop = () => {
123
+ clearTimeout(timer);
124
+ child.stdout.removeListener("data", onData);
125
+ child.stderr.removeListener("data", onData);
126
+ };
127
+ return {
128
+ result: new Promise((resolve, reject) => {
129
+ timer = setTimeout(() => {
130
+ stop();
131
+ reject(/* @__PURE__ */ new Error("Timeout: Could not determine port from server output"));
132
+ }, STARTUP_TIMEOUT);
133
+ onData = (chunk) => {
134
+ output += chunk.toString("utf8");
135
+ for (const pattern of [
136
+ /listening on.*:(\d+)/i,
137
+ /server.*:(\d+)/i,
138
+ /port\s*:?\s*(\d+)/i,
139
+ /https?:\/\/[^:]+:(\d+)/i
140
+ ]) {
141
+ const match = pattern.exec(output);
142
+ const port = Number(match?.[1]);
97
143
  if (port > 0 && port < 65536) {
98
- clearTimeout(timeout);
99
- return port;
144
+ stop();
145
+ resolve(port);
146
+ break;
100
147
  }
101
148
  }
102
- }
103
- return null;
104
- };
105
- const onStreamEnd = () => {
106
- streamsEnded++;
107
- if (streamsEnded === 2) {
108
- clearTimeout(timeout);
109
- reject(/* @__PURE__ */ new Error("Server exited before port could be determined"));
110
- }
111
- };
112
- const readStream = async (stream, onData) => {
113
- const reader = stream.getReader();
114
- const onAbort = () => void reader.cancel().catch(() => {});
115
- signal?.addEventListener("abort", onAbort, { once: true });
116
- const decoder = new TextDecoder();
149
+ };
150
+ child.stdout.on("data", onData);
151
+ child.stderr.on("data", onData);
152
+ }),
153
+ stop
154
+ };
155
+ }
156
+ async function stopProcessTree(child, closed) {
157
+ if (child.pid == null) return;
158
+ if (process.platform === "win32") await new Promise((resolve) => {
159
+ execFile("taskkill", [
160
+ "/pid",
161
+ String(child.pid),
162
+ "/t",
163
+ "/f"
164
+ ], {
165
+ timeout: 1e3,
166
+ windowsHide: true
167
+ }, () => resolve());
168
+ });
169
+ else {
170
+ const kill = (signal) => {
117
171
  try {
118
- while (true) {
119
- const { done, value } = await reader.read();
120
- if (done) break;
121
- onData(decoder.decode(value, { stream: true }));
122
- }
123
- } catch {} finally {
124
- signal?.removeEventListener("abort", onAbort);
125
- reader.releaseLock();
126
- onStreamEnd();
172
+ process.kill(-child.pid, signal);
173
+ } catch (error) {
174
+ if (error.code !== "ESRCH") throw error;
127
175
  }
128
176
  };
129
- readStream(stdout, (chunk) => {
130
- stdoutData += chunk;
131
- const port = checkForPort(stdoutData);
132
- if (port) resolve(port);
133
- });
134
- readStream(stderr, (chunk) => {
135
- stderrData += chunk;
136
- const port = checkForPort(stderrData);
137
- if (port) resolve(port);
138
- });
139
- });
140
- }
141
- async function pipeStream(readable, writable, signal) {
142
- const reader = readable.getReader();
143
- const onAbort = () => void reader.cancel().catch(() => {});
144
- signal?.addEventListener("abort", onAbort, { once: true });
145
- try {
146
- while (true) {
147
- const { done, value } = await reader.read();
148
- if (done) break;
149
- writable.write(value);
150
- }
151
- } catch {} finally {
152
- signal?.removeEventListener("abort", onAbort);
153
- reader.releaseLock();
177
+ kill("SIGTERM");
178
+ await Promise.race([closed, setTimeout$1(1e3)]);
179
+ kill("SIGKILL");
154
180
  }
181
+ child.kill("SIGKILL");
182
+ await Promise.race([closed, setTimeout$1(1e3)]);
155
183
  }
156
184
  //#endregion
157
185
  export { STARTUP_TIMEOUT, serverClosure, waitForServer };
package/dist/types.d.ts CHANGED
@@ -77,6 +77,8 @@ interface MessageQueueDescription {
77
77
  object: string;
78
78
  /** Deno unstable feature flags required (e.g., `["kv"]`). */
79
79
  denoUnstable?: string[];
80
+ /** Whether this message queue is deprecated but still selectable. */
81
+ deprecated?: boolean;
80
82
  /** Environment variables required, keyed by name to default value. */
81
83
  env?: Record<string, string>;
82
84
  }
@@ -99,6 +101,8 @@ interface KvStoreDescription {
99
101
  object: string;
100
102
  /** Deno unstable feature flags required (e.g., `["kv"]`). */
101
103
  denoUnstable?: string[];
104
+ /** Whether this key-value store is deprecated but still selectable. */
105
+ deprecated?: boolean;
102
106
  /** Environment variables required, keyed by name to default value. */
103
107
  env?: Record<string, string>;
104
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/init",
3
- "version": "2.5.0-dev.2516+42d3ed0b",
3
+ "version": "2.5.0-dev.2541+d39a2437",
4
4
  "description": "Project initializer for Fedify",
5
5
  "keywords": [
6
6
  "fedify",
@@ -57,7 +57,9 @@
57
57
  "jsonc-parser": "^3.3.1"
58
58
  },
59
59
  "devDependencies": {
60
+ "@types/cross-spawn": "^6.0.6",
60
61
  "@types/node": "^24.12.2",
62
+ "cross-spawn": "^7.0.6",
61
63
  "tsdown": "^0.22.0",
62
64
  "typescript": "^6.0.0"
63
65
  },
@@ -66,7 +68,6 @@
66
68
  "build": "pnpm --filter @fedify/init... run build:self",
67
69
  "prepublish": "pnpm build",
68
70
  "pretest": "pnpm build",
69
- "test": "node --test --experimental-transform-types 'src/**/*.test.ts'",
70
- "test-init": "deno task test-init"
71
+ "test": "node --test --experimental-transform-types 'src/**/*.test.ts'"
71
72
  }
72
73
  }