diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 1401c622c..3c5a2925f 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -216,7 +216,7 @@ jobs: - run: '[[ "$(jq -r .version deno.json)" = "$(jq -r .version package.json)" ]]' working-directory: ${{ github.workspace }}/packages/fedify/ - run: mise run codegen - - run: deno publish --dry-run + - run: deno publish --dry-run --allow-slow-types - run: pnpm publish --recursive --dry-run --no-git-checks --ignore-scripts # =========================================================================== diff --git a/.gitignore b/.gitignore index c6158e03b..3c36dafdb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ repomix-output.xml t.ts t2.ts plan.md +plans/ diff --git a/.hongdown.toml b/.hongdown.toml index 960644376..b06c80d6a 100644 --- a/.hongdown.toml +++ b/.hongdown.toml @@ -10,6 +10,7 @@ exclude = [ "WARP.md", "packages/fedify/src/cfworkers/**", "plan.md", + "**/plans/**", ] [heading] diff --git a/AGENTS.md b/AGENTS.md index 2a1261288..c4a072c11 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -87,6 +87,10 @@ for the complete package list. ### Other key directories + - *packages/init/*: Project initializer (`@fedify/init`) for Fedify. + Separated from `@fedify/cli` to enable standalone use. + - *packages/create/*: Standalone CLI (`@fedify/create`) + for creating new Fedify projects via `npm init @fedify`. - *docs/*: Documentation built with VitePress (see *docs/README.md*) - *examples/*: Example projects diff --git a/CHANGES.md b/CHANGES.md index 25e2be18a..08f56245a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -476,6 +476,34 @@ To be released. - Requires a Workers KV namespace for lock management. - Due to Workers KV eventual consistency, ordering is best-effort. +### @fedify/init + + - Created project initializer as the *@fedify/init* package. Separated + the `fedify init` functionality from *@fedify/cli* into a standalone + package to improve modularity and enable reuse by other tools such as + `@fedify/create`. [[#482] by Chanhaeng Lee] + + - Added `runInit()` function as the main initialization action handler. + - Added `initCommand` and `initOptions` for CLI integration. + - Added `testInitCommand` for comprehensive testing of all init + combinations. + +[#482]: https://github.com/fedify-dev/fedify/issues/482 + +### @fedify/create + + - Created standalone project scaffolding CLI as the *@fedify/create* + package. This enables creating new Fedify projects without installing + the full `@fedify/cli` toolchain. [[#351] by Chanhaeng Lee] + + - Supports `npm init @fedify`, `pnpm create @fedify`, + `yarn create @fedify`, and `bunx @fedify/create`. + - Uses `@fedify/init` internally for all initialization logic. + - Supports the same interactive prompts and CLI options as + `fedify init`. + +[#351]: https://github.com/fedify-dev/fedify/issues/351 + Version 1.10.3 -------------- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d643c4a5..1b3126e8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -332,6 +332,8 @@ The repository is organized as a monorepo with the following packages: - *packages/cli/*: The Fedify CLI (@fedify/cli). Built with [Deno] and tested with Deno, Node.js, and [Bun]. Uses `deno compile` to create standalone executables. + - *packages/create/*: Standalone CLI (@fedify/create) for + creating new Fedify projects. Wraps @fedify/init. - *packages/amqp/*: AMQP/RabbitMQ driver (@fedify/amqp) for Fedify. - *packages/cfworkers/*: Cloudflare Workers integration (@fedify/cfworkers) for Fedify. @@ -344,6 +346,9 @@ The repository is organized as a monorepo with the following packages: - *packages/fresh/*: Fresh integration (@fedify/fresh) for Fedify. - *packages/h3/*: h3 framework integration (@fedify/h3) for Fedify. - *packages/hono/*: Hono integration (@fedify/hono) for Fedify. + - *packages/init/*: Project initializer (@fedify/init) for Fedify. + Separated from @fedify/cli to enable standalone use and + `npm init @fedify`. - *packages/koa/*: Koa integration (@fedify/koa) for Fedify. - *packages/lint/*: Linting utilities (@fedify/lint) for Fedify. - *packages/postgres/*: PostgreSQL drivers (@fedify/postgres) for Fedify. diff --git a/deno.json b/deno.json index bff4e5b3f..2644e64e2 100644 --- a/deno.json +++ b/deno.json @@ -11,6 +11,7 @@ "./packages/fixture", "./packages/fresh", "./packages/h3", + "./packages/init", "./packages/hono", "./packages/koa", "./packages/lint", @@ -40,6 +41,8 @@ "@opentelemetry/core": "npm:@opentelemetry/core@^2.5.0", "@opentelemetry/sdk-trace-base": "npm:@opentelemetry/sdk-trace-base@^2.5.0", "@opentelemetry/semantic-conventions": "npm:@opentelemetry/semantic-conventions@^1.39.0", + "@optique/core": "jsr:@optique/core@^0.9.0", + "@optique/run": "jsr:@optique/run@^0.9.0", "@std/assert": "jsr:@std/assert@^1.0.13", "@std/async": "jsr:@std/async@^1.0.13", "@std/encoding": "jsr:@std/encoding@^1.0.10", @@ -50,6 +53,7 @@ "@types/node": "npm:@types/node@^22.16.0", "amqplib": "npm:amqplib@^0.10.9", "byte-encodings": "npm:byte-encodings@^1.0.11", + "chalk": "npm:chalk@^5.6.2", "es-toolkit": "npm:es-toolkit@^1.43.0", "h3": "npm:h3@^1.15.0", "hono": "jsr:@hono/hono@^4.8.3", @@ -162,7 +166,7 @@ "./packages" ], "exclude": [ - "./packages/cli/src/init/templates/**" + "./packages/init/src/templates/**" ] } } diff --git a/deno.lock b/deno.lock index 84cd2ac41..ab0a53a06 100644 --- a/deno.lock +++ b/deno.lock @@ -82,8 +82,6 @@ "npm:@opentelemetry/sdk-trace-base@1.30.1": "1.30.1_@opentelemetry+api@1.9.0", "npm:@opentelemetry/sdk-trace-base@^2.5.0": "2.5.0_@opentelemetry+api@1.9.0", "npm:@opentelemetry/semantic-conventions@^1.39.0": "1.39.0", - "npm:@optique/core@0.9": "0.9.0", - "npm:@optique/run@0.9": "0.9.0", "npm:@poppanator/http-constants@^1.1.1": "1.1.1", "npm:@preact/signals@^2.2.1": "2.5.1_preact@10.19.6", "npm:@preact/signals@^2.3.2": "2.5.1_preact@10.19.6", @@ -104,7 +102,6 @@ "npm:chalk@^5.6.2": "5.6.2", "npm:cli-highlight@^2.1.11": "2.1.11", "npm:cli-table3@~0.6.5": "0.6.5", - "npm:enquirer@^2.4.1": "2.4.1", "npm:env-paths@3": "3.0.0", "npm:es-toolkit@^1.30.0": "1.43.0", "npm:es-toolkit@^1.31.0": "1.43.0", @@ -125,7 +122,6 @@ "npm:hono@^4.8.3": "4.11.3", "npm:icojs@~0.19.5": "0.19.5", "npm:inquirer-toggle@^1.0.1": "1.0.1", - "npm:inquirer@^12.9.4": "12.11.1_@types+node@22.19.3", "npm:ioredis@^5.8.2": "5.9.1", "npm:jimp@^1.6.0": "1.6.0", "npm:json-canon@^1.0.1": "1.0.1", @@ -278,7 +274,7 @@ "@optique/run@0.9.0": { "integrity": "d71b0a05a1342e874fe1718a01d10384ca6b3de3245ae485f6565d6e2f0c16ad", "dependencies": [ - "jsr:@optique/core@0.9" + "jsr:@optique/core" ] }, "@std/assert@0.224.0": { @@ -2085,15 +2081,6 @@ "@opentelemetry/semantic-conventions@1.39.0": { "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==" }, - "@optique/core@0.9.0": { - "integrity": "sha512-PN5SwVRK9BPmFUKzcdNYDCV4Q18HGfR4H/1MG1yQYmA1RDNVKTuCV146dTIqI2H8F4lD/WMTiNsTl2wbGr9u1Q==" - }, - "@optique/run@0.9.0": { - "integrity": "sha512-S9hZfXPICeyIq3HArJ61yeSaadZQTnTKc4g03wcYRXLPYd6h/K7tCeVQnOkJ4k3b01TuiuHrartxwwxr3j/dnA==", - "dependencies": [ - "@optique/core" - ] - }, "@oxc-project/types@0.103.0": { "integrity": "sha512-bkiYX5kaXWwUessFRSoXFkGIQTmc6dLGdxuRTrC+h8PSnIdZyuXHHlLAeTmOue5Br/a0/a7dHH0Gca6eXn9MKg==" }, @@ -2845,9 +2832,6 @@ "url-parse" ] }, - "ansi-colors@4.1.3": { - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" - }, "ansi-escapes@4.3.2": { "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dependencies": [ @@ -3319,13 +3303,6 @@ "encodeurl@2.0.0": { "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, - "enquirer@2.4.1": { - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dependencies": [ - "ansi-colors", - "strip-ansi@6.0.1" - ] - }, "env-paths@3.0.0": { "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==" }, @@ -4035,22 +4012,6 @@ "@inquirer/core@8.2.4" ] }, - "inquirer@12.11.1_@types+node@22.19.3": { - "integrity": "sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==", - "dependencies": [ - "@inquirer/ansi", - "@inquirer/core@10.3.2_@types+node@22.19.3", - "@inquirer/prompts", - "@inquirer/type@3.0.10_@types+node@22.19.3", - "@types/node@22.19.3", - "mute-stream@2.0.0", - "run-async", - "rxjs" - ], - "optionalPeers": [ - "@types/node@22.19.3" - ] - }, "ioredis@5.9.1": { "integrity": "sha512-BXNqFQ66oOsR82g9ajFFsR8ZKrjVvYCLyeML9IvSMAsP56XH2VXBdZjmI11p65nXXJxTEt1hie3J2QeFJVgrtQ==", "dependencies": [ @@ -4999,9 +4960,6 @@ ], "bin": true }, - "run-async@4.0.6": { - "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==" - }, "rxjs@7.8.2": { "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dependencies": [ @@ -5889,6 +5847,8 @@ "jsr:@hono/hono@^4.8.3", "jsr:@logtape/file@2", "jsr:@logtape/logtape@2", + "jsr:@optique/core@0.9", + "jsr:@optique/run@0.9", "jsr:@std/assert@^1.0.13", "jsr:@std/async@^1.0.13", "jsr:@std/encoding@^1.0.10", @@ -5908,6 +5868,7 @@ "npm:@types/node@^22.16.0", "npm:amqplib@~0.10.9", "npm:byte-encodings@^1.0.11", + "npm:chalk@^5.6.2", "npm:es-toolkit@^1.43.0", "npm:h3@^1.15.0", "npm:ioredis@^5.8.2", @@ -5957,18 +5918,13 @@ "dependencies": [ "jsr:@hongminhee/localtunnel@0.3", "jsr:@hono/hono@^4.8.3", - "jsr:@optique/core@0.9", - "jsr:@optique/run@0.9", - "npm:@inquirer/prompts@^7.8.4", "npm:@jimp/core@^1.6.0", "npm:@jimp/wasm-webp@^1.6.0", "npm:@poppanator/http-constants@^1.1.1", - "npm:chalk@^5.6.2", "npm:cli-table3@~0.6.5", "npm:env-paths@3", "npm:fetch-mock@^12.5.4", "npm:icojs@~0.19.5", - "npm:inquirer-toggle@^1.0.1", "npm:ora@^8.2.0", "npm:shiki@^1.6.4", "npm:srvx@~0.8.7" @@ -5976,21 +5932,14 @@ "packageJson": { "dependencies": [ "npm:@hongminhee/localtunnel@0.3", - "npm:@inquirer/prompts@^7.8.4", "npm:@jimp/core@^1.6.0", "npm:@jimp/wasm-webp@^1.6.0", - "npm:@optique/core@0.9", - "npm:@optique/run@0.9", "npm:@poppanator/http-constants@^1.1.1", - "npm:chalk@^5.6.2", "npm:cli-highlight@^2.1.11", "npm:cli-table3@~0.6.5", - "npm:enquirer@^2.4.1", "npm:env-paths@3", "npm:hono@^4.8.3", "npm:icojs@~0.19.5", - "npm:inquirer-toggle@^1.0.1", - "npm:inquirer@^12.9.4", "npm:jimp@^1.6.0", "npm:ora@^8.2.0", "npm:shiki@^1.6.4", @@ -6057,6 +6006,18 @@ "jsr:@std/assert@^1.0.13" ] }, + "packages/init": { + "dependencies": [ + "npm:@inquirer/prompts@^7.8.4", + "npm:inquirer-toggle@^1.0.1" + ], + "packageJson": { + "dependencies": [ + "npm:@inquirer/prompts@^7.8.4", + "npm:inquirer-toggle@^1.0.1" + ] + } + }, "packages/koa": { "dependencies": [ "npm:koa@2" diff --git a/docs/cli.md b/docs/cli.md index 201b0c435..ec7ceaddb 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -126,6 +126,19 @@ project. It will ask you a few questions to set up the project: - Message queue: In-memory, [Redis], [PostgreSQL], [AMQP] (e.g., [RabbitMQ]), or [Deno KV] (if Deno) +> [!TIP] +> Projects created with `fedify init` automatically include [`@fedify/lint`] +> for consistent code linting. Deno projects get a lint plugin configured in +> *deno.json*, while Node.js and Bun projects get an *eslint.config.ts* with +> `@fedify/lint`. + +> [!NOTE] +> If you find the full `@fedify/cli` toolchain too heavy for your needs, you +> can use [`@fedify/create`] instead to scaffold a new Fedify project without +> installing the CLI globally. See the +> [*Alternative: Using `@fedify/create`*](./install.md#alternative-using-fedify-create) +> section for details. + Alternatively, you can specify the options in the command line to skip some of interactive prompts: @@ -142,6 +155,8 @@ interactive prompts: [Deno KV]: https://deno.com/kv [AMQP]: https://www.amqp.org/ [RabbitMQ]: https://www.rabbitmq.com/ +[`@fedify/lint`]: /manual/lint +[`@fedify/create`]: https://www.npmjs.com/package/@fedify/create ### `-r`/`--runtime`: JavaScript runtime diff --git a/docs/install.md b/docs/install.md index 79f552474..b06c43bca 100644 --- a/docs/install.md +++ b/docs/install.md @@ -65,6 +65,34 @@ in the *CLI toolchain* docs. [![The “fedify init” command demo](https://asciinema.org/a/671658.svg)](https://asciinema.org/a/671658) +### Alternative: Using `@fedify/create` + +If you don't want to install the `fedify` CLI globally, you can use +`@fedify/create` directly: + +::: code-group + +~~~~ sh [npm] +npm init @fedify your-project-dir +~~~~ + +~~~~ sh [pnpm] +pnpm create @fedify your-project-dir +~~~~ + +~~~~ sh [Yarn] +yarn create @fedify your-project-dir +~~~~ + +~~~~ sh [Bun] +bunx @fedify/create your-project-dir +~~~~ + +::: + +This works the same way as `fedify init` and will guide you through the same +project setup wizard. + Manual installation ------------------- diff --git a/packages/cli/deno.json b/packages/cli/deno.json index fb64a2642..61fdf8f63 100644 --- a/packages/cli/deno.json +++ b/packages/cli/deno.json @@ -5,19 +5,14 @@ "exports": "./src/mod.ts", "imports": { "@hongminhee/localtunnel": "jsr:@hongminhee/localtunnel@^0.3.0", - "@inquirer/prompts": "npm:@inquirer/prompts@^7.8.4", "@jimp/core": "npm:@jimp/core@^1.6.0", "@jimp/wasm-webp": "npm:@jimp/wasm-webp@^1.6.0", - "@optique/core": "jsr:@optique/core@^0.9.0", - "@optique/run": "jsr:@optique/run@^0.9.0", "@poppanator/http-constants": "npm:@poppanator/http-constants@^1.1.1", - "chalk": "npm:chalk@^5.6.2", "cli-table3": "npm:cli-table3@^0.6.5", "env-paths": "npm:env-paths@^3.0.0", "fetch-mock": "npm:fetch-mock@^12.5.4", "hono": "jsr:@hono/hono@^4.8.3", "icojs": "npm:icojs@^0.19.5", - "inquirer-toggle": "npm:inquirer-toggle@^1.0.1", "ora": "npm:ora@^8.2.0", "shiki": "npm:shiki@^1.6.4", "srvx": "npm:srvx@^0.8.7", @@ -30,7 +25,11 @@ "fedify-cli-*.zip" ], "publish": { - "exclude": ["**/*.test.ts", "tsdown.config.ts", "scripts/"] + "exclude": [ + "**/*.test.ts", + "tsdown.config.ts", + "scripts/" + ] }, "tasks": { "codegen": "deno task -f @fedify/vocab compile", @@ -58,27 +57,8 @@ "dependencies": [ "codegen" ] - }, - "test-init": { - "command": "FEDIFY_TEST_MODE=true deno run --allow-all src/init/test/mod.ts test-init", - "dependencies": [ - "codegen" - ] } }, - "fmt": { - "exclude": [ - "src/init/templates/**" - ] - }, - "lint": { - "exclude": [ - "src/init/templates/**" - ] - }, - "test": { - "exclude": [ - "src/init/test/**" - ] - } + "fmt": {}, + "lint": {} } diff --git a/packages/cli/package.json b/packages/cli/package.json index 2b59a1f4d..0b220c2b3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -45,6 +45,7 @@ }, "dependencies": { "@fedify/fedify": "workspace:*", + "@fedify/init": "workspace:*", "@fedify/relay": "workspace:*", "@fedify/sqlite": "workspace:*", "@fedify/vocab": "workspace:*", @@ -53,27 +54,23 @@ "@fedify/webfinger": "workspace:*", "@fxts/core": "catalog:", "@hongminhee/localtunnel": "^0.3.0", - "@inquirer/prompts": "^7.8.4", "@jimp/core": "^1.6.0", "@jimp/wasm-webp": "^1.6.0", "@js-temporal/polyfill": "catalog:", "@logtape/file": "catalog:", "@logtape/logtape": "catalog:", - "@optique/core": "^0.9.0", - "@optique/run": "^0.9.0", + "@optique/core": "catalog:", + "@optique/run": "catalog:", "@poppanator/http-constants": "^1.1.1", "byte-encodings": "catalog:", - "chalk": "^5.6.2", + "chalk": "catalog:", "cli-highlight": "^2.1.11", "cli-table3": "^0.6.5", - "enquirer": "^2.4.1", "env-paths": "^3.0.0", "es-toolkit": "catalog:", "fetch-mock": "catalog:", "hono": "^4.8.3", "icojs": "^0.19.5", - "inquirer": "^12.9.4", - "inquirer-toggle": "^1.0.1", "jimp": "^1.6.0", "ora": "^8.2.0", "shiki": "^1.6.4", @@ -92,7 +89,6 @@ "prepublish": "pnpm build", "pretest": "pnpm build", "test": "node --test --experimental-transform-types 'src/**/*.test.ts' '!src/init/test/**'", - "test-init": "deno task test-init", "pretest:bun": "pnpm build", "test:bun": "bun test", "run": "pnpm build && node dist/mod.js", diff --git a/packages/cli/scripts/pack.ts b/packages/cli/scripts/pack.ts index b4284c9d4..f8ec1d2fe 100644 --- a/packages/cli/scripts/pack.ts +++ b/packages/cli/scripts/pack.ts @@ -25,7 +25,7 @@ async function compile(os: OS, arch: Arch, into: string): Promise { throw new Error(`Unsupported os/arch: ${os}/${arch}`); } await $`deno compile --allow-all --include=${ - join("src", "init", "templates") + join("..", "init", "src", "templates") } --node-modules-dir=none --target=${target} --output=${into} ${ join(dirname(import.meta.dirname!), "src", "mod.ts") }`; diff --git a/packages/cli/src/init/mod.ts b/packages/cli/src/init/mod.ts index ec200713e..05626f949 100644 --- a/packages/cli/src/init/mod.ts +++ b/packages/cli/src/init/mod.ts @@ -1,3 +1 @@ -export { default as runInit } from "./action/mod.ts"; -export { initCommand, testInitCommand } from "./command.ts"; -export { default as runTestInit } from "./test/action.ts"; +export { initCommand, runInit } from "@fedify/init"; diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 355eddd6c..fcb53068d 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -267,7 +267,7 @@ export function* product[]>( } export type GeneratedType = T extends - Generator ? R : never; + Generator ? R : never; type PrintMessage = (...args: Parameters) => void; export const printMessage: PrintMessage = flow(message, print); diff --git a/packages/cli/tsdown.config.ts b/packages/cli/tsdown.config.ts index 461378f04..72481113a 100644 --- a/packages/cli/tsdown.config.ts +++ b/packages/cli/tsdown.config.ts @@ -1,11 +1,8 @@ -import { cp } from "node:fs/promises"; -import { join } from "node:path"; import { defineConfig } from "tsdown"; export default defineConfig({ entry: [ "src/mod.ts", - "src/init/test/mod.ts", "src/kv.bun.ts", "src/kv.node.ts", ], @@ -29,13 +26,4 @@ export default defineConfig({ `; return outputOptions; }, - hooks: { - "build:done": async (ctx) => { - await cp( - join("src", "init", "templates"), - join(ctx.options.outDir, "init", "templates"), - { recursive: true }, - ); - }, - }, }); diff --git a/packages/create/README.md b/packages/create/README.md new file mode 100644 index 000000000..b5c2ccb75 --- /dev/null +++ b/packages/create/README.md @@ -0,0 +1,47 @@ +@fedify/create: Create a new Fedify project +=========================================== + +[![npm][npm badge]][npm] + +This package provides a standalone CLI tool for creating new [Fedify] projects. +It allows you to scaffold a new project without installing the full +[`@fedify/cli`] toolchain, powered by [`@fedify/init`] internally. + +[npm badge]: https://img.shields.io/npm/v/@fedify/create?logo=npm +[npm]: https://www.npmjs.com/package/@fedify/create +[Fedify]: https://fedify.dev/ +[`@fedify/cli`]: https://jsr.io/@fedify/cli +[`@fedify/init`]: https://jsr.io/@fedify/init + + +Usage +----- + +~~~~ sh +npm init @fedify my-project +pnpm create @fedify my-project +yarn create @fedify my-project +bunx @fedify/create my-project +~~~~ + + +Supported options +----------------- + +The tool supports the same project configurations as `fedify init`: + + - **Web frameworks**: [Hono], [Nitro], [Next.js], [Elysia], [Express] + - **Package managers**: Deno, pnpm, Bun, Yarn, npm + - **Key-value stores**: Deno KV, Redis, PostgreSQL + - **Message queues**: Deno KV, Redis, PostgreSQL, AMQP + +See the [`@fedify/init`] package or the [Fedify CLI docs] for details on +available options (`-p`, `-w`, `-k`, `-m`, `--dry-run`). +More information is in the [`@fedify/init`] package. + +[Hono]: https://hono.dev/ +[Nitro]: https://nitro.build/ +[Next.js]: https://nextjs.org/ +[Elysia]: https://elysiajs.com/ +[Express]: https://expressjs.com/ +[Fedify CLI docs]: https://fedify.dev/cli diff --git a/packages/create/package.json b/packages/create/package.json new file mode 100644 index 000000000..f3308c672 --- /dev/null +++ b/packages/create/package.json @@ -0,0 +1,62 @@ +{ + "name": "@fedify/create", + "version": "2.0.0", + "description": "Create a new Fedify application", + "keywords": [ + "fedify", + "activitypub", + "cli", + "init", + "fediverse" + ], + "homepage": "https://fedify.dev/", + "bugs": { + "url": "https://github.com/fedify-dev/fedify/issues" + }, + "license": "MIT", + "author": { + "name": "ChanHaeng Lee", + "email": "2chanhaeng@gmail.com", + "url": "https://chomu.dev/" + }, + "funding": [ + "https://opencollective.com/fedify", + "https://github.com/sponsors/dahlia" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/fedify-dev/fedify.git", + "directory": "packages/create" + }, + "type": "module", + "bin": { + "@fedify/create": "./dist/mod.js" + }, + "exports": "./dist/mod.js", + "files": [ + "dist/", + "package.json", + "README.md" + ], + "engines": { + "node": ">=20.0.0", + "bun": ">=1.2.0" + }, + "dependencies": { + "@fedify/init": "workspace:*", + "@optique/core": "catalog:", + "@optique/run": "catalog:", + "es-toolkit": "catalog:" + }, + "devDependencies": { + "@types/node": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:" + }, + "scripts": { + "build:self": "tsdown", + "build": "pnpm --filter @fedify/create... run build:self", + "prepack": "pnpm build", + "prepublish": "pnpm build" + } +} diff --git a/packages/create/src/mod.ts b/packages/create/src/mod.ts new file mode 100644 index 000000000..c5a94cf0a --- /dev/null +++ b/packages/create/src/mod.ts @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +import { initOptions, runInit } from "@fedify/init"; +import { message, optionNames } from "@optique/core"; +import { run } from "@optique/run"; +import { merge } from "es-toolkit"; + +const result = run(initOptions, { + programName: "@fedify/create", + description: message`Create a new Fedify project. + +Unless you specify all options (${optionNames(["-w", "--web-framework"])}, ${ + optionNames(["-p", "--package-manager"]) + }, ${optionNames(["-k", "--kv-store"])}, and ${ + optionNames(["-m", "--message-queue"]) + }), it will prompt you to select the options interactively.`, + help: "both", +}); + +await runInit(merge(result, { command: "init" } as const)); diff --git a/packages/create/tsdown.config.ts b/packages/create/tsdown.config.ts new file mode 100644 index 000000000..bc0724fe7 --- /dev/null +++ b/packages/create/tsdown.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: ["src/mod.ts"], + platform: "node", + unbundle: true, + external: [/^node:/], +}); diff --git a/packages/fedify/README.md b/packages/fedify/README.md index fcbacb8f1..45d71223c 100644 --- a/packages/fedify/README.md +++ b/packages/fedify/README.md @@ -98,6 +98,7 @@ Here is the list of packages: | ------------------------------------------------- | -------------------------------- | -------------------------------- | ---------------------------------------- | | [@fedify/fedify](/packages/fedify/) | [JSR] | [npm] | The core framework of Fedify | | [@fedify/cli](/packages/cli/) | [JSR][jsr:@fedify/cli] | [npm][npm:@fedify/cli] | CLI toolchain for testing and debugging | +| [@fedify/create](/packages/create/) | | [npm][npm:@fedify/create] | Create a new Fedify project | | [@fedify/amqp](/packages/amqp/) | [JSR][jsr:@fedify/amqp] | [npm][npm:@fedify/amqp] | AMQP/RabbitMQ driver | | [@fedify/cfworkers](/packages/cfworkers/) | [JSR][jsr:@fedify/cfworkers] | [npm][npm:@fedify/cfworkers] | Cloudflare Workers integration | | [@fedify/debugger](/packages/debugger/) | [JSR][jsr:@fedify/debugger] | [npm][npm:@fedify/debugger] | Embedded ActivityPub debug dashboard | @@ -108,6 +109,7 @@ Here is the list of packages: | [@fedify/fresh](/packages/fresh/) | [JSR][jsr:@fedify/fresh] | | Fresh integration | | [@fedify/h3](/packages/h3/) | [JSR][jsr:@fedify/h3] | [npm][npm:@fedify/h3] | H3 integration | | [@fedify/hono](/packages/hono/) | [JSR][jsr:@fedify/hono] | [npm][npm:@fedify/hono] | Hono integration | +| [@fedify/init](/packages/init/) | [JSR][jsr:@fedify/init] | [npm][npm:@fedify/init] | Project initializer for Fedify | | [@fedify/koa](/packages/koa/) | [JSR][jsr:@fedify/koa] | [npm][npm:@fedify/koa] | Koa integration | | [@fedify/lint](/packages/lint/) | [JSR][jsr:@fedify/lint] | [npm][npm:@fedify/lint] | Linting utilities | | [@fedify/nestjs](/packages/nestjs/) | | [npm][npm:@fedify/nestjs] | NestJS integration | @@ -125,6 +127,7 @@ Here is the list of packages: [jsr:@fedify/cli]: https://jsr.io/@fedify/cli [npm:@fedify/cli]: https://www.npmjs.com/package/@fedify/cli +[npm:@fedify/create]: https://www.npmjs.com/package/@fedify/create [jsr:@fedify/amqp]: https://jsr.io/@fedify/amqp [npm:@fedify/amqp]: https://www.npmjs.com/package/@fedify/amqp [jsr:@fedify/cfworkers]: https://jsr.io/@fedify/cfworkers @@ -142,6 +145,8 @@ Here is the list of packages: [npm:@fedify/h3]: https://www.npmjs.com/package/@fedify/h3 [jsr:@fedify/hono]: https://jsr.io/@fedify/hono [npm:@fedify/hono]: https://www.npmjs.com/package/@fedify/hono +[jsr:@fedify/init]: https://jsr.io/@fedify/init +[npm:@fedify/init]: https://www.npmjs.com/package/@fedify/init [jsr:@fedify/koa]: https://jsr.io/@fedify/koa [npm:@fedify/koa]: https://www.npmjs.com/package/@fedify/koa [jsr:@fedify/lint]: https://jsr.io/@fedify/lint diff --git a/packages/init/README.md b/packages/init/README.md new file mode 100644 index 000000000..892fb2f50 --- /dev/null +++ b/packages/init/README.md @@ -0,0 +1,93 @@ +@fedify/init: Project initializer for Fedify +============================================ + +[![JSR][JSR badge]][JSR] +[![npm][npm badge]][npm] + +This package provides the project initialization functionality for [Fedify], +an ActivityPub server framework. It scaffolds new Fedify project directories +with support for various web frameworks, package managers, key-value stores, +and message queues. + +This package powers the `fedify init` command in the [`@fedify/cli`] toolchain, +and can also be used as a standalone library. + +[JSR badge]: https://jsr.io/badges/@fedify/init +[JSR]: https://jsr.io/@fedify/init +[npm badge]: https://img.shields.io/npm/v/@fedify/init?logo=npm +[npm]: https://www.npmjs.com/package/@fedify/init +[Fedify]: https://fedify.dev/ +[`@fedify/cli`]: https://jsr.io/@fedify/cli + + +Supported options +----------------- + +The initializer supports the following project configurations: + + - **Web frameworks**: [Hono], [Nitro], [Next.js], [Elysia], [Express] + - **Package managers**: Deno, pnpm, Bun, Yarn, npm + - **Key-value stores**: Deno KV, Redis, PostgreSQL + - **Message queues**: Deno KV, Redis, PostgreSQL, AMQP + +[Hono]: https://hono.dev/ +[Nitro]: https://nitro.build/ +[Next.js]: https://nextjs.org/ +[Elysia]: https://elysiajs.com/ +[Express]: https://expressjs.com/ + + +Installation +------------ + +~~~~ sh +deno add jsr:@fedify/init # Deno +npm add @fedify/init # npm +pnpm add @fedify/init # pnpm +yarn add @fedify/init # Yarn +bun add @fedify/init # Bun +~~~~ + + +API +--- + +The package exports the following: + + - `runInit`: The main initialization action handler. + - `initCommand`: The CLI command definition for `init`. + +~~~~ typescript +import { initCommand, runInit } from "@fedify/init"; +~~~~ + + +Test +---- + +The `test-init` task is useful for contributors working on `@fedify/init`, +especially when adding support for a new framework/library or modifying the +scaffolding logic. It tests the project initialization by running +`fedify init` across all combinations of supported options on temporary +directories, verifying that the generated projects are valid. + +To run the test using Deno: + +~~~~ sh +deno task test-init +~~~~ + +Or using pnpm: + +~~~~ sh +pnpm test-init +~~~~ + +You can also filter specific options to test a subset of combinations: + +~~~~ sh +deno task test-init -w hono -p deno +~~~~ + +Use `--no-dry-run` to test with actual file creation and dependency +installation, or `--no-hyd-run` to only log outputs without creating files. diff --git a/packages/init/deno.json b/packages/init/deno.json new file mode 100644 index 000000000..9b0018ef5 --- /dev/null +++ b/packages/init/deno.json @@ -0,0 +1,44 @@ +{ + "name": "@fedify/init", + "version": "2.0.0", + "license": "MIT", + "exports": "./src/mod.ts", + "imports": { + "@inquirer/prompts": "npm:@inquirer/prompts@^7.8.4", + "inquirer-toggle": "npm:inquirer-toggle@^1.0.1" + }, + "exclude": [ + "dist/", + "node_modules/" + ], + "publish": { + "exclude": [ + "**/*.test.ts" + ] + }, + "tasks": { + "check": "deno fmt --check && deno lint && deno check src/**/*.ts", + "run": "deno run --allow-all src/mod.ts", + "test-init": "FEDIFY_TEST_MODE=true deno run --allow-all src/test/mod.ts test-init" + }, + "fmt": { + "exclude": [ + "src/templates/**" + ] + }, + "lint": { + "exclude": [ + "src/templates/**" + ], + "rules": { + "exclude": [ + "no-slow-types" + ] + } + }, + "test": { + "exclude": [ + "src/test/**" + ] + } +} diff --git a/packages/init/package.json b/packages/init/package.json new file mode 100644 index 000000000..5ce89eeac --- /dev/null +++ b/packages/init/package.json @@ -0,0 +1,72 @@ +{ + "name": "@fedify/init", + "version": "2.0.0", + "description": "Project initializer for Fedify", + "keywords": [ + "fedify", + "activitypub", + "cli", + "init" + ], + "homepage": "https://fedify.dev/", + "bugs": { + "url": "https://github.com/fedify-dev/fedify/issues" + }, + "license": "MIT", + "author": { + "name": "ChanHaeng Lee", + "email": "2chanhaeng@gmail.com", + "url": "https://chomu.dev/" + }, + "funding": [ + "https://opencollective.com/fedify", + "https://github.com/sponsors/dahlia" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/fedify-dev/fedify.git", + "directory": "packages/init" + }, + "engines": { + "node": ">=20.0.0", + "bun": ">=1.2.0", + "denp": ">=2.0.0" + }, + "type": "module", + "main": "./dist/mod.js", + "types": "./dist/mod.d.ts", + "exports": { + ".": { + "types": "./dist/mod.d.ts", + "import": "./dist/mod.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist/", + "package.json", + "README.md" + ], + "dependencies": { + "@fxts/core": "catalog:", + "@inquirer/prompts": "^7.8.4", + "@logtape/logtape": "catalog:", + "@optique/core": "catalog:", + "@optique/run": "catalog:", + "chalk": "catalog:", + "es-toolkit": "catalog:", + "inquirer-toggle": "^1.0.1" + }, + "devDependencies": { + "@types/node": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:" + }, + "scripts": { + "build:self": "tsdown", + "build": "pnpm --filter @fedify/init... run build:self", + "prepack": "pnpm build", + "prepublish": "pnpm build", + "test-init": "deno task test-init" + } +} diff --git a/packages/cli/src/init/action/configs.ts b/packages/init/src/action/configs.ts similarity index 96% rename from packages/cli/src/init/action/configs.ts rename to packages/init/src/action/configs.ts index f742ac40e..2d58b87bd 100644 --- a/packages/cli/src/init/action/configs.ts +++ b/packages/init/src/action/configs.ts @@ -10,7 +10,6 @@ import { import { uniq } from "es-toolkit"; import { realpathSync } from "node:fs"; import { join as joinPath, relative } from "node:path"; -import { merge } from "../../utils.ts"; import biome from "../json/biome.json" with { type: "json" }; import vscodeSettingsForDeno from "../json/vscode-settings-for-deno.json" with { type: "json", @@ -19,6 +18,7 @@ import vscodeSettings from "../json/vscode-settings.json" with { type: "json", }; import type { InitCommandData } from "../types.ts"; +import { merge } from "../utils.ts"; import { PACKAGES_PATH } from "./const.ts"; import { getDependencies, getDevDependencies, joinDepsReg } from "./deps.ts"; @@ -38,6 +38,7 @@ export const loadDenoConfig = ( unstable: getUnstable(data), nodeModulesDir: "auto", imports: joinDepsReg("deno")(getDependencies(data)), + lint: { plugins: ["jsr:@fedify/lint"] }, ...(data.testMode ? { links: getLinks(data) } : {}), }, }); @@ -116,7 +117,7 @@ export const devToolConfigs = { }, vscExt: { path: joinPath(".vscode", "extensions.json"), - data: { recommendations: ["biomejs.biome"] }, + data: { recommendations: ["biomejs.biome", "dbaeumer.vscode-eslint"] }, }, vscSet: { path: joinPath(".vscode", "settings.json"), diff --git a/packages/cli/src/init/action/const.ts b/packages/init/src/action/const.ts similarity index 92% rename from packages/cli/src/init/action/const.ts rename to packages/init/src/action/const.ts index c1ac4820f..03885ef54 100644 --- a/packages/cli/src/init/action/const.ts +++ b/packages/init/src/action/const.ts @@ -2,8 +2,7 @@ import { join as joinPath } from "node:path"; export const PACKAGES_PATH = joinPath( import.meta.dirname!, // action - "..", // init "..", // src - "..", // cli + "..", // init "..", // packages ); diff --git a/packages/cli/src/init/action/deps.ts b/packages/init/src/action/deps.ts similarity index 99% rename from packages/cli/src/init/action/deps.ts rename to packages/init/src/action/deps.ts index b65be1665..bbcd1cd9f 100644 --- a/packages/cli/src/init/action/deps.ts +++ b/packages/init/src/action/deps.ts @@ -8,7 +8,7 @@ import { when, } from "@fxts/core"; import { join as joinPath } from "node:path"; -import { merge, replace } from "../../utils.ts"; +import { merge, replace } from "../utils.ts"; import { PACKAGE_VERSION } from "../lib.ts"; import type { InitCommandData, PackageManager } from "../types.ts"; import { PACKAGES_PATH } from "./const.ts"; diff --git a/packages/cli/src/init/action/dir.ts b/packages/init/src/action/dir.ts similarity index 100% rename from packages/cli/src/init/action/dir.ts rename to packages/init/src/action/dir.ts diff --git a/packages/cli/src/init/action/env.ts b/packages/init/src/action/env.ts similarity index 91% rename from packages/cli/src/init/action/env.ts rename to packages/init/src/action/env.ts index ac37fa887..edb63bfde 100644 --- a/packages/cli/src/init/action/env.ts +++ b/packages/init/src/action/env.ts @@ -1,5 +1,5 @@ import { entries, forEach, pipeLazy, tap, toArray, when } from "@fxts/core"; -import { notEmpty } from "../../utils.ts"; +import { notEmpty } from "../utils.ts"; import type { InitCommandIo } from "../types.ts"; import { noticeConfigEnv, noticeEnvKeyValue } from "./notice.ts"; diff --git a/packages/cli/src/init/action/install.ts b/packages/init/src/action/install.ts similarity index 91% rename from packages/cli/src/init/action/install.ts rename to packages/init/src/action/install.ts index 8d5a69d56..d9392e24e 100644 --- a/packages/cli/src/init/action/install.ts +++ b/packages/init/src/action/install.ts @@ -1,5 +1,5 @@ import { apply, pipe } from "@fxts/core"; -import { CommandError, runSubCommand } from "../../utils.ts"; +import { CommandError, runSubCommand } from "../utils.ts"; import type { InitCommandData } from "../types.ts"; const installDependencies = (data: InitCommandData) => diff --git a/packages/cli/src/init/action/mod.ts b/packages/init/src/action/mod.ts similarity index 69% rename from packages/cli/src/init/action/mod.ts rename to packages/init/src/action/mod.ts index d1c343ba0..918966c98 100644 --- a/packages/cli/src/init/action/mod.ts +++ b/packages/init/src/action/mod.ts @@ -1,9 +1,9 @@ import { pipe, tap, unless, when } from "@fxts/core"; import process from "node:process"; -import { set } from "../../utils.ts"; import askOptions from "../ask/mod.ts"; import type { InitCommand } from "../command.ts"; import type { InitCommandData } from "../types.ts"; +import { set } from "../utils.ts"; import { makeDirIfHyd } from "./dir.ts"; import recommendConfigEnv from "./env.ts"; import installDependencies from "./install.ts"; @@ -20,20 +20,19 @@ import setData from "./set.ts"; import { hasCommand, isDry } from "./utils.ts"; /** - * options: InitCommand - * ├ drawDinosaur - * ┌─────┴──────┐ - * │ askOptions │ InitCommand -> InitCommandOptions - * └─────┬──────┘ - * ├ noticeOptions - * ┌────┴────┐ - * │ setData │ InitCommandOptions -> InitCommandData - * └────┬────┘ - * ┌─┴─┐ isDry - * handleDryRun ┤ ├ handleHydRun - * └─┬─┘ - * ├ recommendConfigEnv - * ├ noticeHowToRun + * Execution flow of the `runInit` function: + * + * 1. Receives options of type `InitCommand`. + * 2. Prints a dinosaur ASCII art via `drawDinosaur`. + * 3. Prompts the user for options via `askOptions`, + * converting `InitCommand` into `InitCommandOptions`. + * 4. Displays the selected options via `noticeOptions`. + * 5. Converts `InitCommandOptions` into `InitCommandData` via `setData`. + * 6. Branches based on `isDry`: + * - If dry run, executes `handleDryRun`. + * - Otherwise, executes `handleHydRun`. + * 7. Recommends configuration environment via `recommendConfigEnv`. + * 8. Shows how to run the project via `noticeHowToRun`. */ const runInit = (options: InitCommand) => pipe( diff --git a/packages/cli/src/init/action/notice.ts b/packages/init/src/action/notice.ts similarity index 99% rename from packages/cli/src/init/action/notice.ts rename to packages/init/src/action/notice.ts index d9db16124..802efce1a 100644 --- a/packages/cli/src/init/action/notice.ts +++ b/packages/init/src/action/notice.ts @@ -5,7 +5,7 @@ import { printErrorMessage, printMessage, type RequiredNotNull, -} from "../../utils.ts"; +} from "../utils.ts"; import type { InitCommand } from "../command.ts"; import type { InitCommandData } from "../types.ts"; diff --git a/packages/cli/src/init/action/patch.ts b/packages/init/src/action/patch.ts similarity index 99% rename from packages/cli/src/init/action/patch.ts rename to packages/init/src/action/patch.ts index 9365efa1f..868fc3f7a 100644 --- a/packages/cli/src/init/action/patch.ts +++ b/packages/init/src/action/patch.ts @@ -1,7 +1,7 @@ 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 { formatJson, merge, replaceAll, set } from "../utils.ts"; import { createFile, throwUnlessNotExists } from "../lib.ts"; import type { InitCommandData } from "../types.ts"; import { diff --git a/packages/cli/src/init/action/precommand.ts b/packages/init/src/action/precommand.ts similarity index 92% rename from packages/cli/src/init/action/precommand.ts rename to packages/init/src/action/precommand.ts index af6527e90..039652b91 100644 --- a/packages/cli/src/init/action/precommand.ts +++ b/packages/init/src/action/precommand.ts @@ -1,4 +1,4 @@ -import { CommandError, exit, runSubCommand } from "../../utils.ts"; +import { CommandError, exit, runSubCommand } from "../utils.ts"; import type { InitCommandData } from "../types.ts"; /** diff --git a/packages/cli/src/init/action/recommend.ts b/packages/init/src/action/recommend.ts similarity index 96% rename from packages/cli/src/init/action/recommend.ts rename to packages/init/src/action/recommend.ts index 3077589a6..e82d8011d 100644 --- a/packages/cli/src/init/action/recommend.ts +++ b/packages/init/src/action/recommend.ts @@ -1,5 +1,5 @@ import { map, peek, pipeLazy, tap, unless, when } from "@fxts/core"; -import { notEmpty } from "../../utils.ts"; +import { notEmpty } from "../utils.ts"; import type { InitCommandIo } from "../types.ts"; import { getDependencies, getDevDependencies } from "./deps.ts"; import { diff --git a/packages/cli/src/init/action/set.ts b/packages/init/src/action/set.ts similarity index 97% rename from packages/cli/src/init/action/set.ts rename to packages/init/src/action/set.ts index 74e2f195b..b2cadb295 100644 --- a/packages/cli/src/init/action/set.ts +++ b/packages/init/src/action/set.ts @@ -2,7 +2,7 @@ import { pipe } from "@fxts/core"; import { existsSync } from "node:fs"; import { realpath } from "node:fs/promises"; import { basename, normalize } from "node:path"; -import { merge, set } from "../../utils.ts"; +import { merge, set } from "../utils.ts"; import { kvStores, messageQueues } from "../lib.ts"; import type { InitCommandData, diff --git a/packages/cli/src/init/action/templates.ts b/packages/init/src/action/templates.ts similarity index 98% rename from packages/cli/src/init/action/templates.ts rename to packages/init/src/action/templates.ts index a74890146..4ff727219 100644 --- a/packages/cli/src/init/action/templates.ts +++ b/packages/init/src/action/templates.ts @@ -1,6 +1,6 @@ import { entries, join, map, pipe } from "@fxts/core"; import { toMerged } from "es-toolkit"; -import { replace } from "../../utils.ts"; +import { replace } from "../utils.ts"; import { readTemplate } from "../lib.ts"; import type { InitCommandData, PackageManager } from "../types.ts"; diff --git a/packages/cli/src/init/action/utils.ts b/packages/init/src/action/utils.ts similarity index 100% rename from packages/cli/src/init/action/utils.ts rename to packages/init/src/action/utils.ts diff --git a/packages/cli/src/init/ask/dir.ts b/packages/init/src/ask/dir.ts similarity index 97% rename from packages/cli/src/init/ask/dir.ts rename to packages/init/src/ask/dir.ts index b4c99acce..513de170c 100644 --- a/packages/cli/src/init/ask/dir.ts +++ b/packages/init/src/ask/dir.ts @@ -3,7 +3,7 @@ import { input } from "@inquirer/prompts"; import { message } from "@optique/core/message"; import { printError } from "@optique/run"; import toggle from "inquirer-toggle"; -import { getCwd, getOsType, runSubCommand } from "../../utils.ts"; +import { getCwd, getOsType, runSubCommand } from "../utils.ts"; import { isDirectoryEmpty, logger } from "../lib.ts"; /** diff --git a/packages/cli/src/init/ask/kv.ts b/packages/init/src/ask/kv.ts similarity index 98% rename from packages/cli/src/init/ask/kv.ts rename to packages/init/src/ask/kv.ts index 33f0ebdce..8fa2b1a89 100644 --- a/packages/cli/src/init/ask/kv.ts +++ b/packages/init/src/ask/kv.ts @@ -1,6 +1,6 @@ import { pipe, tap, throwError, unless, when } from "@fxts/core/index.js"; import { select } from "@inquirer/prompts"; -import { printErrorMessage } from "../../utils.ts"; +import { printErrorMessage } from "../utils.ts"; import { KV_STORE } from "../const.ts"; import { isTest, kvStores } from "../lib.ts"; import type { KvStore, PackageManager } from "../types.ts"; diff --git a/packages/cli/src/init/ask/mod.ts b/packages/init/src/ask/mod.ts similarity index 100% rename from packages/cli/src/init/ask/mod.ts rename to packages/init/src/ask/mod.ts diff --git a/packages/cli/src/init/ask/mq.ts b/packages/init/src/ask/mq.ts similarity index 98% rename from packages/cli/src/init/ask/mq.ts rename to packages/init/src/ask/mq.ts index 27c906da3..7527d272a 100644 --- a/packages/cli/src/init/ask/mq.ts +++ b/packages/init/src/ask/mq.ts @@ -1,6 +1,6 @@ import { pipe, tap, throwError, unless, when } from "@fxts/core/index.js"; import { select } from "@inquirer/prompts"; -import { printErrorMessage } from "../../utils.ts"; +import { printErrorMessage } from "../utils.ts"; import { MESSAGE_QUEUE } from "../const.ts"; import { isTest, messageQueues } from "../lib.ts"; import type { MessageQueue, PackageManager } from "../types.ts"; diff --git a/packages/cli/src/init/ask/pm.ts b/packages/init/src/ask/pm.ts similarity index 80% rename from packages/cli/src/init/ask/pm.ts rename to packages/init/src/ask/pm.ts index 1f9e1c91f..b4c309bd5 100644 --- a/packages/cli/src/init/ask/pm.ts +++ b/packages/init/src/ask/pm.ts @@ -1,8 +1,16 @@ +import { pipe, when } from "@fxts/core"; import { select } from "@inquirer/prompts"; import { message } from "@optique/core/message"; import { print } from "@optique/run"; import { PACKAGE_MANAGER } from "../const.ts"; -import { getInstallUrl, getLabel, isPackageManagerAvailable } from "../lib.ts"; +import { + getInstallUrl, + isPackageManagerAvailable, + kvStores, + messageQueues, + packageManagers, + runtimes, +} from "../lib.ts"; import type { PackageManager, WebFramework } from "../types.ts"; import webFrameworks from "../webframeworks.ts"; @@ -56,3 +64,15 @@ const noticeInstallUrl = (pm: PackageManager) => { print(message` You can install it from following link: ${url}`); print(message` or choose another package manager:`); }; + +const getLabel = (name: string) => + pipe( + name, + whenHasLabel(webFrameworks), + whenHasLabel(packageManagers), + whenHasLabel(messageQueues), + whenHasLabel(kvStores), + whenHasLabel(runtimes), + ); +const whenHasLabel = >(desc: T) => + when((name: string) => name in desc, (name) => desc[name as keyof T].label); diff --git a/packages/cli/src/init/ask/wf.ts b/packages/init/src/ask/wf.ts similarity index 100% rename from packages/cli/src/init/ask/wf.ts rename to packages/init/src/ask/wf.ts diff --git a/packages/cli/src/init/command.ts b/packages/init/src/command.ts similarity index 80% rename from packages/cli/src/init/command.ts rename to packages/init/src/command.ts index fb51bd881..020b25524 100644 --- a/packages/cli/src/init/command.ts +++ b/packages/init/src/command.ts @@ -14,7 +14,6 @@ import { or, } from "@optique/core"; import { path } from "@optique/run"; -import { debugOption } from "../globals.ts"; import { KV_STORE, MESSAGE_QUEUE, @@ -57,23 +56,33 @@ const messageQueue = optional(option( }, )); +const debugOption = object("Global options", { + debug: option("-d", "--debug", { + description: message`Enable debug mode.`, + }), +}); + +export const initOptions = object("Initialization options", { + dir: optional(argument(path({ metavar: "DIR" }), { + description: + message`The project directory to initialize. If a specified directory does not exist, it will be created.`, + })), + webFramework, + packageManager, + kvStore, + messageQueue, + dryRun: option("--dry-run", { + description: message`Perform a trial run with no changes made.`, + }), + debugOption, +}); + export const initCommand = command( "init", - object("Initialization options", { - command: constant("init"), - dir: optional(argument(path({ metavar: "DIR" }), { - description: - message`The project directory to initialize. If a specified directory does not exist, it will be created.`, - })), - webFramework, - packageManager, - kvStore, - messageQueue, - dryRun: option("--dry-run", { - description: message`Perform a trial run with no changes made.`, - }), - debugOption, - }), + merge( + initOptions, + object({ command: constant("init") }), + ), { brief: message`Initialize a new Fedify project directory.`, description: message`Initialize a new Fedify project directory. @@ -113,7 +122,7 @@ export const testInitCommand = command( optional(or(noHydRun, noDryRun)), ), { - brief: message`Test an initializing command .`, + brief: message`Test an initializing command.`, description: message`Test an initializing command on temporary directories. Unless you specify all options (${optionNames(["-w", "--web-framework"])}, ${ diff --git a/packages/cli/src/init/const.ts b/packages/init/src/const.ts similarity index 82% rename from packages/cli/src/init/const.ts rename to packages/init/src/const.ts index 2b43ffcb2..0cbfec5b9 100644 --- a/packages/cli/src/init/const.ts +++ b/packages/init/src/const.ts @@ -8,3 +8,4 @@ export const WEB_FRAMEWORK = [ ] as const; export const MESSAGE_QUEUE = ["denokv", "redis", "postgres", "amqp"] as const; export const KV_STORE = ["denokv", "redis", "postgres"] as const; +export const DB_TO_CHECK = ["redis", "postgres", "amqp"] as const; diff --git a/packages/cli/src/init/json/biome.json b/packages/init/src/json/biome.json similarity index 76% rename from packages/cli/src/init/json/biome.json rename to packages/init/src/json/biome.json index 5ac9bb449..902345891 100644 --- a/packages/cli/src/init/json/biome.json +++ b/packages/init/src/json/biome.json @@ -9,9 +9,6 @@ "indentWidth": 2 }, "linter": { - "enabled": true, - "rules": { - "recommended": true - } + "enabled": false } } diff --git a/packages/init/src/json/db-to-check.json b/packages/init/src/json/db-to-check.json new file mode 100644 index 000000000..46a64c36e --- /dev/null +++ b/packages/init/src/json/db-to-check.json @@ -0,0 +1,17 @@ +{ + "redis": { + "name": "Redis", + "defaultPort": 6379, + "documentation": "https://redis.io/docs/latest/operate/oss_and_stack/install/archive/install-redis/" + }, + "postgres": { + "name": "PostgreSQL", + "defaultPort": 5432, + "documentation": "https://www.postgresql.org/download/" + }, + "amqp": { + "name": "RabbitMQ", + "defaultPort": 5672, + "documentation": "https://www.rabbitmq.com/docs/download" + } +} diff --git a/packages/cli/src/init/json/kv.json b/packages/init/src/json/kv.json similarity index 100% rename from packages/cli/src/init/json/kv.json rename to packages/init/src/json/kv.json diff --git a/packages/cli/src/init/json/mq.json b/packages/init/src/json/mq.json similarity index 100% rename from packages/cli/src/init/json/mq.json rename to packages/init/src/json/mq.json diff --git a/packages/cli/src/init/json/pm.json b/packages/init/src/json/pm.json similarity index 100% rename from packages/cli/src/init/json/pm.json rename to packages/init/src/json/pm.json diff --git a/packages/cli/src/init/json/rt.json b/packages/init/src/json/rt.json similarity index 100% rename from packages/cli/src/init/json/rt.json rename to packages/init/src/json/rt.json diff --git a/packages/cli/src/init/json/vscode-settings-for-deno.json b/packages/init/src/json/vscode-settings-for-deno.json similarity index 100% rename from packages/cli/src/init/json/vscode-settings-for-deno.json rename to packages/init/src/json/vscode-settings-for-deno.json diff --git a/packages/cli/src/init/json/vscode-settings.json b/packages/init/src/json/vscode-settings.json similarity index 100% rename from packages/cli/src/init/json/vscode-settings.json rename to packages/init/src/json/vscode-settings.json diff --git a/packages/cli/src/init/lib.ts b/packages/init/src/lib.ts similarity index 89% rename from packages/cli/src/init/lib.ts rename to packages/init/src/lib.ts index a9c785f68..e8d43a806 100644 --- a/packages/cli/src/init/lib.ts +++ b/packages/init/src/lib.ts @@ -7,7 +7,6 @@ import { negate, pipe, throwIf, - when, } from "@fxts/core"; import { getLogger } from "@logtape/logtape"; import type { Message } from "@optique/core"; @@ -17,8 +16,7 @@ import { readFileSync } from "node:fs"; import { mkdir, readdir, writeFile } from "node:fs/promises"; import { dirname, join as joinPath } from "node:path"; import process from "node:process"; -import metadata from "../../deno.json" with { type: "json" }; -import { isNotFoundError, runSubCommand } from "../utils.ts"; +import metadata from "../deno.json" with { type: "json" }; import kv from "./json/kv.json" with { type: "json" }; import mq from "./json/mq.json" with { type: "json" }; import pm from "./json/pm.json" with { type: "json" }; @@ -30,7 +28,7 @@ import type { PackageManagers, Runtimes, } from "./types.ts"; -import webFrameworks from "./webframeworks.ts"; +import { isNotFoundError, runSubCommand } from "./utils.ts"; export const PACKAGE_VERSION = metadata.version; export const logger = getLogger(["fedify", "cli", "init"]); @@ -60,20 +58,8 @@ const convertPattern = ( ), fromEntries, ) as Record & { outputPattern: RegExp }>; -const packageManagers = convertPattern(pm) as PackageManagers; -const runtimes = convertPattern(rt) as Runtimes; - -export const getLabel = (name: string) => - pipe( - name, - whenHasLabel(webFrameworks), - whenHasLabel(packageManagers), - whenHasLabel(messageQueues), - whenHasLabel(kvStores), - whenHasLabel(runtimes), - ); -const whenHasLabel = >(desc: T) => - when((name: string) => name in desc, (name) => desc[name as keyof T].label); +export const packageManagers = convertPattern(pm) as PackageManagers; +export const runtimes = convertPattern(rt) as Runtimes; export const getInstallUrl = (pm: PackageManager) => packageManagers[pm].installUrl; diff --git a/packages/init/src/mod.ts b/packages/init/src/mod.ts new file mode 100644 index 000000000..5e8d6f7ee --- /dev/null +++ b/packages/init/src/mod.ts @@ -0,0 +1,2 @@ +export { default as runInit } from "./action/mod.ts"; +export { initCommand, initOptions } from "./command.ts"; diff --git a/packages/init/src/templates/defaults/eslint.config.ts.tpl b/packages/init/src/templates/defaults/eslint.config.ts.tpl new file mode 100644 index 000000000..9ac90a302 --- /dev/null +++ b/packages/init/src/templates/defaults/eslint.config.ts.tpl @@ -0,0 +1,3 @@ +import fedifyLint from "@fedify/lint"; + +export default fedifyLint; diff --git a/packages/cli/src/init/templates/defaults/federation.ts.tpl b/packages/init/src/templates/defaults/federation.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/defaults/federation.ts.tpl rename to packages/init/src/templates/defaults/federation.ts.tpl diff --git a/packages/cli/src/init/templates/defaults/logging.ts.tpl b/packages/init/src/templates/defaults/logging.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/defaults/logging.ts.tpl rename to packages/init/src/templates/defaults/logging.ts.tpl diff --git a/packages/cli/src/init/templates/elysia/index/bun.ts.tpl b/packages/init/src/templates/elysia/index/bun.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/elysia/index/bun.ts.tpl rename to packages/init/src/templates/elysia/index/bun.ts.tpl diff --git a/packages/cli/src/init/templates/elysia/index/deno.ts.tpl b/packages/init/src/templates/elysia/index/deno.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/elysia/index/deno.ts.tpl rename to packages/init/src/templates/elysia/index/deno.ts.tpl diff --git a/packages/cli/src/init/templates/elysia/index/node.ts.tpl b/packages/init/src/templates/elysia/index/node.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/elysia/index/node.ts.tpl rename to packages/init/src/templates/elysia/index/node.ts.tpl diff --git a/packages/cli/src/init/templates/express/app.ts.tpl b/packages/init/src/templates/express/app.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/express/app.ts.tpl rename to packages/init/src/templates/express/app.ts.tpl diff --git a/packages/cli/src/init/templates/express/index.ts.tpl b/packages/init/src/templates/express/index.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/express/index.ts.tpl rename to packages/init/src/templates/express/index.ts.tpl diff --git a/packages/cli/src/init/templates/hono/app.tsx.tpl b/packages/init/src/templates/hono/app.tsx.tpl similarity index 100% rename from packages/cli/src/init/templates/hono/app.tsx.tpl rename to packages/init/src/templates/hono/app.tsx.tpl diff --git a/packages/cli/src/init/templates/hono/index/bun.ts.tpl b/packages/init/src/templates/hono/index/bun.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/hono/index/bun.ts.tpl rename to packages/init/src/templates/hono/index/bun.ts.tpl diff --git a/packages/cli/src/init/templates/hono/index/deno.ts.tpl b/packages/init/src/templates/hono/index/deno.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/hono/index/deno.ts.tpl rename to packages/init/src/templates/hono/index/deno.ts.tpl diff --git a/packages/cli/src/init/templates/hono/index/node.ts.tpl b/packages/init/src/templates/hono/index/node.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/hono/index/node.ts.tpl rename to packages/init/src/templates/hono/index/node.ts.tpl diff --git a/packages/cli/src/init/templates/next/middleware.ts.tpl b/packages/init/src/templates/next/middleware.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/next/middleware.ts.tpl rename to packages/init/src/templates/next/middleware.ts.tpl diff --git a/packages/cli/src/init/templates/nitro/.env.test.tpl b/packages/init/src/templates/nitro/.env.test.tpl similarity index 100% rename from packages/cli/src/init/templates/nitro/.env.test.tpl rename to packages/init/src/templates/nitro/.env.test.tpl diff --git a/packages/cli/src/init/templates/nitro/nitro.config.ts.tpl b/packages/init/src/templates/nitro/nitro.config.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/nitro/nitro.config.ts.tpl rename to packages/init/src/templates/nitro/nitro.config.ts.tpl diff --git a/packages/cli/src/init/templates/nitro/server/error.ts.tpl b/packages/init/src/templates/nitro/server/error.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/nitro/server/error.ts.tpl rename to packages/init/src/templates/nitro/server/error.ts.tpl diff --git a/packages/cli/src/init/templates/nitro/server/middleware/federation.ts.tpl b/packages/init/src/templates/nitro/server/middleware/federation.ts.tpl similarity index 100% rename from packages/cli/src/init/templates/nitro/server/middleware/federation.ts.tpl rename to packages/init/src/templates/nitro/server/middleware/federation.ts.tpl diff --git a/packages/cli/src/init/test/action.ts b/packages/init/src/test/action.ts similarity index 87% rename from packages/cli/src/init/test/action.ts rename to packages/init/src/test/action.ts index 1babf9421..8b64f7d1f 100644 --- a/packages/cli/src/init/test/action.ts +++ b/packages/init/src/test/action.ts @@ -1,6 +1,7 @@ import { pipe, tap, when } from "@fxts/core"; -import { set } from "../../utils.ts"; +import { set } from "../utils.ts"; import type { TestInitCommand } from "../command.ts"; +import { checkRequiredDbs } from "./db.ts"; import { fillEmptyOptions } from "./fill.ts"; import runTests from "./run.ts"; import { @@ -17,6 +18,7 @@ const runTestInit = (options: TestInitCommand) => set("testDirPrefix", genTestDirPrefix), tap(emptyTestDir), fillEmptyOptions, + tap(checkRequiredDbs), tap(logTestDir), tap(when(isDryRun, runTests(true))), tap(when(isHydRun, runTests(false))), diff --git a/packages/cli/src/init/test/create.ts b/packages/init/src/test/create.ts similarity index 97% rename from packages/cli/src/init/test/create.ts rename to packages/init/src/test/create.ts index a71a6d804..44dd17a70 100644 --- a/packages/cli/src/init/test/create.ts +++ b/packages/init/src/test/create.ts @@ -3,14 +3,6 @@ import { values } from "@optique/core"; import { appendFile, mkdir } from "node:fs/promises"; import { join, sep } from "node:path"; import process from "node:process"; -import { - CommandError, - type GeneratedType, - printErrorMessage, - printMessage, - product, - runSubCommand, -} from "../../utils.ts"; import packageManagers from "../json/pm.json" with { type: "json" }; import { kvStores, messageQueues } from "../lib.ts"; import type { @@ -19,6 +11,14 @@ import type { PackageManager, WebFramework, } from "../types.ts"; +import { + CommandError, + type GeneratedType, + printErrorMessage, + printMessage, + product, + runSubCommand, +} from "../utils.ts"; import webFrameworks from "../webframeworks.ts"; import type { InitTestData, MultipleOption } from "./types.ts"; @@ -34,7 +34,7 @@ async ( const result = await runSubCommand( toArray(genInitCommand(testDir, dry, options)), { - cwd: join(import.meta.dirname!, "../../.."), + cwd: join(import.meta.dirname!, "../.."), stdio: ["ignore", "pipe", "pipe"], }, ); @@ -70,7 +70,7 @@ function* genInitCommand( yield "deno"; yield "run"; yield "-A"; - yield "src/mod.ts"; + yield "src/test/execute.ts"; yield "init"; yield testDir; yield "-w"; diff --git a/packages/init/src/test/db.ts b/packages/init/src/test/db.ts new file mode 100644 index 000000000..f25c351b0 --- /dev/null +++ b/packages/init/src/test/db.ts @@ -0,0 +1,67 @@ +import { concat, filter, pipe, toArray, uniq } from "@fxts/core"; +import { createConnection } from "node:net"; +import type { TestInitCommand } from "../command.ts"; +import { DB_TO_CHECK } from "../const.ts"; +import DB_INFO from "../json/db-to-check.json" with { type: "json" }; +import { printErrorMessage, printMessage } from "../utils.ts"; +import type { DbToCheckType, DefineAllOptions } from "./types.ts"; + +/** + * Checks if a given port is open by attempting a raw TCP connection to + * localhost at that port. This works reliably for non-HTTP services like + * Redis, PostgreSQL, or AMQP. + * @param port The port number to check. + * @param timeout The timeout in milliseconds. Defaults to 3000. + * @returns A promise that resolves to true if the port is open, else false. + */ +function isPortOpen(port: number, timeout = 3000): Promise { + return new Promise((resolve) => { + const socket = createConnection({ port, host: "localhost" }); + socket.setTimeout(timeout); + socket.once("connect", () => { + socket.destroy(); + resolve(true); + }); + socket.once("timeout", () => { + socket.destroy(); + resolve(false); + }); + socket.once("error", () => { + socket.destroy(); + resolve(false); + }); + }); +} + +const getRequiredDbs = ( + { kvStore, messageQueue }: DefineAllOptions, +): DbToCheckType[] => + pipe( + kvStore, + concat(messageQueue), + uniq, + filter((db): db is DbToCheckType => + DB_TO_CHECK.includes(db as DbToCheckType) + ), + toArray, + ); + +export async function checkRequiredDbs( + options: DefineAllOptions, +): Promise { + const dbs = Array.from(getRequiredDbs(options)); + if (dbs.length === 0) return; + + printMessage`Checking required databases...`; + + for (const db of dbs) { + const info = DB_INFO[db]; + const port = String(info.defaultPort); + const running = await isPortOpen(info.defaultPort); + if (running) { + printMessage` ${info.name} is running on port ${port}.`; + } else { + printErrorMessage`${info.name} is not running on port ${port}. Tests requiring ${info.name} may fail. Install: ${info.documentation}`; + } + } +} diff --git a/packages/init/src/test/execute.ts b/packages/init/src/test/execute.ts new file mode 100644 index 000000000..4607a039e --- /dev/null +++ b/packages/init/src/test/execute.ts @@ -0,0 +1,12 @@ +import { run } from "@optique/run"; +import runInit from "../action/mod.ts"; +import { initCommand } from "../command.ts"; + +export default async function executeInit() { + await runInit(run(initCommand, { + programName: "fedify-init", + help: "both", + })); +} + +await executeInit(); diff --git a/packages/cli/src/init/test/fill.ts b/packages/init/src/test/fill.ts similarity index 100% rename from packages/cli/src/init/test/fill.ts rename to packages/init/src/test/fill.ts diff --git a/packages/cli/src/init/test/lookup.ts b/packages/init/src/test/lookup.ts similarity index 99% rename from packages/cli/src/init/test/lookup.ts rename to packages/init/src/test/lookup.ts index 821c3ab49..c070c0ba6 100644 --- a/packages/cli/src/init/test/lookup.ts +++ b/packages/init/src/test/lookup.ts @@ -6,7 +6,7 @@ import { createWriteStream } from "node:fs"; import { join, sep } from "node:path"; import process from "node:process"; import type Stream from "node:stream"; -import { printErrorMessage, printMessage, runSubCommand } from "../../utils.ts"; +import { printErrorMessage, printMessage, runSubCommand } from "../utils.ts"; import { getDevCommand } from "../lib.ts"; import type { KvStore, diff --git a/packages/cli/src/init/test/mod.ts b/packages/init/src/test/mod.ts similarity index 94% rename from packages/cli/src/init/test/mod.ts rename to packages/init/src/test/mod.ts index 2ccae1dc1..319a625b3 100644 --- a/packages/cli/src/init/test/mod.ts +++ b/packages/init/src/test/mod.ts @@ -1,4 +1,3 @@ -#!/usr/bin/env node import { run } from "@optique/run"; import { testInitCommand } from "../command.ts"; import runTestInit from "./action.ts"; diff --git a/packages/cli/src/init/test/run.ts b/packages/init/src/test/run.ts similarity index 96% rename from packages/cli/src/init/test/run.ts rename to packages/init/src/test/run.ts index a1cd0a1f5..ec31d0102 100644 --- a/packages/cli/src/init/test/run.ts +++ b/packages/init/src/test/run.ts @@ -1,7 +1,7 @@ import { always, filter, map, pipe, tap, unless } from "@fxts/core"; import { optionNames } from "@optique/core"; import { join } from "node:path"; -import { printMessage } from "../../utils.ts"; +import { printMessage } from "../utils.ts"; import createTestApp, { filterOptions, generateTestCases } from "./create.ts"; import runServerAndLookupUser from "./lookup.ts"; import type { InitTestData } from "./types.ts"; diff --git a/packages/cli/src/init/test/types.ts b/packages/init/src/test/types.ts similarity index 87% rename from packages/cli/src/init/test/types.ts rename to packages/init/src/test/types.ts index 4bba63a9f..9b2e61af9 100644 --- a/packages/cli/src/init/test/types.ts +++ b/packages/init/src/test/types.ts @@ -1,4 +1,5 @@ import type { TestInitCommand } from "../command.ts"; +import type { DB_TO_CHECK } from "../const.ts"; export interface InitTestData extends DefineAllOptions { runId: string; @@ -25,3 +26,5 @@ export type DefineAllOptions = & Omit & { [K in MultipleOption]: (TestInitCommand[K][number] & string)[] } & { [R in RunMode]: boolean }; + +export type DbToCheckType = (typeof DB_TO_CHECK)[number]; diff --git a/packages/cli/src/init/test/utils.ts b/packages/init/src/test/utils.ts similarity index 79% rename from packages/cli/src/init/test/utils.ts rename to packages/init/src/test/utils.ts index a43804a41..b7ed69b1d 100644 --- a/packages/cli/src/init/test/utils.ts +++ b/packages/init/src/test/utils.ts @@ -1,7 +1,7 @@ -import { rmdir } from "node:fs/promises"; +import { rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { printMessage } from "../../utils.ts"; +import { printMessage } from "../utils.ts"; export const genRunId = () => `${Date.now()}-${Math.random().toString(36).slice(2)}`; @@ -12,7 +12,7 @@ export const genTestDirPrefix = ({ runId }: T) => export const emptyTestDir = < T extends { testDirPrefix: string }, >({ testDirPrefix }: T) => - rmdir(testDirPrefix, { recursive: true }).catch(() => {}); + rm(testDirPrefix, { recursive: true }).catch(() => {}); export const logTestDir = < T extends { runId: string; testDirPrefix: string }, diff --git a/packages/cli/src/init/types.ts b/packages/init/src/types.ts similarity index 98% rename from packages/cli/src/init/types.ts rename to packages/init/src/types.ts index 532ada05c..689c19128 100644 --- a/packages/cli/src/init/types.ts +++ b/packages/init/src/types.ts @@ -1,5 +1,4 @@ import type { Message } from "@optique/core"; -import type { RequiredNotNull } from "../utils.ts"; import type { InitCommand } from "./command.ts"; import type { KV_STORE, @@ -7,6 +6,7 @@ import type { PACKAGE_MANAGER, WEB_FRAMEWORK, } from "./const.ts"; +import type { RequiredNotNull } from "./utils.ts"; export type PackageManager = typeof PACKAGE_MANAGER[number]; export type WebFramework = typeof WEB_FRAMEWORK[number]; diff --git a/packages/init/src/utils.ts b/packages/init/src/utils.ts new file mode 100644 index 000000000..d4ef23cbd --- /dev/null +++ b/packages/init/src/utils.ts @@ -0,0 +1,189 @@ +import { isObject } from "@fxts/core"; +import { message } from "@optique/core"; +import { print, printError } from "@optique/run"; +import { Chalk } from "chalk"; +import { flow, toMerged } from "es-toolkit"; +import { spawn } from "node:child_process"; +import process from "node:process"; + +export const colorEnabled: boolean = process.stdout.isTTY && + !("NO_COLOR" in process.env && process.env.NO_COLOR !== ""); + +export const colors = new Chalk(colorEnabled ? {} : { level: 0 }); + +export type RequiredNotNull = { + [P in keyof T]: NonNullable; +}; + +export const isPromise = (value: unknown): value is Promise => + value instanceof Promise; + +export function set( + key: K, + f: (value: T) => S, +): ( + obj: T, +) => S extends Promise ? Promise }> + : T & { [P in K]: S } { + return ((obj) => { + const result = f(obj); + if (isPromise ? U : never>(result)) { + return result.then((value) => ({ ...obj, [key]: value })) as S extends + Promise ? Promise< + T & { [P in K]: Awaited } + > + : never; + } + return ({ ...obj, [key]: result }) as S extends Promise ? never + : T & { [P in K]: S }; + }); +} + +export const merge = + (source: Parameters[1] = {}) => + (target: Parameters[0] = {}) => toMerged(target, source); + +export const replace = ( + pattern: string | RegExp, + replacement: string | ((substring: string, ...args: unknown[]) => string), +) => +(text: string): string => text.replace(pattern, replacement as string); + +export const replaceAll = ( + pattern: string | RegExp, + replacement: string | ((substring: string, ...args: unknown[]) => string), +) => +(text: string): string => text.replaceAll(pattern, replacement as string); + +export const formatJson = (obj: unknown) => JSON.stringify(obj, null, 2) + "\n"; + +export const notEmpty = (s: T) => + s.length > 0; + +export const isNotFoundError = (e: unknown): e is { code: "ENOENT" } => + isObject(e) && "code" in e && e.code === "ENOENT"; + +export class CommandError extends Error { + public commandLine: string; + constructor( + message: string, + public stdout: string, + public stderr: string, + public code: number, + public command: string[], + ) { + super(message); + this.name = "CommandError"; + this.commandLine = command.join(" "); + } +} + +export const runSubCommand = async [2]>( + command: string[], + options: Opt, +): Promise<{ + stdout: string; + stderr: string; +}> => { + const commands = command.reduce((acc, cur) => { + if (cur === "&&") { + acc.push([]); + } else { + if (acc.length === 0) acc.push([]); + acc[acc.length - 1].push(cur); + } + return acc; + }, []); + + const results = { stdout: "", stderr: "" }; + + for (const cmd of commands) { + try { + const result = await runSingularCommand(cmd, options); + results.stdout += (results.stdout ? "\n" : "") + result.stdout; + results.stderr += (results.stderr ? "\n" : "") + result.stderr; + } catch (error) { + if (error instanceof CommandError) { + results.stdout += (results.stdout ? "\n" : "") + error.stdout; + results.stderr += (results.stderr ? "\n" : "") + error.stderr; + } + throw error; + } + } + return results; +}; + +const runSingularCommand = ( + command: string[], + options: Parameters[2], +) => + new Promise<{ + stdout: string; + stderr: string; + }>((resolve, reject) => { + let stdout = ""; + let stderr = ""; + const child = spawn(command[0], command.slice(1), options); + + child.stdout?.on("data", (data) => { + stdout += data.toString(); + }); + child.stderr?.on("data", (data) => { + stderr += data.toString(); + }); + + child.on("close", (code) => { + if (code === 0) { + resolve({ + stdout: stdout.trim(), + stderr: stderr.trim(), + }); + } else { + reject( + new CommandError( + `Command exited with code ${code ?? "unknown"}`, + stdout.trim(), + stderr.trim(), + code ?? -1, + command, + ), + ); + } + }); + + child.on("error", (error) => { + reject(error); + }); + }); + +export const getCwd = () => process.cwd(); + +export const getOsType = () => process.platform; + +export const exit = (code: number) => process.exit(code); + +export type ItersItems[]> = T extends [] ? [] + : T extends [infer Head, ...infer Tail] + ? Head extends Iterable + ? Tail extends Iterable[] ? [Item, ...ItersItems] + : [Item] + : never + : never; + +export function* product[]>( + ...[head, ...tail]: T +): Generator> { + if (!head) yield [] as ItersItems; + else { + for (const x of head) { + for (const xs of product(...tail)) yield [x, ...xs] as ItersItems; + } + } +} + +export type GeneratedType = T extends + Generator ? R : never; + +type PrintMessage = (...args: Parameters) => void; +export const printMessage: PrintMessage = flow(message, print); +export const printErrorMessage: PrintMessage = flow(message, printError); diff --git a/packages/cli/src/init/webframeworks.ts b/packages/init/src/webframeworks.ts similarity index 77% rename from packages/cli/src/init/webframeworks.ts rename to packages/init/src/webframeworks.ts index abc7f0b1b..612a232de 100644 --- a/packages/cli/src/init/webframeworks.ts +++ b/packages/init/src/webframeworks.ts @@ -1,5 +1,4 @@ import { pipe } from "@fxts/core"; -import { replace } from "../utils.ts"; import { PACKAGE_MANAGER } from "./const.ts"; import { getInstruction, @@ -10,6 +9,7 @@ import { readTemplate, } from "./lib.ts"; import type { WebFrameworks } from "./types.ts"; +import { replace } from "./utils.ts"; const webFrameworks: WebFrameworks = { hono: { @@ -18,6 +18,7 @@ const webFrameworks: WebFrameworks = { init: ({ projectName, packageManager: pm }) => ({ dependencies: pm === "deno" ? { + ...defaultDenoDependencies, "@std/dotenv": "^0.225.2", "@hono/hono": "^4.5.0", "@hongminhee/x-forwarded-fetch": "^0.2.0", @@ -37,7 +38,10 @@ const webFrameworks: WebFrameworks = { "x-forwarded-fetch": "^0.2.0", "@fedify/hono": PACKAGE_VERSION, }, - devDependencies: pm === "bun" ? { "@types/bun": "^1.1.6" } : {}, + devDependencies: { + ...defaultDevDependencies, + ...(pm === "bun" ? { "@types/bun": "^1.1.6" } : {}), + }, federationFile: "src/federation.ts", loggingFile: "src/logging.ts", files: { @@ -50,6 +54,9 @@ const webFrameworks: WebFrameworks = { "src/index.ts": readTemplate( `hono/index/${packageManagerToRuntime(pm)}.ts`, ), + ...(pm !== "deno" + ? { "eslint.config.ts": readTemplate("defaults/eslint.config.ts") } + : {}), }, compilerOptions: pm === "deno" ? undefined : { "lib": ["ESNext", "DOM"], @@ -74,6 +81,7 @@ const webFrameworks: WebFrameworks = { : pm === "bun" ? "bun run ./src/index.ts" : "dotenvx run -- node --import tsx ./src/index.ts", + ...(pm !== "deno" ? { "lint": "eslint ." } : {}), }, instruction: getInstruction(pm, 8000), }), @@ -85,6 +93,7 @@ const webFrameworks: WebFrameworks = { init: ({ projectName, packageManager: pm }) => ({ dependencies: pm === "deno" ? { + ...defaultDenoDependencies, elysia: "npm:elysia@^1.3.6", "@fedify/elysia": PACKAGE_VERSION, } @@ -104,21 +113,23 @@ const webFrameworks: WebFrameworks = { } : {}), }, - devDependencies: pm === "bun" - ? { "@types/bun": "^1.2.19" } - : pm === "deno" - ? {} - : { + devDependencies: { + ...(pm === "bun" ? { "@types/bun": "^1.2.19" } : { tsx: "^4.21.0", "@types/node": "^25.0.3", typescript: "^5.9.3", - }, + }), + ...defaultDevDependencies, + }, federationFile: "src/federation.ts", loggingFile: "src/logging.ts", files: { "src/index.ts": readTemplate( `elysia/index/${packageManagerToRuntime(pm)}.ts`, ).replace(/\/\* logger \*\//, projectName), + ...(pm !== "deno" + ? { "eslint.config.ts": readTemplate("defaults/eslint.config.ts") } + : {}), }, compilerOptions: pm === "deno" || pm === "bun" ? undefined : { "lib": ["ESNext", "DOM"], @@ -144,6 +155,7 @@ const webFrameworks: WebFrameworks = { "build": "tsc src/index.ts --outDir dist", "start": "NODE_ENV=production node dist/index.js", }), + ...(pm !== "deno" ? { "lint": "eslint ." } : {}), }, instruction: getInstruction(pm, 3000), }), @@ -159,10 +171,12 @@ const webFrameworks: WebFrameworks = { ...(pm !== "deno" && pm !== "bun" ? { "@dotenvx/dotenvx": "^1.14.1", tsx: "^4.17.0" } : {}), + ...(pm === "deno" ? defaultDenoDependencies : {}), }, devDependencies: { "@types/express": "^4.17.21", ...(pm === "bun" ? { "@types/bun": "^1.1.6" } : {}), + ...defaultDevDependencies, }, federationFile: "src/federation.ts", loggingFile: "src/logging.ts", @@ -170,6 +184,9 @@ const webFrameworks: WebFrameworks = { "src/app.ts": readTemplate("express/app.ts") .replace(/\/\* logger \*\//, projectName), "src/index.ts": readTemplate("express/index.ts"), + ...(pm !== "deno" + ? { "eslint.config.ts": readTemplate("defaults/eslint.config.ts") } + : {}), }, compilerOptions: pm === "deno" ? undefined : { "lib": ["ESNext", "DOM"], @@ -192,6 +209,7 @@ const webFrameworks: WebFrameworks = { : 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 ." } : {}), }, instruction: getInstruction(pm, 8000), }), @@ -202,7 +220,11 @@ const webFrameworks: WebFrameworks = { packageManagers: PACKAGE_MANAGER, init: ({ packageManager: pm, testMode }) => ({ command: getNitroInitCommand(pm), - dependencies: { "@fedify/h3": PACKAGE_VERSION }, + dependencies: { + "@fedify/h3": PACKAGE_VERSION, + ...(pm === "deno" ? defaultDenoDependencies : {}), + }, + devDependencies: defaultDevDependencies, federationFile: "server/federation.ts", loggingFile: "server/logging.ts", files: { @@ -214,6 +236,12 @@ const webFrameworks: WebFrameworks = { ...( testMode ? { ".env": readTemplate("nitro/.env.test") } : {} ), + ...(pm !== "deno" + ? { "eslint.config.ts": readTemplate("defaults/eslint.config.ts") } + : {}), + }, + tasks: { + ...(pm !== "deno" ? { "lint": "eslint ." } : {}), }, instruction: getInstruction(pm, 3000), }), @@ -225,14 +253,37 @@ const webFrameworks: WebFrameworks = { init: ({ packageManager: pm }) => ({ label: "Next.js", command: getNextInitCommand(pm), - dependencies: { "@fedify/next": PACKAGE_VERSION }, - devDependencies: { "@types/node": "^20.11.2" }, + dependencies: { + "@fedify/next": PACKAGE_VERSION, + ...(pm === "deno" ? defaultDenoDependencies : {}), + }, + devDependencies: { + "@types/node": "^20.11.2", + ...defaultDevDependencies, + }, federationFile: "federation/index.ts", loggingFile: "logging.ts", - files: { "middleware.ts": readTemplate("next/middleware.ts") }, + files: { + "middleware.ts": readTemplate("next/middleware.ts"), + ...(pm !== "deno" + ? { "eslint.config.ts": readTemplate("defaults/eslint.config.ts") } + : {}), + }, + tasks: { + ...(pm !== "deno" ? { "lint": "eslint ." } : {}), + }, instruction: getInstruction(pm, 3000), }), defaultPort: 3000, }, } as const; export default webFrameworks; + +const defaultDevDependencies = { + "eslint": "^9.0.0", + "@fedify/lint": PACKAGE_VERSION, +}; + +const defaultDenoDependencies = { + "@fedify/lint": PACKAGE_VERSION, +}; diff --git a/packages/init/tsdown.config.ts b/packages/init/tsdown.config.ts new file mode 100644 index 000000000..b98934d3f --- /dev/null +++ b/packages/init/tsdown.config.ts @@ -0,0 +1,24 @@ +import { cp } from "node:fs/promises"; +import { join } from "node:path"; +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: ["src/mod.ts", "src/test/mod.ts"], + platform: "node", + unbundle: true, + external: [/^node:/], + hooks: { + "build:done": async (ctx) => { + await cp( + join("src", "templates"), + join(ctx.options.outDir, "templates"), + { recursive: true }, + ); + await cp( + join("src", "json"), + join(ctx.options.outDir, "json"), + { recursive: true }, + ); + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e6d02af8..7bbde0106 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,12 @@ catalogs: '@opentelemetry/semantic-conventions': specifier: ^1.39.0 version: 1.39.0 + '@optique/core': + specifier: ^0.9.0 + version: 0.9.0 + '@optique/run': + specifier: ^0.9.0 + version: 0.9.0 '@std/assert': specifier: jsr:^1.0.13 version: 1.0.13 @@ -84,6 +90,9 @@ catalogs: byte-encodings: specifier: ^1.0.11 version: 1.0.11 + chalk: + specifier: ^5.6.2 + version: 5.6.2 elysia: specifier: ^1.3.8 version: 1.3.8 @@ -738,6 +747,9 @@ importers: '@fedify/fedify': specifier: workspace:* version: link:../fedify + '@fedify/init': + specifier: workspace:* + version: link:../init '@fedify/relay': specifier: workspace:* version: link:../relay @@ -762,9 +774,6 @@ importers: '@hongminhee/localtunnel': specifier: ^0.3.0 version: 0.3.0 - '@inquirer/prompts': - specifier: ^7.8.4 - version: 7.10.1(@types/node@22.19.1) '@jimp/core': specifier: ^1.6.0 version: 1.6.0 @@ -781,10 +790,10 @@ importers: specifier: 'catalog:' version: 2.0.0 '@optique/core': - specifier: ^0.9.0 + specifier: 'catalog:' version: 0.9.0 '@optique/run': - specifier: ^0.9.0 + specifier: 'catalog:' version: 0.9.0 '@poppanator/http-constants': specifier: ^1.1.1 @@ -793,7 +802,7 @@ importers: specifier: 'catalog:' version: 1.0.11 chalk: - specifier: ^5.6.2 + specifier: 'catalog:' version: 5.6.2 cli-highlight: specifier: ^2.1.11 @@ -801,9 +810,6 @@ importers: cli-table3: specifier: ^0.6.5 version: 0.6.5 - enquirer: - specifier: ^2.4.1 - version: 2.4.1 env-paths: specifier: ^3.0.0 version: 3.0.0 @@ -819,12 +825,6 @@ importers: icojs: specifier: ^0.19.5 version: 0.19.5(@jimp/custom@0.22.12) - inquirer: - specifier: ^12.9.4 - version: 12.11.1(@types/node@22.19.1) - inquirer-toggle: - specifier: ^1.0.1 - version: 1.0.1 jimp: specifier: ^1.6.0 version: 1.6.0 @@ -851,6 +851,31 @@ importers: specifier: 'catalog:' version: 5.9.3 + packages/create: + dependencies: + '@fedify/init': + specifier: workspace:* + version: link:../init + '@optique/core': + specifier: 'catalog:' + version: 0.9.0 + '@optique/run': + specifier: 'catalog:' + version: 0.9.0 + es-toolkit: + specifier: 'catalog:' + version: 1.43.0 + devDependencies: + '@types/node': + specifier: 'catalog:' + version: 22.19.1 + tsdown: + specifier: 'catalog:' + version: 0.12.9(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + packages/debugger: dependencies: '@fedify/fedify': @@ -1099,6 +1124,43 @@ importers: specifier: 'catalog:' version: 5.9.3 + packages/init: + dependencies: + '@fxts/core': + specifier: 'catalog:' + version: 1.20.0 + '@inquirer/prompts': + specifier: ^7.8.4 + version: 7.10.1(@types/node@22.19.1) + '@logtape/logtape': + specifier: 'catalog:' + version: 2.0.0 + '@optique/core': + specifier: 'catalog:' + version: 0.9.0 + '@optique/run': + specifier: 'catalog:' + version: 0.9.0 + chalk: + specifier: 'catalog:' + version: 5.6.2 + es-toolkit: + specifier: 'catalog:' + version: 1.43.0 + inquirer-toggle: + specifier: ^1.0.1 + version: 1.0.1 + devDependencies: + '@types/node': + specifier: 'catalog:' + version: 22.19.1 + tsdown: + specifier: 'catalog:' + version: 0.12.9(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + packages/koa: dependencies: '@fedify/fedify': @@ -4940,10 +5002,6 @@ packages: resolution: {integrity: sha512-jwSftI4QjS3mizvnSnOrPGYiUnm1vI2OP1iXeOUz5pb74Ua0nbf6nPyyTzuiCLEE3fMpaJORXh2K/TQ08H5xGA==} engines: {node: '>=10'} - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -5756,10 +5814,6 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -6450,15 +6504,6 @@ packages: inquirer-toggle@1.0.1: resolution: {integrity: sha512-0cReq29SpyO4JnoVmGBZJPoBv8sBzsGXw3MDjNxilOzhAFxIvC8mOFj34bCMtlFYKfkBKNYVLmmnP/qmrVuVMg==} - inquirer@12.11.1: - resolution: {integrity: sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -8015,10 +8060,6 @@ packages: roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} - run-async@4.0.6: - resolution: {integrity: sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==} - engines: {node: '>=0.12.0'} - run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -12750,8 +12791,6 @@ snapshots: buffer-more-ints: 1.0.0 url-parse: 1.5.10 - ansi-colors@4.1.3: {} - ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -13552,11 +13591,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.2 - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - entities@4.5.0: {} env-paths@3.0.0: {} @@ -13790,8 +13824,8 @@ snapshots: '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3) eslint: 9.32.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.32.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.32.0(jiti@2.5.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.32.0(jiti@2.5.1)) @@ -13810,8 +13844,8 @@ snapshots: '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.32.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.32.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.32.0(jiti@2.5.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.32.0(jiti@2.5.1)) @@ -13834,22 +13868,22 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 - eslint: 9.32.0(jiti@2.5.1) + eslint: 8.57.1 get-tsconfig: 4.10.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -13860,22 +13894,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)) - transitivePeerDependencies: - - supports-color - - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.1 - eslint: 8.57.1 - get-tsconfig: 4.10.1 - is-bun-module: 2.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.15 - unrs-resolver: 1.11.1 - optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -13890,25 +13909,25 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.32.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3) eslint: 9.32.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.32.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -13941,7 +13960,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -13952,7 +13971,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.32.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -13970,7 +13989,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -13981,7 +14000,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.32.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)))(eslint@9.32.0(jiti@2.5.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.32.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -14765,18 +14784,6 @@ snapshots: dependencies: '@inquirer/core': 8.2.4 - inquirer@12.11.1(@types/node@22.19.1): - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@22.19.1) - '@inquirer/prompts': 7.10.1(@types/node@22.19.1) - '@inquirer/type': 3.0.10(@types/node@22.19.1) - mute-stream: 2.0.0 - run-async: 4.0.6 - rxjs: 7.8.2 - optionalDependencies: - '@types/node': 22.19.1 - internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -16522,8 +16529,6 @@ snapshots: points-on-curve: 0.2.0 points-on-path: 0.2.1 - run-async@4.0.6: {} - run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cc1cd4cf9..76bc0256f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,12 +3,14 @@ packages: - packages/cfworkers - packages/cli - packages/debugger +- packages/create - packages/elysia - packages/express - packages/fastify - packages/fedify - packages/fixture - packages/h3 +- packages/init - packages/hono - packages/koa - packages/lint @@ -49,6 +51,8 @@ catalog: "@opentelemetry/sdk-node": ^0.211.0 "@opentelemetry/sdk-trace-base": ^2.5.0 "@opentelemetry/semantic-conventions": ^1.39.0 + "@optique/core": ^0.9.0 + "@optique/run": ^0.9.0 "@std/assert": "jsr:^1.0.13" "@std/async": "jsr:^1.0.13" "@std/path": "jsr:^1.0.6" @@ -62,6 +66,7 @@ catalog: amqplib: ^0.10.9 asn1js: ^3.0.6 byte-encodings: ^1.0.11 + chalk: ^5.6.2 elysia: ^1.3.8 es-toolkit: 1.43.0 express: ^4.0.0