diff --git a/CHANGES.md b/CHANGES.md index e225318e4..13158e9a2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -47,6 +47,17 @@ To be released. [#601]: https://github.com/fedify-dev/fedify/pull/601 [#652]: https://github.com/fedify-dev/fedify/pull/652 +### @fedify/init + + - Fixed errors when using `fedify init` with certain web framework + integration packages (Astro, ElysiaJS, Nitro) alongside `@fedify/mysql`. + Environment variables are now properly loaded at runtime, resolving the + `TypeError: Cannot read properties of undefined` from `mysql2`. + [[#649], [#656] by ChanHaeng Lee] + +[#649]: https://github.com/fedify-dev/fedify/issues/649 +[#656]: https://github.com/fedify-dev/fedify/pull/656 + Version 2.1.4 ------------- diff --git a/cspell.json b/cspell.json index 10cfad56a..1f22276ec 100644 --- a/cspell.json +++ b/cspell.json @@ -28,9 +28,11 @@ "dereferenceable", "discoverability", "docloader", + "dotenvx", "draft-cavage", "eddsa", "elysia", + "elysiajs", "fanout", "federatable", "Federatable", diff --git a/mise.toml b/mise.toml index ec6805555..d10d48986 100644 --- a/mise.toml +++ b/mise.toml @@ -176,6 +176,7 @@ run = "deno run -A examples/test-examples/mod.ts" [tasks."test:init"] description = "Run tests for the init package" run = "deno task -f @fedify/init test-init" +depends = ["prepare"] # Snapshot updates # Note: vocab uses @std/testing/snapshot which only works on Deno diff --git a/packages/init/src/action/deps.ts b/packages/init/src/action/deps.ts index d0cfad4f5..bf36516cd 100644 --- a/packages/init/src/action/deps.ts +++ b/packages/init/src/action/deps.ts @@ -13,7 +13,7 @@ import { PACKAGE_VERSION } from "../lib.ts"; import type { InitCommandData, PackageManager } from "../types.ts"; import { merge, replace } from "../utils.ts"; import { getPackagesPath } from "./const.ts"; -import { isDeno } from "./utils.ts"; +import { isDeno, needsDenoDotenv } from "./utils.ts"; type Deps = Record; @@ -26,9 +26,9 @@ type Deps = Record; * @returns A record of dependencies with their versions */ export const getDependencies = ( - { initializer, kv, mq, testMode, packageManager }: Pick< + { initializer, kv, mq, env, testMode, packageManager }: Pick< InitCommandData, - "initializer" | "kv" | "mq" | "packageManager" | "testMode" + "initializer" | "kv" | "mq" | "env" | "packageManager" | "testMode" >, ): Deps => pipe( @@ -36,6 +36,8 @@ export const getDependencies = ( "@fedify/fedify": PACKAGE_VERSION, "@fedify/vocab": PACKAGE_VERSION, "@logtape/logtape": deps["@logtape/logtape"], + ...(needsDenoDotenv({ packageManager, env }) && + { "@std/dotenv": deps["@std/dotenv"] }), }, merge(initializer.dependencies), merge(kv.dependencies), diff --git a/packages/init/src/action/patch.ts b/packages/init/src/action/patch.ts index 553cfca05..764722b50 100644 --- a/packages/init/src/action/patch.ts +++ b/packages/init/src/action/patch.ts @@ -1,9 +1,9 @@ import { always, apply, entries, map, pipe, pipeLazy, tap } from "@fxts/core"; import { toMerged } from "es-toolkit"; import { readFile } from "node:fs/promises"; -import { formatJson, merge, replaceAll, set } from "../utils.ts"; import { createFile, throwUnlessNotExists } from "../lib.ts"; import type { InitCommandData } from "../types.ts"; +import { formatJson, merge, replaceAll, set } from "../utils.ts"; import { devToolConfigs, loadDenoConfig, diff --git a/packages/init/src/action/set.ts b/packages/init/src/action/set.ts index 24574c02e..e50eecb31 100644 --- a/packages/init/src/action/set.ts +++ b/packages/init/src/action/set.ts @@ -12,6 +12,7 @@ import type { MessageQueue, MessageQueueDescription, WebFrameworkDescription, + WebFrameworkInitializer, } from "../types.ts"; import webFrameworks from "../webframeworks/mod.ts"; @@ -58,6 +59,11 @@ const setMq = set( const setEnv = set( "env", < - T extends { kv: KvStoreDescription; mq: MessageQueueDescription }, - >({ kv, mq }: T) => merge(kv.env)(mq.env), + T extends { + initializer: WebFrameworkInitializer; + kv: KvStoreDescription; + mq: MessageQueueDescription; + }, + >({ initializer, kv, mq }: T) => + merge(initializer.env)(merge(kv.env)(mq.env)), ); diff --git a/packages/init/src/action/templates.ts b/packages/init/src/action/templates.ts index 8d3e23864..8e489c11e 100644 --- a/packages/init/src/action/templates.ts +++ b/packages/init/src/action/templates.ts @@ -1,14 +1,17 @@ -import { entries, join, map, pipe } from "@fxts/core"; +import { concat, entries, join, map, pipe, when } from "@fxts/core"; import { toMerged } from "es-toolkit"; -import { replace } from "../utils.ts"; import { readTemplate } from "../lib.ts"; import type { InitCommandData, PackageManager } from "../types.ts"; +import { replace } from "../utils.ts"; +import { needsDenoDotenv } from "./utils.ts"; /** * Loads the federation configuration file content from template. - * Reads the default federation template and replaces placeholders with actual configuration values. + * Reads the default federation template and replaces placeholders with actual + * configuration values. * - * @param param0 - Configuration object containing imports, project name, KV store, message queue, and package manager + * @param param0 - Configuration object containing imports, project name, + * KV store, message queue, and package manager * @returns The complete federation configuration file content as a string */ export const loadFederation = async ( @@ -43,12 +46,19 @@ export const loadLogging = async ({ projectName }: InitCommandData) => /** * Generates import statements for KV store and message queue dependencies. - * Merges imports from both KV and MQ configurations and creates proper ES module import syntax. + * Merges imports from both KV and MQ configurations and creates proper + * ES module import syntax. * - * @param param0 - Destructured object containing kv and mq configurations + * Destructured parameters: + * - kv: KV store configuration, including module import mappings + * - mq: Message queue configuration, including module import mappings + * - packageManager: Package manager used for environment-specific handling + * - env: Environment variable setup used to determine loading requirements + * + * @param param0 - InitCommandData containing kv, mq, packageManager, and env * @returns A multi-line string containing all necessary import statements */ -export const getImports = ({ kv, mq }: InitCommandData) => +export const getImports = ({ kv, mq, packageManager, env }: InitCommandData) => pipe( toMerged(kv.imports, mq.imports), entries, @@ -61,12 +71,17 @@ export const getImports = ({ kv, mq }: InitCommandData) => .join(", ") } from ${JSON.stringify(module)};` ), + when( + () => needsDenoDotenv({ packageManager, env }), + concat(['import "@std/dotenv/load";']), + ), join("\n"), ); /** * Converts import mappings to named import string with aliases. - * Creates proper ES module named import syntax, using aliases when the import name differs from the local name. + * Creates proper ES module named import syntax, using aliases when the import + * name differs from the local name. * * @param imports - A record mapping import names to their local aliases * @returns A comma-separated string of named imports with aliases where needed @@ -81,12 +96,16 @@ export const getAlias = (imports: Record) => const ENV_REG_EXP = /process\.env\.(\w+)/g; /** - * Converts Node.js environment variable access to Deno-compatible syntax when needed. - * Transforms `process.env.VAR_NAME` to `Deno.env.get("VAR_NAME")` for Deno projects. + * Converts Node.js environment variable access to Deno-compatible syntax when + * needed. + * Transforms `process.env.VAR_NAME` to `Deno.env.get("VAR_NAME")` for Deno + * projects. * - * @param obj - The object string containing potential environment variable references + * @param obj - The object string containing potential environment variable + * references * @param pm - The package manager (runtime) being used - * @returns The converted object string with appropriate environment variable access syntax + * @returns The converted object string with appropriate environment variable + * access syntax */ export const convertEnv = (obj: string, pm: PackageManager) => pm === "deno" && ENV_REG_EXP.test(obj) diff --git a/packages/init/src/action/utils.ts b/packages/init/src/action/utils.ts index 32aab88b4..019dddb71 100644 --- a/packages/init/src/action/utils.ts +++ b/packages/init/src/action/utils.ts @@ -13,6 +13,14 @@ export const isDeno = ( { packageManager }: Pick, ) => packageManager === "deno"; +/** + * Returns `true` when the `@std/dotenv` dependency should be included: + * the project uses Deno and has at least one environment variable to load. + */ +export const needsDenoDotenv = ( + { packageManager, env }: Pick, +) => packageManager === "deno" && Object.keys(env).length > 0; + /** * Returns a function that prepends the project directory to a * `[filename, content]` tuple, resolving the filename into an absolute path. diff --git a/packages/init/src/templates/bare-bones/main/deno.ts.tpl b/packages/init/src/templates/bare-bones/main/deno.ts.tpl index 7d1c08896..c50d91c5f 100644 --- a/packages/init/src/templates/bare-bones/main/deno.ts.tpl +++ b/packages/init/src/templates/bare-bones/main/deno.ts.tpl @@ -1,4 +1,3 @@ -import "@std/dotenv/load"; import { behindProxy } from "@hongminhee/x-forwarded-fetch"; import federation from "./federation.ts"; import "./logging.ts"; diff --git a/packages/init/src/templates/hono/index/deno.ts.tpl b/packages/init/src/templates/hono/index/deno.ts.tpl index 4377a96be..4cf42e975 100644 --- a/packages/init/src/templates/hono/index/deno.ts.tpl +++ b/packages/init/src/templates/hono/index/deno.ts.tpl @@ -1,5 +1,4 @@ import { behindProxy } from "@hongminhee/x-forwarded-fetch"; -import "@std/dotenv/load"; import app from "./app.tsx"; import "./logging.ts"; diff --git a/packages/init/src/types.ts b/packages/init/src/types.ts index 94b8eb3d1..fb46c9f0e 100644 --- a/packages/init/src/types.ts +++ b/packages/init/src/types.ts @@ -40,7 +40,6 @@ export type Runtimes = Record; * whether it is installed. */ export interface RuntimeDescription { - /** Human-readable name of the runtime (e.g., `"Deno"`, `"Node.js"`). */ label: string; /** Shell command to run for checking availability (e.g., `["deno", "--version"]`). */ checkCommand: [string, ...string[]]; @@ -72,15 +71,23 @@ export interface WebFrameworkInitializer { /** Optional shell command to run before scaffolding (e.g., `create-next-app`). */ command?: string[]; /** Runtime dependencies to install (package name to version). */ - dependencies?: object; + dependencies?: Record; /** Development-only dependencies to install (package name to version). */ - devDependencies?: object; + devDependencies?: Record; /** Relative path where the federation configuration file will be created. */ federationFile: string; /** Relative path where the logging configuration file will be created. */ loggingFile: string; - /** Additional files to create, keyed by relative path to file content. */ - files?: Record; + /** + * Additional files to create, keyed by relative path to file content. + * Do not use `".env"` as a key — use the {@link env} property instead so + * that environment variables are properly merged with KV/MQ env vars. + */ + files?: Record & { ".env"?: never }; + /** Environment variables required by this framework, keyed by name to + * default value. Merged together with KV store and message queue env vars + * into the generated `.env` file. */ + env?: Record; /** TypeScript compiler options to include in `tsconfig.json`. */ compilerOptions?: Record; /** Task scripts keyed by task name (e.g., `"dev"`, `"prod"`, `"lint"`). */ diff --git a/packages/init/src/webframeworks/astro.ts b/packages/init/src/webframeworks/astro.ts index f99e0a477..063297209 100644 --- a/packages/init/src/webframeworks/astro.ts +++ b/packages/init/src/webframeworks/astro.ts @@ -3,7 +3,7 @@ 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"; +import { getInstruction, pmToRt } from "./utils.ts"; const astroDescription: WebFrameworkDescription = { label: "Astro", @@ -22,6 +22,8 @@ const astroDescription: WebFrameworkDescription = { : { "@astrojs/node": deps["npm:@astrojs/node"], "@fedify/astro": PACKAGE_VERSION, + ...(pm !== "bun" && + { "@dotenvx/dotenvx": deps["npm:@dotenvx/dotenvx"] }), }, devDependencies: { ...defaultDevDependencies, @@ -35,37 +37,15 @@ const astroDescription: WebFrameworkDescription = { federationFile: "src/federation.ts", loggingFile: "src/logging.ts", files: { - [`astro.config.ts`]: await readTemplate( + "astro.config.ts": await readTemplate( `astro/astro.config.${pm === "deno" ? "deno" : "node"}.ts`, ), "src/middleware.ts": await readTemplate("astro/src/middleware.ts"), - ...(pm !== "deno" - ? { - "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), - } - : {}), - }, - compilerOptions: undefined, - tasks: { - ...(pm === "deno" - ? { - dev: "deno run -A npm:astro dev", - build: "deno run -A npm:astro build", - preview: "deno run -A npm:astro preview", - } - : pm === "bun" - ? { - dev: "bunx astro dev", - build: "bunx astro build", - preview: "bunx astro preview", - } - : { - dev: "astro dev", - build: "astro build", - preview: "astro preview", - }), - ...(pm !== "deno" ? { lint: "eslint ." } : {}), + ...(pm !== "deno" && { + "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), + }), }, + tasks: TASKS[pmToRt(pm)], instruction: getInstruction(pm, 4321), }), }; @@ -97,3 +77,23 @@ function* getAstroInitCommand( const createAstroAppCommand = (pm: PackageManager): string[] => pm === "deno" ? ["deno", "init", "-y", "--npm"] : [pm, "create"]; + +const TASKS = { + "deno": { + dev: "deno run -A npm:astro dev", + build: "deno run -A npm:astro build", + preview: "deno run -A npm:astro preview", + }, + "bun": { + dev: "bunx astro dev", + build: "bunx astro build", + preview: "bunx astro preview", + lint: "eslint .", + }, + "node": { + dev: "dotenvx run -- astro dev", + build: "dotenvx run -- astro build", + preview: "dotenvx run -- astro preview", + lint: "eslint .", + }, +}; diff --git a/packages/init/src/webframeworks/bare-bones.ts b/packages/init/src/webframeworks/bare-bones.ts index fc4daf93c..03cc2409b 100644 --- a/packages/init/src/webframeworks/bare-bones.ts +++ b/packages/init/src/webframeworks/bare-bones.ts @@ -3,27 +3,14 @@ import deps from "../json/deps.json" with { type: "json" }; import { readTemplate } from "../lib.ts"; import type { WebFrameworkDescription } from "../types.ts"; import { defaultDenoDependencies, defaultDevDependencies } from "./const.ts"; -import { getInstruction, packageManagerToRuntime } from "./utils.ts"; +import { getInstruction, pmToRt } from "./utils.ts"; const bareBonesDescription: WebFrameworkDescription = { label: "Bare-bones", packageManagers: PACKAGE_MANAGER, defaultPort: 8000, init: async ({ packageManager: pm }) => ({ - dependencies: pm === "deno" - ? { - ...defaultDenoDependencies, - "@std/dotenv": deps["@std/dotenv"], - "@hongminhee/x-forwarded-fetch": deps["@hongminhee/x-forwarded-fetch"], - } - : pm === "bun" - ? { "npm:x-forwarded-fetch": deps["npm:x-forwarded-fetch"] } - : { - "npm:@dotenvx/dotenvx": deps["npm:@dotenvx/dotenvx"], - "npm:@hono/node-server": deps["npm:@hono/node-server"], - "npm:tsx": deps["npm:tsx"], - "npm:x-forwarded-fetch": deps["npm:x-forwarded-fetch"], - }, + dependencies: getDependencies(pm), devDependencies: { ...defaultDevDependencies, ...(pm === "bun" @@ -33,14 +20,10 @@ const bareBonesDescription: WebFrameworkDescription = { federationFile: "src/federation.ts", loggingFile: "src/logging.ts", files: { - "src/main.ts": await readTemplate( - `bare-bones/main/${packageManagerToRuntime(pm)}.ts`, - ), - ...(pm !== "deno" - ? { - "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), - } - : {}), + "src/main.ts": await readTemplate(`bare-bones/main/${pmToRt(pm)}.ts`), + ...(pm !== "deno" && { + "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), + }), }, compilerOptions: (pm === "deno" ? { @@ -57,20 +40,41 @@ const bareBonesDescription: WebFrameworkDescription = { "noEmit": true, "strict": true, }) as Record, - tasks: { - "dev": pm === "deno" - ? "deno run -A --watch ./src/main.ts" - : pm === "bun" - ? "bun run --hot ./src/main.ts" - : "dotenvx run -- tsx watch ./src/main.ts", - "prod": pm === "deno" - ? "deno run -A ./src/main.ts" - : pm === "bun" - ? "bun run ./src/main.ts" - : "dotenvx run -- node --import tsx ./src/main.ts", - }, + tasks: TASKS[pmToRt(pm)], instruction: getInstruction(pm, 8000), }), }; export default bareBonesDescription; + +const getDependencies = (pm: string): Record => + pm === "deno" + ? { + ...defaultDenoDependencies, + "@hongminhee/x-forwarded-fetch": deps["@hongminhee/x-forwarded-fetch"], + } + : pm === "bun" + ? { "npm:x-forwarded-fetch": deps["npm:x-forwarded-fetch"] } + : { + "npm:@dotenvx/dotenvx": deps["npm:@dotenvx/dotenvx"], + "npm:@hono/node-server": deps["npm:@hono/node-server"], + "npm:tsx": deps["npm:tsx"], + "npm:x-forwarded-fetch": deps["npm:x-forwarded-fetch"], + }; + +const TASKS = { + deno: { + dev: "deno run -A --watch ./src/main.ts", + prod: "deno run -A ./src/main.ts", + }, + bun: { + dev: "bun run --hot ./src/main.ts", + prod: "bun run ./src/main.ts", + lint: "eslint .", + }, + node: { + dev: "dotenvx run -- tsx watch ./src/main.ts", + prod: "dotenvx run -- node --import tsx ./src/main.ts", + lint: "eslint .", + }, +}; diff --git a/packages/init/src/webframeworks/elysia.ts b/packages/init/src/webframeworks/elysia.ts index dd5ac7e36..6c4e4b408 100644 --- a/packages/init/src/webframeworks/elysia.ts +++ b/packages/init/src/webframeworks/elysia.ts @@ -3,7 +3,7 @@ import deps from "../json/deps.json" with { type: "json" }; import { PACKAGE_VERSION, readTemplate } from "../lib.ts"; import type { WebFrameworkDescription } from "../types.ts"; import { defaultDenoDependencies, defaultDevDependencies } from "./const.ts"; -import { getInstruction, packageManagerToRuntime } from "./utils.ts"; +import { getInstruction, pmToRt } from "./utils.ts"; const elysiaDescription: WebFrameworkDescription = { label: "ElysiaJS", @@ -22,15 +22,14 @@ const elysiaDescription: WebFrameworkDescription = { "@fedify/elysia": PACKAGE_VERSION, } : { + "@dotenvx/dotenvx": deps["npm:@dotenvx/dotenvx"], elysia: deps["npm:elysia"], "@elysiajs/node": deps["npm:@elysiajs/node"], "@fedify/elysia": PACKAGE_VERSION, - ...(pm === "pnpm" - ? { - "@sinclair/typebox": deps["npm:@sinclair/typebox"], - "openapi-types": deps["npm:openapi-types"], - } - : {}), + ...(pm === "pnpm" && { + "@sinclair/typebox": deps["npm:@sinclair/typebox"], + "openapi-types": deps["npm:openapi-types"], + }), }, devDependencies: { ...(pm === "bun" ? { "@types/bun": deps["npm:@types/bun"] } : { @@ -44,13 +43,11 @@ const elysiaDescription: WebFrameworkDescription = { loggingFile: "src/logging.ts", files: { "src/index.ts": (await readTemplate( - `elysia/index/${packageManagerToRuntime(pm)}.ts`, + `elysia/index/${pmToRt(pm)}.ts`, )).replace(/\/\* logger \*\//, projectName), - ...(pm !== "deno" - ? { - "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), - } - : {}), + ...(pm !== "deno" && { + "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), + }), }, compilerOptions: pm === "deno" || pm === "bun" ? undefined : { "lib": ["ESNext", "DOM"], @@ -62,24 +59,30 @@ const elysiaDescription: WebFrameworkDescription = { "noEmit": true, "strict": true, }, - tasks: { - "dev": pm === "deno" - ? "deno serve --allow-env --allow-net --watch ./src/index.ts" - : pm === "bun" - ? "bun run --hot ./src/index.ts" - : "tsx watch src/index.ts", - ...(pm === "deno" - ? { "prod": "deno serve --allow-env --allow-net ./src/index.ts" } - : pm === "bun" - ? { "prod": "bun run ./src/index.ts" } - : { - "build": "tsc src/index.ts --outDir dist", - "start": "NODE_ENV=production node dist/index.js", - }), - ...(pm !== "deno" ? { "lint": "eslint ." } : {}), - }, + tasks: TASKS[pmToRt(pm)], instruction: getInstruction(pm, 3000), }), }; export default elysiaDescription; + +const TASKS = { + deno: { + dev: + "deno serve --allow-read --allow-env --allow-net --watch ./src/index.ts", + prod: "deno serve --allow-read --allow-env --allow-net ./src/index.ts", + }, + bun: { + dev: "bun run --hot ./src/index.ts", + prod: "bun run ./src/index.ts", + lint: "eslint .", + }, + node: { + dev: "dotenvx run -- tsx watch src/index.ts", + build: "tsc src/index.ts --outDir dist", + start: "NODE_ENV=production dotenvx run -- node dist/index.js", + lint: "eslint .", + }, +}; + +// cspell: ignore typebox diff --git a/packages/init/src/webframeworks/express.ts b/packages/init/src/webframeworks/express.ts index 7d54ed08f..101c03a8b 100644 --- a/packages/init/src/webframeworks/express.ts +++ b/packages/init/src/webframeworks/express.ts @@ -3,7 +3,7 @@ import deps from "../json/deps.json" with { type: "json" }; import { PACKAGE_VERSION, readTemplate } from "../lib.ts"; import type { WebFrameworkDescription } from "../types.ts"; import { defaultDenoDependencies, defaultDevDependencies } from "./const.ts"; -import { getInstruction } from "./utils.ts"; +import { getInstruction, pmToRt } from "./utils.ts"; const expressDescription: WebFrameworkDescription = { label: "Express", @@ -13,13 +13,11 @@ const expressDescription: WebFrameworkDescription = { dependencies: { "npm:express": deps["npm:express"], "@fedify/express": PACKAGE_VERSION, - ...(pm !== "deno" && pm !== "bun" - ? { - "@dotenvx/dotenvx": deps["npm:@dotenvx/dotenvx"], - tsx: deps["npm:tsx"], - } - : {}), - ...(pm === "deno" ? defaultDenoDependencies : {}), + ...(pmToRt(pm) === "node" && { + "@dotenvx/dotenvx": deps["npm:@dotenvx/dotenvx"], + tsx: deps["npm:tsx"], + }), + ...(pm === "deno" && defaultDenoDependencies), }, devDependencies: { "@types/express": deps["npm:@types/express"], @@ -48,21 +46,28 @@ const expressDescription: WebFrameworkDescription = { "noEmit": true, "strict": true, }, - tasks: { - "dev": pm === "bun" - ? "bun run --hot ./src/index.ts" - : pm === "deno" - ? "deno run --allow-net --allow-env --allow-sys --watch ./src/index.ts" - : "dotenvx run -- tsx watch ./src/index.ts", - "prod": pm === "bun" - ? "bun run ./src/index.ts" - : pm === "deno" - ? "deno run --allow-net --allow-env --allow-sys ./src/index.ts" - : "dotenvx run -- node --import tsx ./src/index.ts", - ...(pm !== "deno" ? { "lint": "eslint ." } : {}), - }, + tasks: TASKS[pmToRt(pm)], instruction: getInstruction(pm, 8000), }), }; export default expressDescription; + +const TASKS = { + deno: { + dev: + "deno run --allow-read --allow-net --allow-env --allow-sys --watch ./src/index.ts", + prod: + "deno run --allow-read --allow-net --allow-env --allow-sys ./src/index.ts", + }, + bun: { + dev: "bun run --hot ./src/index.ts", + prod: "bun run ./src/index.ts", + lint: "eslint .", + }, + node: { + dev: "dotenvx run -- tsx watch ./src/index.ts", + prod: "dotenvx run -- node --import tsx ./src/index.ts", + lint: "eslint .", + }, +}; diff --git a/packages/init/src/webframeworks/hono.ts b/packages/init/src/webframeworks/hono.ts index 4937b5ac1..a9b867a27 100644 --- a/packages/init/src/webframeworks/hono.ts +++ b/packages/init/src/webframeworks/hono.ts @@ -5,35 +5,14 @@ import { PACKAGE_VERSION, readTemplate } from "../lib.ts"; import type { WebFrameworkDescription } from "../types.ts"; import { replace } from "../utils.ts"; import { defaultDenoDependencies, defaultDevDependencies } from "./const.ts"; -import { getInstruction, packageManagerToRuntime } from "./utils.ts"; +import { getInstruction, pmToRt } from "./utils.ts"; const honoDescription: WebFrameworkDescription = { label: "Hono", packageManagers: PACKAGE_MANAGER, defaultPort: 8000, init: async ({ projectName, packageManager: pm }) => ({ - dependencies: pm === "deno" - ? { - ...defaultDenoDependencies, - "@std/dotenv": deps["@std/dotenv"], - "@hono/hono": deps["@hono/hono"], - "@hongminhee/x-forwarded-fetch": deps["@hongminhee/x-forwarded-fetch"], - "@fedify/hono": PACKAGE_VERSION, - } - : pm === "bun" - ? { - hono: deps["npm:hono"], - "x-forwarded-fetch": deps["npm:x-forwarded-fetch"], - "@fedify/hono": PACKAGE_VERSION, - } - : { - "@dotenvx/dotenvx": deps["npm:@dotenvx/dotenvx"], - hono: deps["npm:hono"], - "@hono/node-server": deps["npm:@hono/node-server"], - tsx: deps["npm:tsx"], - "x-forwarded-fetch": deps["npm:x-forwarded-fetch"], - "@fedify/hono": PACKAGE_VERSION, - }, + dependencies: getDependencies(pm), devDependencies: { ...defaultDevDependencies, ...(pm === "bun" ? { "@types/bun": deps["npm:@types/bun"] } : {}), @@ -47,7 +26,7 @@ const honoDescription: WebFrameworkDescription = { replace(/\/\* logger \*\//, projectName), ), "src/index.ts": await readTemplate( - `hono/index/${packageManagerToRuntime(pm)}.ts`, + `hono/index/${pmToRt(pm)}.ts`, ), ...(pm !== "deno" ? { @@ -67,21 +46,49 @@ const honoDescription: WebFrameworkDescription = { "jsx": "react-jsx", "jsxImportSource": "hono/jsx", }, - tasks: { - "dev": pm === "deno" - ? "deno run -A --watch ./src/index.ts" - : pm === "bun" - ? "bun run --hot ./src/index.ts" - : "dotenvx run -- tsx watch ./src/index.ts", - "prod": pm === "deno" - ? "deno run -A ./src/index.ts" - : pm === "bun" - ? "bun run ./src/index.ts" - : "dotenvx run -- node --import tsx ./src/index.ts", - ...(pm !== "deno" ? { "lint": "eslint ." } : {}), - }, + tasks: TASKS[pmToRt(pm)], instruction: getInstruction(pm, 8000), }), }; export default honoDescription; + +const getDependencies = (pm: string): Record => + pm === "deno" + ? { + ...defaultDenoDependencies, + "@hono/hono": deps["@hono/hono"], + "@hongminhee/x-forwarded-fetch": deps["@hongminhee/x-forwarded-fetch"], + "@fedify/hono": PACKAGE_VERSION, + } + : pm === "bun" + ? { + hono: deps["npm:hono"], + "x-forwarded-fetch": deps["npm:x-forwarded-fetch"], + "@fedify/hono": PACKAGE_VERSION, + } + : { + "@dotenvx/dotenvx": deps["npm:@dotenvx/dotenvx"], + hono: deps["npm:hono"], + "@hono/node-server": deps["npm:@hono/node-server"], + tsx: deps["npm:tsx"], + "x-forwarded-fetch": deps["npm:x-forwarded-fetch"], + "@fedify/hono": PACKAGE_VERSION, + }; + +const TASKS = { + deno: { + dev: "deno run -A --watch ./src/index.ts", + prod: "deno run -A ./src/index.ts", + }, + bun: { + dev: "bun run --hot ./src/index.ts", + prod: "bun run ./src/index.ts", + lint: "eslint .", + }, + node: { + dev: "dotenvx run -- tsx watch ./src/index.ts", + prod: "dotenvx run -- node --import tsx ./src/index.ts", + lint: "eslint .", + }, +}; diff --git a/packages/init/src/webframeworks/next.ts b/packages/init/src/webframeworks/next.ts index 0d9062b57..cf7bcc00d 100644 --- a/packages/init/src/webframeworks/next.ts +++ b/packages/init/src/webframeworks/next.ts @@ -23,15 +23,13 @@ const nextDescription: WebFrameworkDescription = { loggingFile: "logging.ts", files: { "middleware.ts": await readTemplate("next/middleware.ts"), - ...(pm !== "deno" - ? { - "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), - } - : {}), - }, - tasks: { - ...(pm !== "deno" ? { "lint": "eslint ." } : {}), + ...(pm !== "deno" && { + "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), + }), }, + tasks: pm !== "deno" + ? { "lint": "eslint ." } + : {} as Record, instruction: getInstruction(pm, 3000), }), }; diff --git a/packages/init/src/webframeworks/nitro.ts b/packages/init/src/webframeworks/nitro.ts index 7a1fd09d0..5bfd5eadd 100644 --- a/packages/init/src/webframeworks/nitro.ts +++ b/packages/init/src/webframeworks/nitro.ts @@ -12,27 +12,25 @@ const nitroDescription: WebFrameworkDescription = { command: getNitroInitCommand(pm), dependencies: { "@fedify/h3": PACKAGE_VERSION, - ...(pm === "deno" ? defaultDenoDependencies : {}), + ...(pm === "deno" && defaultDenoDependencies), }, devDependencies: defaultDevDependencies, federationFile: "server/federation.ts", loggingFile: "server/logging.ts", + env: testMode ? { HOST: "127.0.0.1" } : {} as Record, files: { "server/middleware/federation.ts": await readTemplate( "nitro/server/middleware/federation.ts", ), "server/error.ts": await readTemplate("nitro/server/error.ts"), "nitro.config.ts": await readTemplate("nitro/nitro.config.ts"), - ...( - testMode ? { ".env": await readTemplate("nitro/.env.test") } : {} - ), - ...(pm !== "deno" - ? { - "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), - } - : {}), + ...(pm !== "deno" && { + "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), + }), }, - tasks: pm !== "deno" ? { "lint": "eslint ." } : {} as { lint?: string }, + tasks: pm !== "deno" + ? { "lint": "eslint ." } + : {} as Record, instruction: getInstruction(pm, 3000), }), }; diff --git a/packages/init/src/webframeworks/solidstart.ts b/packages/init/src/webframeworks/solidstart.ts index 3187074ef..dee370003 100644 --- a/packages/init/src/webframeworks/solidstart.ts +++ b/packages/init/src/webframeworks/solidstart.ts @@ -3,7 +3,7 @@ import deps from "../json/deps.json" with { type: "json" }; import { PACKAGE_VERSION, readTemplate } from "../lib.ts"; import type { WebFrameworkDescription } from "../types.ts"; import { defaultDenoDependencies, defaultDevDependencies } from "./const.ts"; -import { getInstruction } from "./utils.ts"; +import { getInstruction, pmToRt } from "./utils.ts"; const NPM_SOLIDSTART = `npm:@solidjs/start@${deps["npm:@solidjs/start"]}`; const solidstartDescription: WebFrameworkDescription = { @@ -11,27 +11,7 @@ const solidstartDescription: WebFrameworkDescription = { packageManagers: PACKAGE_MANAGER, defaultPort: 3000, init: async ({ packageManager: pm }) => ({ - dependencies: pm === "deno" - ? { - ...defaultDenoDependencies, - "@solidjs/router": `npm:@solidjs/router@${deps["npm:@solidjs/router"]}`, - "@solidjs/start": `${NPM_SOLIDSTART}`, - "@solidjs/start/client": `${NPM_SOLIDSTART}/client`, - "@solidjs/start/config": `${NPM_SOLIDSTART}/config`, - "@solidjs/start/middleware": `${NPM_SOLIDSTART}/middleware`, - "@solidjs/start/router": `${NPM_SOLIDSTART}/router`, - "@solidjs/start/server": `${NPM_SOLIDSTART}/server`, - "solid-js": `npm:solid-js@${deps["npm:solid-js"]}`, - vinxi: `npm:vinxi@${deps["npm:vinxi"]}`, - "@fedify/solidstart": PACKAGE_VERSION, - } - : { - "@solidjs/router": deps["npm:@solidjs/router"], - "@solidjs/start": deps["npm:@solidjs/start"], - "solid-js": deps["npm:solid-js"], - vinxi: deps["npm:vinxi"], - "@fedify/solidstart": PACKAGE_VERSION, - }, + dependencies: getDependencies(pm), devDependencies: { ...defaultDevDependencies, typescript: deps["npm:typescript"], @@ -58,11 +38,9 @@ const solidstartDescription: WebFrameworkDescription = { "src/middleware/index.ts": await readTemplate( "solidstart/src/middleware/index.ts", ), - ...(pm !== "deno" - ? { - "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), - } - : {}), + ...(pm !== "deno" && { + "eslint.config.ts": await readTemplate("defaults/eslint.config.ts"), + }), }, compilerOptions: pm === "deno" ? undefined : { target: "ESNext", @@ -76,26 +54,57 @@ const solidstartDescription: WebFrameworkDescription = { noEmit: true, skipLibCheck: true, }, - tasks: { - dev: pm === "deno" - ? "deno run -A npm:vinxi dev" - : pm === "bun" - ? "bunx vinxi dev" - : "vinxi dev", - build: pm === "deno" - ? "deno run -A npm:vinxi build" - : pm === "bun" - ? "bunx vinxi build" - : "vinxi build", - start: pm === "deno" - ? "deno run -A npm:vinxi start" - : pm === "bun" - ? "bunx vinxi start" - : "vinxi start", - ...(pm !== "deno" ? { lint: "eslint ." } : {}), - }, + tasks: TASKS[pmToRt(pm)], instruction: getInstruction(pm, 3000), }), }; export default solidstartDescription; + +const getDependencies = (pm: string): Record => + pm === "deno" + ? { + ...defaultDenoDependencies, + "@solidjs/router": `npm:@solidjs/router@${deps["npm:@solidjs/router"]}`, + "@solidjs/start": NPM_SOLIDSTART, + ...DENO_SOLIDSTART, + "solid-js": `npm:solid-js@${deps["npm:solid-js"]}`, + vinxi: `npm:vinxi@${deps["npm:vinxi"]}`, + "@fedify/solidstart": PACKAGE_VERSION, + } + : { + "@dotenvx/dotenvx": deps["npm:@dotenvx/dotenvx"], + "@solidjs/router": deps["npm:@solidjs/router"], + "@solidjs/start": deps["npm:@solidjs/start"], + "solid-js": deps["npm:solid-js"], + vinxi: deps["npm:vinxi"], + "@fedify/solidstart": PACKAGE_VERSION, + }; + +const DENO_SOLIDSTART = Object.fromEntries([ + "client", + "config", + "middleware", + "router", + "server", +].map((pkg) => [`@solidjs/start/${pkg}`, `${NPM_SOLIDSTART}/${pkg}`])); + +const TASKS = { + deno: { + dev: "deno run -A npm:vinxi dev", + build: "deno run -A npm:vinxi build", + start: "deno run -A npm:vinxi start", + }, + bun: { + dev: "bunx vinxi dev", + build: "bunx vinxi build", + start: "bunx vinxi start", + lint: "eslint .", + }, + node: { + dev: "vinxi dev", + build: "vinxi build", + start: "dotenvx run -- vinxi start", + lint: "eslint .", + }, +}; diff --git a/packages/init/src/webframeworks/utils.ts b/packages/init/src/webframeworks/utils.ts index 1e36c03bf..12a9a1229 100644 --- a/packages/init/src/webframeworks/utils.ts +++ b/packages/init/src/webframeworks/utils.ts @@ -31,7 +31,5 @@ Then, try to look up an actor from your server: * @param pm - The package manager (deno, bun, npm, yarn, pnpm) * @returns The runtime name (deno, bun, or node) */ -export const packageManagerToRuntime = ( - pm: PackageManager, -): "deno" | "bun" | "node" => - pm === "deno" ? "deno" : pm === "bun" ? "bun" : "node"; +export const pmToRt = (pm: PackageManager): "deno" | "bun" | "node" => + (pm !== "deno" && pm !== "bun") ? "node" : pm;