@fedify/init 2.4.0-pr.936.41 → 2.4.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.
Files changed (46) hide show
  1. package/README.md +9 -5
  2. package/dist/action/configs.js +1 -1
  3. package/dist/action/deps.js +1 -1
  4. package/dist/action/mod.d.ts +1 -1
  5. package/dist/action/patch.js +4 -2
  6. package/dist/action/templates.js +14 -2
  7. package/dist/ask/dir.js +51 -19
  8. package/dist/ask/kv.js +1 -1
  9. package/dist/ask/mq.js +1 -1
  10. package/dist/ask/pm.js +44 -26
  11. package/dist/command.d.ts +5 -5
  12. package/dist/const.js +6 -2
  13. package/dist/deno.js +1 -1
  14. package/dist/json/deps.js +13 -13
  15. package/dist/json/deps.json +15 -15
  16. package/dist/json/kv.js +1 -1
  17. package/dist/json/kv.json +1 -1
  18. package/dist/json/mq.js +1 -1
  19. package/dist/json/mq.json +1 -1
  20. package/dist/json/rt.js +7 -14
  21. package/dist/json/rt.json +7 -20
  22. package/dist/lib.js +78 -2
  23. package/dist/mod.d.ts +1 -1
  24. package/dist/templates/defaults/smoke.test.ts.tpl +170 -0
  25. package/dist/templates/next/middleware.ts.tpl +7 -2
  26. package/dist/templates/sveltekit/hooks.server.ts.tpl +7 -0
  27. package/dist/test/create.js +7 -12
  28. package/dist/test/db.js +1 -1
  29. package/dist/test/lookup.js +3 -2
  30. package/dist/test/port.js +4 -4
  31. package/dist/types.d.ts +3 -1
  32. package/dist/utils.js +9 -3
  33. package/dist/webframeworks/astro.js +11 -5
  34. package/dist/webframeworks/bare-bones.js +8 -4
  35. package/dist/webframeworks/const.js +1 -1
  36. package/dist/webframeworks/elysia.js +9 -5
  37. package/dist/webframeworks/express.js +8 -4
  38. package/dist/webframeworks/hono.js +8 -4
  39. package/dist/webframeworks/mod.js +3 -1
  40. package/dist/webframeworks/next.js +10 -5
  41. package/dist/webframeworks/nitro.js +11 -4
  42. package/dist/webframeworks/nuxt.js +10 -5
  43. package/dist/webframeworks/solidstart.js +10 -5
  44. package/dist/webframeworks/sveltekit.js +82 -0
  45. package/dist/webframeworks/utils.js +15 -1
  46. package/package.json +6 -5
package/dist/lib.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { version } from "./deno.js";
2
2
  import kv_default from "./json/kv.js";
3
3
  import mq_default from "./json/mq.js";
4
- import pm_default from "./json/pm.js";
5
4
  import rt_default from "./json/rt.js";
5
+ import { RUNTIME } from "./const.js";
6
+ import pm_default from "./json/pm.js";
6
7
  import { CommandError, isNotFoundError, runSubCommand } from "./utils.js";
7
8
  import { entries, evolve, fromEntries, isObject, map, negate, pipe, throwIf } from "@fxts/core";
8
9
  import process from "node:process";
@@ -119,6 +120,81 @@ async function isCommandAvailable({ checkCommand, outputPattern }) {
119
120
  }
120
121
  }
