@fedify/init 2.4.0-dev.1805 → 2.4.0-dev.1832

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.
@@ -3,7 +3,7 @@ import { createFile, throwUnlessNotExists } from "../lib.js";
3
3
  import { displayFile, noticeFilesToCreate, noticeFilesToInsert } from "./notice.js";
4
4
  import { joinDir, stringifyEnvs } from "./utils.js";
5
5
  import { devToolConfigs, loadDenoConfig, loadOxfmtConfig, loadOxlintConfig, loadPackageJson, loadTsConfig, loadVscodeExtensions, loadVscodeSettings } from "./configs.js";
6
- import { getImports, loadFederation, loadLogging } from "./templates.js";
6
+ import { getImports, loadFederation, loadLogging, loadTest } from "./templates.js";
7
7
  import { always, apply, entries, map, pipe, pipeLazy, tap } from "@fxts/core";
8
8
  import { toMerged } from "es-toolkit";
9
9
  import { access, readFile } from "node:fs/promises";
@@ -80,6 +80,7 @@ const getFiles = async (data) => ({
80
80
  ...data
81
81
  }),
82
82
  [data.initializer.loggingFile]: await loadLogging(data),
83
+ [data.initializer.testFile]: await loadTest(data),
83
84
  ".env": stringifyEnvs(data.env),
84
85
  ...data.initializer.files
85
86
  });
