diff --git a/CHANGES.md b/CHANGES.md index 2a2deb404..5bc246fbf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -68,8 +68,13 @@ To be released. `TypeError: Cannot read properties of undefined` from `mysql2`. [[#649], [#656] by ChanHaeng Lee] + - Supported [Nuxt] as a web framework option in `fedify init`, with + templates for federation setup, logging, and Nitro middleware. + [[#149], [#675] by ChanHaeng Lee] + [#649]: https://github.com/fedify-dev/fedify/issues/649 [#656]: https://github.com/fedify-dev/fedify/pull/656 +[#675]: https://github.com/fedify-dev/fedify/pull/675 Version 2.1.5 diff --git a/packages/init/src/const.ts b/packages/init/src/const.ts index 363922a78..b4120a80d 100644 --- a/packages/init/src/const.ts +++ b/packages/init/src/const.ts @@ -13,6 +13,7 @@ export const WEB_FRAMEWORK = [ "elysia", "astro", "express", + "nuxt", "solidstart", ] as const; /** All supported message queue backend identifiers. */ diff --git a/packages/init/src/json/deps.json b/packages/init/src/json/deps.json index 414ded649..488d9b0f4 100644 --- a/packages/init/src/json/deps.json +++ b/packages/init/src/json/deps.json @@ -9,6 +9,7 @@ "npm:@dotenvx/dotenvx": "^1.59.1", "npm:@elysiajs/node": "^1.4.5", "npm:@hono/node-server": "^1.19.12", + "npm:@nuxt/kit": "^4.4.2", "npm:@sinclair/typebox": "^0.34.49", "npm:@solidjs/router": "^0.16.1", "npm:@solidjs/start": "^1.3.2", @@ -21,7 +22,10 @@ "npm:elysia": "^1.4.28", "npm:eslint": "^9.0.0", "npm:express": "^5.2.1", + "npm:h3": "^1.15.0", "npm:hono": "^4.12.9", + "npm:nuxi": "^3.34.0", + "npm:nuxt": "^4.4.2", "npm:openapi-types": "^12.1.3", "npm:solid-js": "^1.9.12", "npm:tsx": "^4.21.0", diff --git a/packages/init/src/templates/nuxt/nuxt.config.ts.tpl b/packages/init/src/templates/nuxt/nuxt.config.ts.tpl new file mode 100644 index 000000000..b45e801fa --- /dev/null +++ b/packages/init/src/templates/nuxt/nuxt.config.ts.tpl @@ -0,0 +1,6 @@ +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + modules: ["@fedify/nuxt"], + fedify: { federationModule: "#server/federation" }, + ssr: true, +}); diff --git a/packages/init/src/test/lookup.ts b/packages/init/src/test/lookup.ts index e8064f530..e8ea62acb 100644 --- a/packages/init/src/test/lookup.ts +++ b/packages/init/src/test/lookup.ts @@ -31,6 +31,7 @@ const BANNED_LOOKUP_REASONS: Record = { "next,*,*,*": "Next.js doesn't support remote packages", "solidstart,deno,*,*": "Error occurred while loading submodules in Deno", "astro,deno,*,*": "Astro doesn't support remote packages in Deno", + "nuxt,deno,*,*": "Nuxt doesn't support remote packages in Deno", }; const BANNED_LOOKUP_CASES: LookupCasePattern[] = Object.keys( BANNED_LOOKUP_REASONS, diff --git a/packages/init/src/test/port.ts b/packages/init/src/test/port.ts index ef63da9f8..9b3804418 100644 --- a/packages/init/src/test/port.ts +++ b/packages/init/src/test/port.ts @@ -1,5 +1,5 @@ import { execFile } from "node:child_process"; -import { appendFile, readFile, writeFile } from "node:fs/promises"; +import { readFile, writeFile } from "node:fs/promises"; import { createConnection, createServer } from "node:net"; import { join } from "node:path"; import process from "node:process"; @@ -100,6 +100,12 @@ const ENTRY_FILES: Partial> = { elysia: "src/index.ts", }; +const WF_READ_PORT_FROM_ENV: Set = new Set([ + "nuxt", + "nitro", + "solidstart", +]); + /** * Replace the hardcoded default port with `newPort` in the generated test * app's source files. Strategy varies by framework. @@ -110,6 +116,7 @@ export async function replacePortInApp( defaultPort: number, newPort: number, ): Promise { + if (WF_READ_PORT_FROM_ENV.has(wf)) return; if (defaultPort === newPort) return; const entryFile = ENTRY_FILES[wf]; @@ -125,12 +132,6 @@ export async function replacePortInApp( return; } - if (wf === "nitro") { - // Nitro reads PORT from .env - await appendFile(join(dir, ".env"), `\nPORT=${newPort}\n`); - return; - } - if (wf === "astro") { // Insert server.port into the Astro config const configPath = join(dir, "astro.config.ts"); diff --git a/packages/init/src/webframeworks/mod.ts b/packages/init/src/webframeworks/mod.ts index f5baa6855..eccde686c 100644 --- a/packages/init/src/webframeworks/mod.ts +++ b/packages/init/src/webframeworks/mod.ts @@ -6,6 +6,7 @@ import express from "./express.ts"; import hono from "./hono.ts"; import next from "./next.ts"; import nitro from "./nitro.ts"; +import nuxt from "./nuxt.ts"; import solidstart from "./solidstart.ts"; /** @@ -23,6 +24,7 @@ const webFrameworks: WebFrameworks = { hono, next, nitro, + nuxt, solidstart, } as const; diff --git a/packages/init/src/webframeworks/nuxt.ts b/packages/init/src/webframeworks/nuxt.ts new file mode 100644 index 000000000..2e02f4187 --- /dev/null +++ b/packages/init/src/webframeworks/nuxt.ts @@ -0,0 +1,78 @@ +import { PACKAGE_MANAGER } from "../const.ts"; +import deps from "../json/deps.json" with { type: "json" }; +import { PACKAGE_VERSION, readTemplate } from "../lib.ts"; +import type { PackageManager, WebFrameworkDescription } from "../types.ts"; +import { defaultDenoDependencies, defaultDevDependencies } from "./const.ts"; +import { getInstruction } from "./utils.ts"; + +const nuxtDescription: WebFrameworkDescription = { + label: "Nuxt", + packageManagers: PACKAGE_MANAGER, + defaultPort: 3000, + init: async ({ packageManager: pm, testMode }) => ({ + command: Array.from(getInitCommand(pm)), + dependencies: getDeps(pm), + devDependencies: { + ...defaultDevDependencies, + "typescript": deps["npm:typescript"], + "@types/node": deps["npm:@types/node@25"], + }, + federationFile: "server/federation.ts", + loggingFile: "server/logging.ts", + env: testMode ? { HOST: "127.0.0.1" } : {} as Record, + files: { + "nuxt.config.ts": await readTemplate("nuxt/nuxt.config.ts"), + ...(pm !== "deno" && { + "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), + }), + }, + tasks: pm !== "deno" + ? { "lint": "eslint ." } + : {} as Record, + instruction: getInstruction(pm, 3000), + }), +}; + +export default nuxtDescription; + +function* getInitCommand(pm: PackageManager) { + yield* getNuxtInitCommand(pm); + yield* [ + "init", + ".", + "--template", + "minimal", + "--no-install", + "--force", + "--packageManager", + pm, + "--no-gitInit", + "--no-modules", + "&&", + "rm", + "nuxt.config.ts", + ]; +} + +/** + * Returns the shell command array to scaffold a new Nuxt project + * in the current directory using the given package manager. + */ +const getNuxtInitCommand = (pm: PackageManager): string[] => + pm === "bun" + ? ["bunx", "nuxi"] + : pm === "deno" + ? ["deno", "-A", "npm:nuxi@latest"] + : pm === "npm" + ? ["npx", "nuxi"] + : [pm, "dlx", "nuxi"]; + +const getDeps = (pm: PackageManager): Record => + pm !== "deno" + ? { + "@fedify/nuxt": PACKAGE_VERSION, + } + : { + ...defaultDenoDependencies, + "@fedify/nuxt": PACKAGE_VERSION, + };