121
122
  /**
123
+ * Compares two dotted version strings segment by segment and returns whether
124
+ * `detected` is higher than or equal to `required`.
125
+ */
126
+ function verifyRuntimeVersion(detected, required) {
127
+ const detectedParts = detected.split(".").map(Number);
128
+ const requiredParts = required.split(".").map(Number);
129
+ for (let i = 0; i < Math.max(detectedParts.length, requiredParts.length); i++) {
130
+ const detectedPart = detectedParts[i] ?? 0;
131
+ const requiredPart = requiredParts[i] ?? 0;
132
+ if (detectedPart > requiredPart) return true;
133
+ if (detectedPart < requiredPart) return false;
134
+ }
135
+ return true;
136
+ }
137
+ /**
138
+ * Runs a runtime's version command and classifies the result as `"ok"`,
139
+ * `"unsupported"`, `"missing"`, or `"malformed"` against its `minVersion`.
140
+ */
141
+ async function checkRuntimeVersion({ checkCommand, outputPattern, minVersion }) {
142
+ try {
143
+ const { stdout } = await $`${checkCommand}`.stdout("piped").spawn();
144
+ logger.debug("The stdout of the command {command} is: {stdout}", {
145
+ command: checkCommand,
146
+ stdout
147
+ });
148
+ const detected = outputPattern.exec(stdout.trim())?.[1] ?? null;
149
+ if (detected == null) return {
150
+ status: "malformed",
151
+ detected: null,
152
+ required: minVersion
153
+ };
154
+ if (!verifyRuntimeVersion(detected, minVersion)) return {
155
+ status: "unsupported",
156
+ detected,
157
+ required: minVersion
158
+ };
159
+ return {
160
+ status: "ok",
161
+ detected,
162
+ required: minVersion
163
+ };
164
+ } catch (error) {
165
+ if (isNotFoundError(error)) return {
166
+ status: "missing",
167
+ detected: null,
168
+ required: minVersion
169
+ };
170
+ logger.debug("The command {command} failed with the error: {error}", {
171
+ command: checkCommand,
172
+ error
173
+ });
174
+ throw error;
175
+ }
176
+ }
177
+ /**
178
+ * Resolves the required version for `runtime` as the higher of its base minimum
179
+ * and an optional framework `override`.
180
+ */
181
+ function resolveRequiredVersion(runtime, override) {
182
+ const base = runtimes[runtime].minVersion;
183
+ return override != null && verifyRuntimeVersion(override, base) ? override : base;
184
+ }
185
+ /**
186
+ * Checks every supported runtime once and returns a map from each runtime
187
+ * identifier to its version-check result, applying framework `overrides` on
188
+ * top of each runtime's base minimum.
189
+ */
190
+ async function checkAllRuntimes(overrides = {}) {
191
+ const checked = await Promise.all(RUNTIME.map(async (runtime) => [runtime, await checkRuntimeVersion({
192
+ ...runtimes[runtime],
193
+ minVersion: resolveRequiredVersion(runtime, overrides[runtime])
194
+ })]));
195
+ return Object.fromEntries(checked);
196
+ }
197
+ /**
122
198
  * Creates a file at the given path with the given content, creating
123
199
  * any necessary parent directories along the way.
124
200
  */