@@ -119,6 +120,7 @@ const getJsons = (data) => {
119
120
  const getGeneratedFilePaths = (data) => [
120
121
  data.initializer.federationFile,
121
122
  data.initializer.loggingFile,
123
+ data.initializer.testFile,
122
124
  ".env",
123
125
  ...Object.keys(data.initializer.files ?? {}),
124
126
  ...Object.keys(getJsons(data)),
@@ -1,5 +1,5 @@
1
1
  import { replace } from "../utils.js";
2
- import { readTemplate } from "../lib.js";
2
+ import { getDevCommand, readTemplate } from "../lib.js";
3
3
  import { needsDenoDotenv } from "./utils.js";
4
4
  import { concat, entries, join, map, pipe, when } from "@fxts/core";
5
5
  import { toMerged } from "es-toolkit";
@@ -30,6 +30,18 @@ const getFederationTemplate = (packageManager) => packageManager === "deno" ? "d
30
30
  */
31
31
  const loadLogging = async ({ projectName, initializer }) => pipe(await readTemplate(initializer.loggingTemplate ?? "defaults/logging.ts"), replace(/\/\* project name \*\//, JSON.stringify(projectName)));
32
32
  /**
33
+ * Loads the smoke-test script content for the initializer.
34
+ *
35
+ * Every framework shares the same *defaults/smoke.test.ts* template, so unlike
36
+ * {@link loadLogging} there is no per-framework template override. The
37
+ * template spawns the project's own dev server, so it needs the dev command
38
+ * for the chosen package manager baked in at generation time.
39
+ *
40
+ * @param param0 - {@link InitCommandData} containing `packageManager`
41
+ * @returns The complete smoke-test script content as a string
42
+ */
43
+ const loadTest = async ({ packageManager }) => pipe(await readTemplate("defaults/smoke.test.ts"), replace(/\/\* dev command \*\//, JSON.stringify(getDevCommand(packageManager).split(" ")).replaceAll(",", ", ")));
44
+ /**
33
45
  * Generates import statements for KV store and message queue dependencies.
34
46
  * Merges imports from both KV and MQ configurations and creates proper
35
47
  * ES module import syntax.
@@ -75,4 +87,4 @@ const ENV_REG_EXP = /process\.env\.(\w+)/g;
75
87
  */
76
88
  const convertEnv = (obj, pm) => pm === "deno" && ENV_REG_EXP.test(obj) ? obj.replaceAll(ENV_REG_EXP, (_, g1) => `Deno.env.get("${g1}")`) : obj;
77
89
  //#endregion
78
- export { convertEnv, getAlias, getImports, loadFederation, loadLogging };
90
+ export { convertEnv, getAlias, getImports, loadFederation, loadLogging, loadTest };
package/dist/deno.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region deno.json
2
- var version = "2.4.0-dev.1805+bf6d5258";
2
+ var version = "2.4.0-dev.1832+1af9355c";
3
3
  //#endregion
4
4
  export { version };
@@ -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();
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
  /**
@@ -2,7 +2,7 @@ import { PACKAGE_MANAGER } from "../const.js";
2
2
  import { PACKAGE_VERSION, readTemplate } from "../lib.js";
3
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,
@@ -55,10 +55,12 @@ const astroDescription = {
55
55
  ...pm !== "deno" ? {
56
56
  typescript: npm_typescript,
57
57
  "@types/node": npm__types_node_22
58
- } : {}
58
+ } : {},
59
+ ...getTestDependencies(pm)
59
60
  },
60
61
  federationFile: "src/federation.ts",
61
62
  loggingFile: "src/logging.ts",
63
+ testFile: "scripts/smoke.test.ts",
62
64
  format: pm === "deno" ? void 0 : { tool: "prettier" },
63
65
  files: {
64
66
  "astro.config.ts": await readTemplate(`astro/astro.config.${pmToRt(pm)}.ts`),
@@ -107,18 +109,21 @@ const TASKS = {
107
109
  "deno": {
108
110
  dev: `${astroDenoCommand} dev`,
109
111
  build: `${astroDenoCommand} build`,
110
- preview: `${astroDenoCommand} preview`
112
+ preview: `${astroDenoCommand} preview`,
113
+ test: getTestTask("deno")
111
114
  },
112
115
  "bun": {
113
116
  dev: "bunx --bun astro dev",
114
117
  build: "bunx --bun astro build",
115
118
  preview: "bun ./dist/server/entry.mjs",
119
+ test: getTestTask("bun"),
116
120
  ...astroNodeBunDevToolTasks
117
121
  },
118
122
  "node": {
119
123
  dev: "dotenvx run -- astro dev",
120
124
  build: "dotenvx run -- astro build",
121
125
  preview: "dotenvx run -- astro preview",
126
+ test: getTestTask("npm"),
122
127
  ...astroNodeBunDevToolTasks
123
128
  }
124
129
  };
@@ -2,7 +2,7 @@ import { PACKAGE_MANAGER } from "../const.js";
2
2
  import { readTemplate } from "../lib.js";
3
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
  };
@@ -2,7 +2,7 @@ import { PACKAGE_MANAGER } from "../const.js";
2
2
  import { PACKAGE_VERSION, readTemplate } from "../lib.js";
3
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
8
  label: "Elysia",
@@ -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
  };
@@ -2,7 +2,7 @@ import { PACKAGE_MANAGER } from "../const.js";
2
2
  import { PACKAGE_VERSION, readTemplate } from "../lib.js";
3
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
  };
@@ -3,7 +3,7 @@ import { replace } from "../utils.js";
3
3
  import { PACKAGE_VERSION, readTemplate } from "../lib.js";
4
4
  import { "@hongminhee/x-forwarded-fetch" as _hongminhee_x_forwarded_fetch, "@hono/hono" as _hono_hono, "npm:@dotenvx/dotenvx" as npm__dotenvx_dotenvx, "npm:@hono/node-server" as npm__hono_node_server, "npm:@types/bun" as npm__types_bun, "npm:hono" as npm_hono, "npm:tsx" as npm_tsx, "npm:x-forwarded-fetch" as npm_x_forwarded_fetch } from "../json/deps.js";
5
5
  import { defaultDenoDependencies, defaultDevDependencies } from "./const.js";
6
- import { getInstruction, nodeBunDevToolTasks, pmToRt } from "./utils.js";
6
+ import { getInstruction, getTestTask, nodeBunDevToolTasks, pmToRt } from "./utils.js";
7
7
  import { pipe } from "@fxts/core";
8
8
  //#region src/webframeworks/hono.ts
9
9
  const honoDescription = {
@@ -18,6 +18,7 @@ const honoDescription = {
18
18
  },
19
19
  federationFile: "src/federation.ts",
20
20
  loggingFile: "src/logging.ts",
21
+ testFile: "scripts/smoke.test.ts",
21
22
  files: {
22
23
  "src/app.tsx": pipe(await readTemplate("hono/app.tsx"), replace(/\/\* hono \*\//, pm === "deno" ? "@hono/hono" : "hono"), replace(/\/\* logger \*\//, projectName)),
23
24
  "src/index.ts": await readTemplate(`hono/index/${pmToRt(pm)}.ts`)
@@ -58,16 +59,19 @@ const getDependencies = (pm) => pm === "deno" ? {
58
59
  const TASKS = {
59
60
  deno: {
60
61
  dev: "deno run -A --watch ./src/index.ts",
61
- prod: "deno run -A ./src/index.ts"
62
+ prod: "deno run -A ./src/index.ts",
63
+ test: getTestTask("deno")
62
64
  },
63
65
  bun: {
64
66
  dev: "bun run --hot ./src/index.ts",
65
67
  prod: "bun run ./src/index.ts",
68
+ test: getTestTask("bun"),
66
69
  ...nodeBunDevToolTasks
67
70
  },
68
71
  node: {
69
72
  dev: "dotenvx run -- tsx watch ./src/index.ts",
70
73
  prod: "dotenvx run -- node --import tsx ./src/index.ts",
74
+ test: getTestTask("npm"),
71
75
  ...nodeBunDevToolTasks
72
76
  }
73
77
  };
@@ -2,7 +2,7 @@ import { PACKAGE_MANAGER } from "../const.js";
2
2
  import { PACKAGE_VERSION, readTemplate } from "../lib.js";
3
3
  import { "npm:@types/node@20" as npm__types_node_20 } from "../json/deps.js";
4
4
  import { defaultDenoDependencies, defaultDevDependencies } from "./const.js";
5
- import { getInstruction, getNodeBunDevToolTasks } from "./utils.js";
5
+ import { getInstruction, getNodeBunDevToolTasks, getTestDependencies, getTestTask } from "./utils.js";
6
6
  //#region src/webframeworks/next.ts
7
7
  const nextDescription = {
8
8
  label: "Next.js",
@@ -21,16 +21,21 @@ const nextDescription = {
21
21
  },
22
22
  devDependencies: {
23
23
  "@types/node": npm__types_node_20,
24
- ...defaultDevDependencies
24
+ ...defaultDevDependencies,
25
+ ...getTestDependencies(pm)
25
26
  },
26
27
  federationFile: "federation/index.ts",
27
28
  loggingFile: "logging.ts",
29
+ testFile: "scripts/smoke.test.ts",
28
30
  format: { ignorePatterns: [".next/**"] },
29
31
  files: {
30
32
  "instrumentation.ts": await readTemplate("next/instrumentation.ts"),
31
33
  "middleware.ts": await readTemplate("next/middleware.ts")
32
34
  },
33
- tasks: getNodeBunDevToolTasks(pm),
35
+ tasks: {
36
+ ...getNodeBunDevToolTasks(pm),
37
+ test: getTestTask(pm)
38
+ },
34
39
  instruction: getInstruction(pm, 3e3)
35
40
  })
36
41
  };
@@ -1,7 +1,7 @@
1
1
  import { PACKAGE_MANAGER } from "../const.js";
2
2
  import { PACKAGE_VERSION, readTemplate } from "../lib.js";
3
3
  import { defaultDenoDependencies, defaultDevDependencies } from "./const.js";
4
- import { getInstruction, getNodeBunDevToolTasks } from "./utils.js";
4
+ import { getInstruction, getNodeBunDevToolTasks, getTestDependencies, getTestTask } from "./utils.js";
5
5
  //#region src/webframeworks/nitro.ts
6
6
  const nitroDescription = {
7
7
  label: "Nitro",
@@ -19,9 +19,13 @@ const nitroDescription = {
19
19
  "@fedify/h3": PACKAGE_VERSION,
20
20
  ...pm === "deno" && defaultDenoDependencies
21
21
  },
22
- devDependencies: defaultDevDependencies,
22
+ devDependencies: {
23
+ ...defaultDevDependencies,
24
+ ...getTestDependencies(pm)
25
+ },
23
26
  federationFile: "server/federation.ts",
24
27
  loggingFile: "server/logging.ts",
28
+ testFile: "scripts/smoke.test.ts",
25
29
  format: { ignorePatterns: [".output/**"] },
26
30
  env: testMode ? { HOST: "127.0.0.1" } : {},
27
31
  files: {
@@ -51,7 +55,10 @@ const nitroDescription = {
51
55
  lib: ["ESNext", "DOM"],
52
56
  baseUrl: "."
53
57
  },
54
- tasks: getNodeBunDevToolTasks(pm),
58
+ tasks: {
59
+ ...getNodeBunDevToolTasks(pm),
60
+ test: getTestTask(pm)
61
+ },
55
62
  instruction: getInstruction(pm, 3e3)
56
63
  })
57
64
  };
@@ -2,7 +2,7 @@ import { PACKAGE_MANAGER } from "../const.js";
2
2
  import { PACKAGE_VERSION, readTemplate } from "../lib.js";
3
3
  import { "npm:@types/node@25" as npm__types_node_25, "npm:typescript" as npm_typescript } from "../json/deps.js";
4
4
  import { defaultDenoDependencies, defaultDevDependencies } from "./const.js";
5
- import { getInstruction, getNodeBunDevToolTasks } from "./utils.js";
5
+ import { getInstruction, getNodeBunDevToolTasks, getTestDependencies, getTestTask } from "./utils.js";
6
6
  //#region src/webframeworks/nuxt.ts
7
7
  const nuxtDescription = {
8
8
  label: "Nuxt",
@@ -14,18 +14,23 @@ const nuxtDescription = {
14
14
  devDependencies: {
15
15
  ...defaultDevDependencies,
16
16
  "typescript": npm_typescript,
17
- "@types/node": npm__types_node_25
17
+ "@types/node": npm__types_node_25,
18
+ ...getTestDependencies(pm)
18
19
  },
19
20
  federationFile: "server/federation.ts",
20
21
  loggingFile: "server/logging.ts",
21
22
  loggingTemplate: "nuxt/server/logging.ts",
23
+ testFile: "scripts/smoke.test.ts",
22
24
  format: { ignorePatterns: [".output/**"] },
23
25
  env: testMode ? { HOST: "127.0.0.1" } : {},
24
26
  files: {
25
27
  "nuxt.config.ts": await readTemplate("nuxt/nuxt.config.ts"),
26
28
  "server/plugins/logging.ts": await readTemplate("nuxt/server/plugins/logging.ts")
27
29
  },
28
- tasks: getNodeBunDevToolTasks(pm),
30
+ tasks: {
31
+ ...getNodeBunDevToolTasks(pm),
32
+ test: getTestTask(pm)
33
+ },
29
34
  instruction: getInstruction(pm, 3e3)
30
35
  })
31
36
  };
@@ -2,7 +2,7 @@ import { PACKAGE_MANAGER } from "../const.js";
2
2
  import { PACKAGE_VERSION, readTemplate } from "../lib.js";
3
3
  import { "npm:@dotenvx/dotenvx" as npm__dotenvx_dotenvx, "npm:@solidjs/router" as npm__solidjs_router, "npm:@solidjs/start" as npm__solidjs_start, "npm:@types/node@22" as npm__types_node_22, "npm:solid-js" as npm_solid_js, "npm:typescript" as npm_typescript, "npm:vinxi" as npm_vinxi } from "../json/deps.js";
4
4
  import { defaultDenoDependencies, defaultDevDependencies } from "./const.js";
5
- import { getInstruction, nodeBunDevToolTasks, pmToRt } from "./utils.js";
5
+ import { getInstruction, getTestDependencies, getTestTask, nodeBunDevToolTasks, pmToRt } from "./utils.js";
6
6
  //#region src/webframeworks/solidstart.ts
7
7
  const NPM_SOLIDSTART = `npm:@solidjs/start@${npm__solidjs_start}`;
8
8
  const solidstartDescription = {
@@ -14,10 +14,12 @@ const solidstartDescription = {
14
14
  devDependencies: {
15
15
  ...defaultDevDependencies,
16
16
  typescript: npm_typescript,
17
- "@types/node": npm__types_node_22
17
+ "@types/node": npm__types_node_22,
18
+ ...getTestDependencies(pm)
18
19
  },
19
20
  federationFile: "src/federation.ts",
20
21
  loggingFile: "src/logging.ts",
22
+ testFile: "scripts/smoke.test.ts",
21
23
  format: { ignorePatterns: [".solid/**", ".vinxi/**"] },
22
24
  files: {
23
25
  "app.config.ts": (await readTemplate("solidstart/app.config.ts")).replace(/\/\* preset \*\//, pm === "deno" ? "deno-server" : "node-server"),
@@ -70,18 +72,21 @@ const TASKS = {
70
72
  deno: {
71
73
  dev: "deno run -A npm:vinxi dev",
72
74
  build: "deno run -A npm:vinxi build",
73
- start: "deno run -A npm:vinxi start"
75
+ start: "deno run -A npm:vinxi start",
76
+ test: getTestTask("deno")
74
77
  },
75
78
  bun: {
76
79
  dev: "bunx vinxi dev",
77
80
  build: "bunx vinxi build",
78
81
  start: "bunx vinxi start",
82
+ test: getTestTask("bun"),
79
83
  ...nodeBunDevToolTasks
80
84
  },
81
85
  node: {
82
86
  dev: "vinxi dev",
83
87
  build: "vinxi build",
84
88
  start: "dotenvx run -- vinxi start",
89
+ test: getTestTask("npm"),
85
90
  ...nodeBunDevToolTasks
86
91
  }
87
92
  };
@@ -2,7 +2,7 @@ import { PACKAGE_MANAGER } from "../const.js";
2
2
  import { PACKAGE_VERSION, readTemplate } from "../lib.js";
3
3
  import { "npm:@dotenvx/dotenvx" as npm__dotenvx_dotenvx, "npm:@types/node@25" as npm__types_node_25, "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, getTestDependencies, getTestTask, nodeBunDevToolTasks, pmToRt } from "./utils.js";
6
6
  //#region src/webframeworks/sveltekit.ts
7
7
  const sveltekitDescription = {
8
8
  label: "SvelteKit",
@@ -18,13 +18,18 @@ const sveltekitDescription = {
18
18
  ...defaultDevDependencies,
19
19
  "typescript": npm_typescript,
20
20
  "@types/node": npm__types_node_25,
21
- ...pmToRt(pm) === "deno" ? {} : { "@dotenvx/dotenvx": npm__dotenvx_dotenvx }
21
+ ...pmToRt(pm) === "deno" ? {} : { "@dotenvx/dotenvx": npm__dotenvx_dotenvx },
22
+ ...getTestDependencies(pm)
22
23
  },
23
24
  federationFile: "src/lib/federation.ts",
24
25
  loggingFile: "src/lib/logging.ts",
26
+ testFile: "scripts/smoke.test.ts",
25
27
  env: testMode ? { HOST: "127.0.0.1" } : {},
26
28
  files: { "src/hooks.server.ts": await readTemplate("sveltekit/hooks.server.ts") },
27
- tasks: pmToRt(pm) === "deno" ? {} : { ...TASKS },
29
+ tasks: pmToRt(pm) === "deno" ? { test: getTestTask("deno") } : {
30
+ ...TASKS,
31
+ test: getTestTask(pm)
32
+ },
28
33
  instruction: getInstruction(pm, 5173)
29
34
  })
30
35
  };
@@ -1,4 +1,5 @@
1
1
  import { getDevCommand } from "../lib.js";
2
+ import { "npm:tsx" as npm_tsx } from "../json/deps.js";
2
3
  import { commandLine, message } from "@optique/core/message";
3
4
  //#region src/webframeworks/utils.ts
4
5
  const nodeBunDevToolTasks = {
@@ -7,6 +8,19 @@ const nodeBunDevToolTasks = {
7
8
  lint: "oxlint ."
8
9
  };
9
10
  const getNodeBunDevToolTasks = (pm) => pm === "deno" ? {} : nodeBunDevToolTasks;
11
+ const SMOKE_TEST_FILE = "scripts/smoke.test.ts";
12
+ /**
13
+ * Returns the `test` task command that runs the generated smoke-test
14
+ * script (`WebFrameworkInitializer.testFile`) with the runtime matching the
15
+ * given package manager.
16
+ */
17
+ const getTestTask = (pm) => pmToRt(pm) === "deno" ? `deno run -A ${SMOKE_TEST_FILE}` : pmToRt(pm) === "bun" ? `bun run ${SMOKE_TEST_FILE}` : `tsx ${SMOKE_TEST_FILE}`;
18
+ /**
19
+ * Returns the dev dependencies the `test` task needs beyond what the
20
+ * framework already declares. Node.js runs the smoke-test script through
21
+ * `tsx`; Deno and Bun execute TypeScript natively.
22
+ */
23
+ const getTestDependencies = (pm) => pmToRt(pm) === "node" ? { tsx: npm_tsx } : {};
10
24
  /**
11
25
  * Generates the post-initialization instruction message that shows
12
26
  * the user how to start the dev server and look up an actor.
@@ -32,4 +46,4 @@ Then, try to look up an actor from your server:
32
46
  */
33
47
  const pmToRt = (pm) => pm !== "deno" && pm !== "bun" ? "node" : pm;
34
48
  //#endregion
35
- export { getInstruction, getNodeBunDevToolTasks, nodeBunDevToolTasks, pmToRt };
49
+ export { getInstruction, getNodeBunDevToolTasks, getTestDependencies, getTestTask, nodeBunDevToolTasks, pmToRt };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/init",
3
- "version": "2.4.0-dev.1805+bf6d5258",
3
+ "version": "2.4.0-dev.1832+1af9355c",
4
4
  "description": "Project initializer for Fedify",
5
5
  "keywords": [
6
6
  "fedify",