@@ -291,4 +367,4 @@ const isDirectory = async (path) => {
291
367
  /** Returns `true` if the current run is in test mode. */
292
368
  const isTest = ({ testMode }) => testMode;
293
369
  //#endregion
294
- export { PACKAGE_VERSION, createFile, getBuildCommand, getDevCommand, getInstallUrl, isDirectoryEmpty, isPackageManagerAvailable, isTest, kvStores, logger, messageQueues, packageManagers, readTemplate, runtimes, throwUnlessNotExists };
370
+ export { PACKAGE_VERSION, checkAllRuntimes, createFile, getBuildCommand, getDevCommand, getInstallUrl, isDirectoryEmpty, isPackageManagerAvailable, isTest, kvStores, logger, messageQueues, packageManagers, readTemplate, resolveRequiredVersion, runtimes, throwUnlessNotExists, verifyRuntimeVersion };
package/dist/mod.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { initCommand, initOptions } from "./command.js";
2
- import { runInit } from "./action/mod.js";
2
+ import runInit from "./action/mod.js";
3
3
  export { initCommand, initOptions, runInit };
@@ -0,0 +1,170 @@
1
+ import { getDocumentLoader } from "@fedify/fedify";
2
+ import { type Actor, isActor, lookupObject } from "@fedify/vocab";
3
+ import { spawn, spawnSync } from "node:child_process";
4
+ import type { Readable } from "node:stream";
5
+
6
+ const DEV_COMMAND: string[] = /* dev command */;
7
+ const HANDLE = "john";
8
+ const STARTUP_TIMEOUT = 15_000;
9
+ const IS_WINDOWS = process.platform === "win32";
10
+
11
+ async function main(): Promise<void> {
12
+ const [command, ...args] = DEV_COMMAND;
13
+ const server = spawn(command, args, {
14
+ stdio: ["ignore", "pipe", "pipe"],
15
+ shell: IS_WINDOWS,
16
+ windowsHide: true,
17
+ detached: !IS_WINDOWS,
18
+ });
19
+ server.on("error", () => {});
20
+
21
+ const exitOnSignal = () => {
22
+ stopServer(server);
23
+ process.exit(1);
24
+ };
25
+ process.once("SIGINT", exitOnSignal);
26
+ process.once("SIGTERM", exitOnSignal);
27
+
28
+ let output = "";
29
+ const collectOutput = (stream: Readable | null) => {
30
+ const decoder = new TextDecoder();
31
+ stream?.on("data", (chunk: Buffer) => {
32
+ output += decoder.decode(chunk, { stream: true });
33
+ });
34
+ };
35
+ collectOutput(server.stdout);
36
+ collectOutput(server.stderr);
37
+
38
+ try {
39
+ const port = await determinePort(server);
40
+ const target = `http://localhost:${port}/users/${HANDLE}`;
41
+ await waitForServer(target);
42
+ console.log(`Server is up at http://localhost:${port}.`);
43
+ const actor = await checkActor(target);
44
+ console.log(actor);
45
+ console.log(`Smoke test passed: ${target} resolved to an actor.`);
46
+ } catch (error) {
47
+ console.error("Smoke test failed:", error instanceof Error ? error.message : error);
48
+ if (output.trim() !== "") {
49
+ console.error(`\nDev server output:\n${output}`);
50
+ }
51
+ process.exitCode = 1;
52
+ } finally {
53
+ stopServer(server);
54
+ }
55
+ }
56
+
57
+ function stripEscape(text: string): string {
58
+ return text.replace(new RegExp("\\u001B\\[[0-9;]*[A-Za-z]", "g"), "");
59
+ }
60
+
61
+ function determinePort(server: ReturnType<typeof spawn>): Promise<number> {
62
+ const portPatterns = [
63
+ /listening on.*:(\d+)/i,
64
+ /server.*:(\d+)/i,
65
+ /https?:\/\/localhost:(\d+)/i,
66
+ /https?:\/\/0\.0\.0\.0:(\d+)/i,
67
+ /https?:\/\/127\.0\.0\.1:(\d+)/i,
68
+ /https?:\/\/[^:]+:(\d+)/i,
69
+ ];
70
+ return new Promise((resolve, reject) => {
71
+ const timeout = setTimeout(() => {
72
+ reject(
73
+ new Error(
74
+ `Timeout: Could not determine port from server output within ${STARTUP_TIMEOUT}ms.`,
75
+ ),
76
+ );
77
+ }, STARTUP_TIMEOUT);
78
+
79
+ const findPort = (text: string) => {
80
+ for (const pattern of portPatterns) {
81
+ const match = text.match(pattern);
82
+ if (match && match[1]) {
83
+ const port = Number.parseInt(match[1], 10);
84
+ if (port > 0 && port < 65536) return port;
85
+ }
86
+ }
87
+ return null;
88
+ };
89
+
90
+ const scan = (stream: Readable | null) => {
91
+ const decoder = new TextDecoder();
92
+ let text = "";
93
+ stream?.on("data", (chunk: Buffer) => {
94
+ text += decoder.decode(chunk, { stream: true });
95
+ const port = findPort(stripEscape(text));
96
+ if (port != null) {
97
+ clearTimeout(timeout);
98
+ resolve(port);
99
+ }
100
+ });
101
+ };
102
+
103
+ scan(server.stdout);
104
+ scan(server.stderr);
105
+ server.once("exit", (code) => {
106
+ clearTimeout(timeout);
107
+ reject(new Error(`The dev server exited early with code ${String(code)}.`));
108
+ });
109
+ });
110
+ }
111
+
112
+ async function waitForServer(url: string): Promise<void> {
113
+ const startTime = Date.now();
114
+ let lastStatus: number | undefined;
115
+
116
+ while (Date.now() - startTime < STARTUP_TIMEOUT) {
117
+ try {
118
+ const response = await fetch(url, {
119
+ headers: { Accept: "application/activity+json" },
120
+ signal: AbortSignal.timeout(1000),
121
+ });
122
+ await response.body?.cancel();
123
+ if (response.ok) return;
124
+ lastStatus = response.status;
125
+ } catch {
126
+ // Server not ready yet, continue waiting
127
+ }
128
+ await new Promise((resolve) => setTimeout(resolve, 500));
129
+ }
130
+ throw new Error(
131
+ `The server did not become ready within ${STARTUP_TIMEOUT}ms.` +
132
+ (lastStatus == null ? "" : ` Last response status: ${lastStatus}.`),
133
+ );
134
+ }
135
+
136
+ async function checkActor(url: string): Promise<Actor> {
137
+ const object = await lookupObject(url, {
138
+ documentLoader: getDocumentLoader({ allowPrivateAddress: true }),
139
+ });
140
+ if (object == null) {
141
+ throw new Error(`Could not resolve an actor at ${url}.`);
142
+ }
143
+ if (!isActor(object)) {
144
+ throw new Error(`Expected an actor at ${url}, but got a non-actor object.`);
145
+ }
146
+ return object;
147
+ }
148
+
149
+ function stopServer(server: ReturnType<typeof spawn>): void {
150
+ if (server.pid == null) return;
151
+ if (IS_WINDOWS) {
152
+ spawnSync("taskkill", ["/pid", String(server.pid), "/T", "/F"], {
153
+ stdio: "ignore",
154
+ windowsHide: true,
155
+ });
156
+ return;
157
+ }
158
+ try {
159
+ process.kill(-server.pid, "SIGKILL");
160
+ } catch {
161
+ // Process group already exited.
162
+ }
163
+ try {
164
+ server.kill("SIGKILL");
165
+ } catch {
166
+ // Process already exited.
167
+ }
168
+ }
169
+
170
+ await main();
@@ -13,8 +13,12 @@ export default fedifyWith(federation)(
13
13
  */
14
14
  );
15
15
 
16
- // This config needs because middleware process only requests with the
17
- // "Accept" header matching the federation accept regex.
16
+ // This config makes the middleware run only for requests that may be
17
+ // federation requests: requests whose "Accept" or "Content-Type" header
18
+ // has a federation media type, NodeInfo requests, and FEP-ef61 gateway
19
+ // requests such as hashlink media, which clients fetch with, e.g.,
20
+ // "Accept: image/*". fedifyWith() then decides which of them Fedify
21
+ // handles.
18
22
  // More details: https://nextjs.org/docs/app/api-reference/file-conventions/middleware#config-object-optional
19
23
  export const config = {
20
24
  runtime: "nodejs",
@@ -41,5 +45,6 @@ export const config = {
41
45
  },
42
46
  { source: "/.well-known/nodeinfo" },
43
47
  { source: "/.well-known/x-nodeinfo2" },
48
+ { source: "/.well-known/apgateway/:path*" },
44
49
  ],
45
50
  };
@@ -0,0 +1,7 @@
1
+ import { fedifyHook } from "@fedify/sveltekit";
2
+ import { sequence } from "@sveltejs/kit/hooks";
3
+ import federation from "$lib/federation";
4
+
5
+ export const handle = sequence(
6
+ fedifyHook(federation),
7
+ );
@@ -74,26 +74,21 @@ const saveOutputs = async (dirPath, { stdout, stderr }) => {
74
74
  if (stderr) await appendFile(join$1(dirPath, "err.txt"), stderr + "\n", "utf8");
75
75
  };
76
76
  async function validateDevToolScripts(dir, options) {
77
- const [webFramework, packageManager] = options;
77
+ const [_, packageManager] = options;
78
78
  if (packageManager === "deno") return true;
79
79
  if (!await hasInstalledNodeDependencies(dir)) return true;
80
- if (webFramework === "astro") {
81
- const format = await $`${[
82
- packageManager,
83
- "run",
84
- "format"
85
- ]}`.cwd(dir).stdin("null").stdout("piped").stderr("piped").noThrow().spawn();
86
- await saveOutputs(dir, format);
87
- if (format.code !== 0) return false;
88
- }
89
- for (const script of ["format:check", "lint"]) {
80
+ for (const script of [
81
+ "format",
82
+ "format:check",
83
+ "lint"
84
+ ]) {
90
85
  const result = await $`${[
91
86
  packageManager,
92
87
  "run",
93
88
  script
94
89
  ]}`.cwd(dir).stdin("null").stdout("piped").stderr("piped").noThrow().spawn();
95
90
  await saveOutputs(dir, result);
96
- if (result.code !== 0) return false;
91
+ if (result.code !== 0) printMessage` Warning: ${script} failed, continuing anyway.`;
97
92
  }
98
93
  return true;
99
94
  }
package/dist/test/db.js CHANGED
@@ -1,5 +1,5 @@
1
- import { printErrorMessage, printMessage } from "../utils.js";
2
1
  import { DB_TO_CHECK } from "../const.js";
2
+ import { printErrorMessage, printMessage } from "../utils.js";
3
3
  import db_to_check_default from "../json/db-to-check.js";
4
4
  import { concat, filter, pipe, toArray, uniq } from "@fxts/core";
5
5
  import { createConnection } from "node:net";
@@ -13,7 +13,8 @@ const BANNED_LOOKUP_REASONS = {
13
13
  "next,*,*,*": "Next.js doesn't support remote packages",
14
14
  "solidstart,deno,*,*": "Error occurred while loading submodules in Deno",
15
15
  "astro,deno,*,*": "Astro doesn't support remote packages in Deno",
16
- "nuxt,deno,*,*": "Nuxt doesn't support remote packages in Deno"
16
+ "nuxt,deno,*,*": "Nuxt doesn't support remote packages in Deno",
17
+ "sveltekit,deno,*,*": "SvelteKit doesn't support remote packages in Deno"
17
18
  };
18
19
  const BANNED_LOOKUP_CASES = Object.keys(BANNED_LOOKUP_REASONS).map((key) => key.split(","));
19
20
  /**
@@ -109,4 +110,4 @@ async function sendLookup(port) {
109
110
  return false;
110
111
  }
111
112
  //#endregion
112
- export { runServerAndLookupUser as default };
113
+ export { runServerAndLookupUser as default, isTestable, matchesLookupCasePattern, parseLookupCase };
package/dist/test/port.js CHANGED
@@ -90,7 +90,7 @@ const ENTRY_FILES = {
90
90
  hono: "src/index.ts",
91
91
  elysia: "src/index.ts"
92
92
  };
93
- const WF_READ_PORT_FROM_ENV = new Set([
93
+ const WF_READ_PORT_FROM_ENV = /* @__PURE__ */ new Set([
94
94
  "nuxt",
95
95
  "nitro",
96
96
  "solidstart"
@@ -108,8 +108,8 @@ async function replacePortInApp(dir, wf, defaultPort, newPort) {
108
108
  await writeFile(filePath, (await readFile(filePath, "utf8")).replaceAll(String(defaultPort), String(newPort)));
109
109
  return;
110
110
  }
111
- if (wf === "astro") {
112
- const configPath = join(dir, "astro.config.ts");
111
+ if (wf === "astro" || wf === "sveltekit") {
112
+ const configPath = join(dir, wf === "astro" ? "astro.config.ts" : "vite.config.ts");
113
113
  await writeFile(configPath, (await readFile(configPath, "utf8")).replace("defineConfig({", `defineConfig({\n server: { port: ${newPort} },`));
114
114
  return;
115
115
  }
@@ -127,4 +127,4 @@ async function ensurePortReleased(port) {
127
127
  }
128
128
  }
129
129
  //#endregion
130
- export { ensurePortReleased, killProcessOnPort, replacePortInApp, reservePort };
130
+ export { ensurePortReleased, isPortInUse, killProcessOnPort, replacePortInApp, reservePort, waitForPortRelease };
package/dist/types.d.ts CHANGED
@@ -30,6 +30,8 @@ interface WebFrameworkInitializer {
30
30
  federationFile: string;
31
31
  /** Relative path where the logging configuration file will be created. */
32
32
  loggingFile: string;
33
+ /** Relative path where the smoke-test script file will be created. */
34
+ testFile: string;
33
35
  /** Optional template path for the logging configuration file. */
34
36
  loggingTemplate?: string;
35
37
  /**
@@ -125,4 +127,4 @@ interface InitCommandData extends InitCommandOptions {
125
127
  readonly env: Record<string, string>;
126
128
  }
127
129
  //#endregion
128
- export { InitCommandData };
130
+ export { InitCommandData, InitCommandOptions, KvStoreDescription, MessageQueueDescription, PackageManager, WebFrameworkInitializer };
package/dist/utils.js CHANGED
@@ -5,8 +5,14 @@ import { flow, toMerged } from "es-toolkit";
5
5
  import { message } from "@optique/core";
6
6
  import { Chalk } from "chalk";
7
7
  import { spawn } from "node:child_process";
8
+ //#region src/utils.ts
9
+ /**
10
+ * Whether terminal color output is enabled.
11
+ * `true` when stdout is a TTY and the `NO_COLOR` environment variable is not set.
12
+ */
13
+ const colorEnabled = process.stdout.isTTY && !("NO_COLOR" in process.env && process.env.NO_COLOR !== "");
8
14
  /** Chalk instance configured based on {@link colorEnabled}. */
9
- const colors = new Chalk(process.stdout.isTTY && !("NO_COLOR" in process.env && process.env.NO_COLOR !== "") ? {} : { level: 0 });
15
+ const colors = new Chalk(colorEnabled ? {} : { level: 0 });
10
16
  /** Type guard that checks whether a value is a `Promise`. */
11
17
  const isPromise = (value) => value instanceof Promise;
12
18
  /**
@@ -59,7 +65,7 @@ const createJsonDepthGuard = () => {
59
65
  /** Checks whether a string or array-like value has a length greater than zero. */
60
66
  const notEmpty = (s) => s.length > 0;
61
67
  /** Type guard that checks whether an error is a "file not found" (`ENOENT`) error. */
62
- const isNotFoundError = (e) => isObject(e) && "code" in e && e.code === "ENOENT";
68
+ const isNotFoundError = (e) => isObject(e) && ("code" in e && e.code === "ENOENT" || "exitCode" in e && e.exitCode === 127);
63
69
  /**
64
70
  * Error thrown when a spawned shell command exits with a non-zero code.
65
71
  * Captures stdout, stderr, exit code, and the original command array.
@@ -160,4 +166,4 @@ const printMessage = flow(message, print);
160
166
  /** Prints a formatted error message to stderr using `@optique/run`'s `printError`. */
161
167
  const printErrorMessage = flow(message, printError);
162
168
  //#endregion
163
- export { CommandError, colors, formatJson, getCwd, getOsType, isNotFoundError, merge$1 as merge, notEmpty, printErrorMessage, printMessage, product, replace, runSubCommand, set };
169
+ export { CommandError, colorEnabled, colors, formatJson, getCwd, getOsType, isNotFoundError, isPromise, merge$1 as merge, notEmpty, printErrorMessage, printMessage, product, replace, runSubCommand, set };
@@ -1,8 +1,8 @@
1
- import { PACKAGE_VERSION, readTemplate } from "../lib.js";
2
1
  import { PACKAGE_MANAGER } from "../const.js";
3
- import { _logtape_logtape, npm__astrojs_node, npm__deno_astro_adapter, npm__dotenvx_dotenvx, npm__types_node_22, npm_astro, npm_create_astro, npm_oxlint, npm_prettier, npm_prettier_plugin_astro, npm_typescript } from "../json/deps.js";
2
+ import { PACKAGE_VERSION, readTemplate } from "../lib.js";
3
+ import { "@logtape/logtape" as _logtape_logtape, "npm:@astrojs/node" as npm__astrojs_node, "npm:@deno/astro-adapter" as npm__deno_astro_adapter, "npm:@dotenvx/dotenvx" as npm__dotenvx_dotenvx, "npm:@types/node@22" as npm__types_node_22, "npm:astro" as npm_astro, "npm:create-astro" as npm_create_astro, "npm:oxlint" as npm_oxlint, "npm:prettier" as npm_prettier, "npm:prettier-plugin-astro" as npm_prettier_plugin_astro, "npm:typescript" as npm_typescript } from "../json/deps.js";
4
4
  import { defaultDenoDependencies } from "./const.js";
5
- import { getInstruction, pmToRt } from "./utils.js";
5
+ import { getInstruction, getTestDependencies, getTestTask, pmToRt } from "./utils.js";
6
6
  //#region src/webframeworks/astro.ts
7
7
  const astroNodeBunDevDependencies = {
8
8
  "@fedify/lint": PACKAGE_VERSION,
@@ -27,6 +27,7 @@ const astroDescription = {
27
27
  label: "Astro",
28
28
  packageManagers: PACKAGE_MANAGER,
29
29
  defaultPort: 4321,
30
+ minRuntimeVersions: { node: "22.12.0" },
30
31
  init: async ({ packageManager: pm }) => {
31
32
  const dependencies = pm === "deno" ? {
32
33
  ...defaultDenoDependencies,
@@ -54,10 +55,12 @@ const astroDescription = {
54
55
  ...pm !== "deno" ? {
55
56
  typescript: npm_typescript,
56
57
  "@types/node": npm__types_node_22
57
- } : {}
58
+ } : {},
59
+ ...getTestDependencies(pm)
58
60
  },
59
61
  federationFile: "src/federation.ts",
60
62
  loggingFile: "src/logging.ts",
63
+ testFile: "scripts/smoke.test.ts",
61
64
  format: pm === "deno" ? void 0 : { tool: "prettier" },
62
65
  files: {
63
66
  "astro.config.ts": await readTemplate(`astro/astro.config.${pmToRt(pm)}.ts`),
@@ -106,18 +109,21 @@ const TASKS = {
106
109
  "deno": {
107
110
  dev: `${astroDenoCommand} dev`,
108
111
  build: `${astroDenoCommand} build`,
109
- preview: `${astroDenoCommand} preview`
112
+ preview: `${astroDenoCommand} preview`,
113
+ test: getTestTask("deno")
110
114
  },
111
115
  "bun": {
112
116
  dev: "bunx --bun astro dev",
113
117
  build: "bunx --bun astro build",
114
118
  preview: "bun ./dist/server/entry.mjs",
119
+ test: getTestTask("bun"),
115
120
  ...astroNodeBunDevToolTasks
116
121
  },
117
122
  "node": {
118
123
  dev: "dotenvx run -- astro dev",
119
124
  build: "dotenvx run -- astro build",
120
125
  preview: "dotenvx run -- astro preview",
126
+ test: getTestTask("npm"),
121
127
  ...astroNodeBunDevToolTasks
122
128
  }
123
129
  };
@@ -1,8 +1,8 @@
1
- import { readTemplate } from "../lib.js";
2
1
  import { PACKAGE_MANAGER } from "../const.js";
3
- import { _hongminhee_x_forwarded_fetch, npm__dotenvx_dotenvx, npm__hono_node_server, npm__types_bun, npm__types_node_25, npm_tsx, npm_x_forwarded_fetch } from "../json/deps.js";
2
+ import { readTemplate } from "../lib.js";
3
+ import { "@hongminhee/x-forwarded-fetch" as _hongminhee_x_forwarded_fetch, "npm:@dotenvx/dotenvx" as npm__dotenvx_dotenvx, "npm:@hono/node-server" as npm__hono_node_server, "npm:@types/bun" as npm__types_bun, "npm:@types/node@25" as npm__types_node_25, "npm:tsx" as npm_tsx, "npm:x-forwarded-fetch" as npm_x_forwarded_fetch } from "../json/deps.js";
4
4
  import { defaultDenoDependencies, defaultDevDependencies } from "./const.js";
5
- import { getInstruction, nodeBunDevToolTasks, pmToRt } from "./utils.js";
5
+ import { getInstruction, getTestTask, nodeBunDevToolTasks, pmToRt } from "./utils.js";
6
6
  //#region src/webframeworks/bare-bones.ts
7
7
  const bareBonesDescription = {
8
8
  label: "Bare-bones",
@@ -16,6 +16,7 @@ const bareBonesDescription = {
16
16
  },
17
17
  federationFile: "src/federation.ts",
18
18
  loggingFile: "src/logging.ts",
19
+ testFile: "scripts/smoke.test.ts",
19
20
  files: { "src/main.ts": await readTemplate(`bare-bones/main/${pmToRt(pm)}.ts`) },
20
21
  compilerOptions: pm === "deno" ? {
21
22
  "jsx": "precompile",
@@ -46,16 +47,19 @@ const getDependencies = (pm) => pm === "deno" ? {
46
47
  const TASKS = {
47
48
  deno: {
48
49
  dev: "deno run -A --watch ./src/main.ts",
49
- prod: "deno run -A ./src/main.ts"
50
+ prod: "deno run -A ./src/main.ts",
51
+ test: getTestTask("deno")
50
52
  },
51
53
  bun: {
52
54
  dev: "bun run --hot ./src/main.ts",
53
55
  prod: "bun run ./src/main.ts",
56
+ test: getTestTask("bun"),
54
57
  ...nodeBunDevToolTasks
55
58
  },
56
59
  node: {
57
60
  dev: "dotenvx run -- tsx watch ./src/main.ts",
58
61
  prod: "dotenvx run -- node --import tsx ./src/main.ts",
62
+ test: getTestTask("npm"),
59
63
  ...nodeBunDevToolTasks
60
64
  }
61
65
  };
@@ -1,5 +1,5 @@
1
1
  import { PACKAGE_VERSION } from "../lib.js";
2
- import { npm_oxfmt, npm_oxlint } from "../json/deps.js";
2
+ import { "npm:oxfmt" as npm_oxfmt, "npm:oxlint" as npm_oxlint } from "../json/deps.js";
3
3
  //#region src/webframeworks/const.ts
4
4
  const defaultDevDependencies = {
5
5
  "@fedify/lint": PACKAGE_VERSION,
@@ -1,11 +1,11 @@
1
- import { PACKAGE_VERSION, readTemplate } from "../lib.js";
2
1
  import { PACKAGE_MANAGER } from "../const.js";
3
- import { npm__dotenvx_dotenvx, npm__elysiajs_node, npm__sinclair_typebox, npm__types_bun, npm__types_node_25, npm_elysia, npm_openapi_types, npm_tsx, npm_typescript } from "../json/deps.js";
2
+ import { PACKAGE_VERSION, readTemplate } from "../lib.js";
3
+ import { "npm:@dotenvx/dotenvx" as npm__dotenvx_dotenvx, "npm:@elysiajs/node" as npm__elysiajs_node, "npm:@sinclair/typebox" as npm__sinclair_typebox, "npm:@types/bun" as npm__types_bun, "npm:@types/node@25" as npm__types_node_25, "npm:elysia" as npm_elysia, "npm:openapi-types" as npm_openapi_types, "npm:tsx" as npm_tsx, "npm:typescript" as npm_typescript } from "../json/deps.js";
4
4
  import { defaultDenoDependencies, defaultDevDependencies } from "./const.js";
5
- import { getInstruction, nodeBunDevToolTasks, pmToRt } from "./utils.js";
5
+ import { getInstruction, getTestTask, nodeBunDevToolTasks, pmToRt } from "./utils.js";
6
6
  //#region src/webframeworks/elysia.ts
7
7
  const elysiaDescription = {
8
- label: "ElysiaJS",
8
+ label: "Elysia",
9
9
  packageManagers: PACKAGE_MANAGER,
10
10
  defaultPort: 3e3,
11
11
  init: async ({ projectName, packageManager: pm }) => ({
@@ -36,6 +36,7 @@ const elysiaDescription = {
36
36
  },
37
37
  federationFile: "src/federation.ts",
38
38
  loggingFile: "src/logging.ts",
39
+ testFile: "scripts/smoke.test.ts",
39
40
  files: { "src/index.ts": (await readTemplate(`elysia/index/${pmToRt(pm)}.ts`)).replace(/\/\* logger \*\//, projectName) },
40
41
  compilerOptions: pm === "deno" || pm === "bun" ? void 0 : {
41
42
  "lib": ["ESNext", "DOM"],
@@ -54,17 +55,20 @@ const elysiaDescription = {
54
55
  const TASKS = {
55
56
  deno: {
56
57
  dev: "deno serve --allow-read --allow-env --allow-net --watch ./src/index.ts",
57
- prod: "deno serve --allow-read --allow-env --allow-net ./src/index.ts"
58
+ prod: "deno serve --allow-read --allow-env --allow-net ./src/index.ts",
59
+ test: getTestTask("deno")
58
60
  },
59
61
  bun: {
60
62
  dev: "bun run --hot ./src/index.ts",
61
63
  prod: "bun run ./src/index.ts",
64
+ test: getTestTask("bun"),
62
65
  ...nodeBunDevToolTasks
63
66
  },
64
67
  node: {
65
68
  dev: "dotenvx run -- tsx watch src/index.ts",
66
69
  build: "tsc src/index.ts --outDir dist",
67
70
  start: "NODE_ENV=production dotenvx run -- node dist/index.js",
71
+ test: getTestTask("npm"),
68
72
  ...nodeBunDevToolTasks
69
73
  }
70
74
  };
@@ -1,8 +1,8 @@
1
- import { PACKAGE_VERSION, readTemplate } from "../lib.js";
2
1
  import { PACKAGE_MANAGER } from "../const.js";
3
- import { npm__dotenvx_dotenvx, npm__types_bun, npm__types_express, npm_express, npm_tsx } from "../json/deps.js";
2
+ import { PACKAGE_VERSION, readTemplate } from "../lib.js";
3
+ import { "npm:@dotenvx/dotenvx" as npm__dotenvx_dotenvx, "npm:@types/bun" as npm__types_bun, "npm:@types/express" as npm__types_express, "npm:express" as npm_express, "npm:tsx" as npm_tsx } from "../json/deps.js";
4
4
  import { defaultDenoDependencies, defaultDevDependencies } from "./const.js";
5
- import { getInstruction, nodeBunDevToolTasks, pmToRt } from "./utils.js";
5
+ import { getInstruction, getTestTask, nodeBunDevToolTasks, pmToRt } from "./utils.js";
6
6
  //#region src/webframeworks/express.ts
7
7
  const expressDescription = {
8
8
  label: "Express",
@@ -25,6 +25,7 @@ const expressDescription = {
25
25
  },
26
26
  federationFile: "src/federation.ts",
27
27
  loggingFile: "src/logging.ts",
28
+ testFile: "scripts/smoke.test.ts",
28
29
  files: {
29
30
  "src/app.ts": (await readTemplate("express/app.ts")).replace(/\/\* logger \*\//, projectName),
30
31
  "src/index.ts": await readTemplate("express/index.ts")
@@ -46,16 +47,19 @@ const expressDescription = {
46
47
  const TASKS = {
47
48
  deno: {
48
49
  dev: "deno run --allow-read --allow-net --allow-env --allow-sys --watch ./src/index.ts",
49
- prod: "deno run --allow-read --allow-net --allow-env --allow-sys ./src/index.ts"
50
+ prod: "deno run --allow-read --allow-net --allow-env --allow-sys ./src/index.ts",
51
+ test: getTestTask("deno")
50
52
  },
51
53
  bun: {
52
54
  dev: "bun run --hot ./src/index.ts",
53
55
  prod: "bun run ./src/index.ts",
56
+ test: getTestTask("bun"),
54
57
  ...nodeBunDevToolTasks
55
58
  },
56
59
  node: {
57
60
  dev: "dotenvx run -- tsx watch ./src/index.ts",
58
61
  prod: "dotenvx run -- node --import tsx ./src/index.ts",
62
+ test: getTestTask("npm"),
59
63
  ...nodeBunDevToolTasks
60
64
  }
61
65
  };