@fedify/init 2.0.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +93 -0
  3. package/dist/action/configs.mjs +89 -0
  4. package/dist/action/const.mjs +7 -0
  5. package/dist/action/deps.mjs +50 -0
  6. package/dist/action/dir.mjs +13 -0
  7. package/dist/action/env.mjs +10 -0
  8. package/dist/action/install.mjs +17 -0
  9. package/dist/action/mod.d.mts +23 -0
  10. package/dist/action/mod.mjs +38 -0
  11. package/dist/action/notice.mjs +52 -0
  12. package/dist/action/patch.mjs +143 -0
  13. package/dist/action/precommand.mjs +25 -0
  14. package/dist/action/recommend.mjs +21 -0
  15. package/dist/action/set.mjs +28 -0
  16. package/dist/action/templates.mjs +55 -0
  17. package/dist/action/utils.mjs +47 -0
  18. package/dist/ask/dir.mjs +79 -0
  19. package/dist/ask/kv.mjs +41 -0
  20. package/dist/ask/mod.mjs +13 -0
  21. package/dist/ask/mq.mjs +43 -0
  22. package/dist/ask/pm.mjs +49 -0
  23. package/dist/ask/wf.mjs +26 -0
  24. package/dist/command.d.mts +41 -0
  25. package/dist/command.mjs +44 -0
  26. package/dist/const.d.mts +4 -0
  27. package/dist/const.mjs +34 -0
  28. package/dist/deno.mjs +5 -0
  29. package/dist/json/biome.json +14 -0
  30. package/dist/json/biome.mjs +14 -0
  31. package/dist/json/db-to-check.json +17 -0
  32. package/dist/json/db-to-check.mjs +21 -0
  33. package/dist/json/kv.json +39 -0
  34. package/dist/json/kv.mjs +47 -0
  35. package/dist/json/mq.json +95 -0
  36. package/dist/json/mq.mjs +65 -0
  37. package/dist/json/pm.json +47 -0
  38. package/dist/json/pm.mjs +36 -0
  39. package/dist/json/rt.json +42 -0
  40. package/dist/json/rt.mjs +31 -0
  41. package/dist/json/vscode-settings-for-deno.json +43 -0
  42. package/dist/json/vscode-settings-for-deno.mjs +39 -0
  43. package/dist/json/vscode-settings.json +41 -0
  44. package/dist/json/vscode-settings.mjs +37 -0
  45. package/dist/lib.mjs +129 -0
  46. package/dist/mod.d.mts +3 -0
  47. package/dist/mod.mjs +4 -0
  48. package/dist/templates/defaults/eslint.config.ts.tpl +3 -0
  49. package/dist/templates/defaults/federation.ts.tpl +24 -0
  50. package/dist/templates/defaults/logging.ts.tpl +23 -0
  51. package/dist/templates/elysia/index/bun.ts.tpl +13 -0
  52. package/dist/templates/elysia/index/deno.ts.tpl +19 -0
  53. package/dist/templates/elysia/index/node.ts.tpl +14 -0
  54. package/dist/templates/express/app.ts.tpl +16 -0
  55. package/dist/templates/express/index.ts.tpl +6 -0
  56. package/dist/templates/hono/app.tsx.tpl +14 -0
  57. package/dist/templates/hono/index/bun.ts.tpl +10 -0
  58. package/dist/templates/hono/index/deno.ts.tpl +13 -0
  59. package/dist/templates/hono/index/node.ts.tpl +14 -0
  60. package/dist/templates/next/middleware.ts.tpl +45 -0
  61. package/dist/templates/nitro/.env.test.tpl +1 -0
  62. package/dist/templates/nitro/nitro.config.ts.tpl +14 -0
  63. package/dist/templates/nitro/server/error.ts.tpl +3 -0
  64. package/dist/templates/nitro/server/middleware/federation.ts.tpl +8 -0
  65. package/dist/test/action.mjs +15 -0
  66. package/dist/test/create.mjs +92 -0
  67. package/dist/test/db.mjs +42 -0
  68. package/dist/test/fill.mjs +29 -0
  69. package/dist/test/lookup.mjs +183 -0
  70. package/dist/test/mod.d.mts +1 -0
  71. package/dist/test/mod.mjs +16 -0
  72. package/dist/test/run.mjs +22 -0
  73. package/dist/test/utils.mjs +15 -0
  74. package/dist/types.d.mts +50 -0
  75. package/dist/utils.d.mts +9 -0
  76. package/dist/utils.mjs +102 -0
  77. package/dist/webframeworks.mjs +220 -0
  78. package/package.json +66 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright 2024–2026 Hong Minhee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ @fedify/init: Project initializer for Fedify
2
+ ============================================
3
+
4
+ [![JSR][JSR badge]][JSR]
5
+ [![npm][npm badge]][npm]
6
+
7
+ This package provides the project initialization functionality for [Fedify],
8
+ an ActivityPub server framework. It scaffolds new Fedify project directories
9
+ with support for various web frameworks, package managers, key-value stores,
10
+ and message queues.
11
+
12
+ This package powers the `fedify init` command in the [`@fedify/cli`] toolchain,
13
+ and can also be used as a standalone library.
14
+
15
+ [JSR badge]: https://jsr.io/badges/@fedify/init
16
+ [JSR]: https://jsr.io/@fedify/init
17
+ [npm badge]: https://img.shields.io/npm/v/@fedify/init?logo=npm
18
+ [npm]: https://www.npmjs.com/package/@fedify/init
19
+ [Fedify]: https://fedify.dev/
20
+ [`@fedify/cli`]: https://jsr.io/@fedify/cli
21
+
22
+
23
+ Supported options
24
+ -----------------
25
+
26
+ The initializer supports the following project configurations:
27
+
28
+ - **Web frameworks**: [Hono], [Nitro], [Next.js], [Elysia], [Express]
29
+ - **Package managers**: Deno, pnpm, Bun, Yarn, npm
30
+ - **Key-value stores**: Deno KV, Redis, PostgreSQL
31
+ - **Message queues**: Deno KV, Redis, PostgreSQL, AMQP
32
+
33
+ [Hono]: https://hono.dev/
34
+ [Nitro]: https://nitro.build/
35
+ [Next.js]: https://nextjs.org/
36
+ [Elysia]: https://elysiajs.com/
37
+ [Express]: https://expressjs.com/
38
+
39
+
40
+ Installation
41
+ ------------
42
+
43
+ ~~~~ sh
44
+ deno add jsr:@fedify/init # Deno
45
+ npm add @fedify/init # npm
46
+ pnpm add @fedify/init # pnpm
47
+ yarn add @fedify/init # Yarn
48
+ bun add @fedify/init # Bun
49
+ ~~~~
50
+
51
+
52
+ API
53
+ ---
54
+
55
+ The package exports the following:
56
+
57
+ - `runInit`: The main initialization action handler.
58
+ - `initCommand`: The CLI command definition for `init`.
59
+
60
+ ~~~~ typescript
61
+ import { initCommand, runInit } from "@fedify/init";
62
+ ~~~~
63
+
64
+
65
+ Test
66
+ ----
67
+
68
+ The `test-init` task is useful for contributors working on `@fedify/init`,
69
+ especially when adding support for a new framework/library or modifying the
70
+ scaffolding logic. It tests the project initialization by running
71
+ `fedify init` across all combinations of supported options on temporary
72
+ directories, verifying that the generated projects are valid.
73
+
74
+ To run the test using Deno:
75
+
76
+ ~~~~ sh
77
+ deno task test-init
78
+ ~~~~
79
+
80
+ Or using pnpm:
81
+
82
+ ~~~~ sh
83
+ pnpm test-init
84
+ ~~~~
85
+
86
+ You can also filter specific options to test a subset of combinations:
87
+
88
+ ~~~~ sh
89
+ deno task test-init -w hono -p deno
90
+ ~~~~
91
+
92
+ Use `--no-dry-run` to test with actual file creation and dependency
93
+ installation, or `--no-hyd-run` to only log outputs without creating files.
@@ -0,0 +1,89 @@
1
+ import { merge } from "../utils.mjs";
2
+ import biome_default from "../json/biome.mjs";
3
+ import vscode_settings_for_deno_default from "../json/vscode-settings-for-deno.mjs";
4
+ import vscode_settings_default from "../json/vscode-settings.mjs";
5
+ import { PACKAGES_PATH } from "./const.mjs";
6
+ import { getDependencies, getDevDependencies, joinDepsReg } from "./deps.mjs";
7
+ import { uniq } from "es-toolkit";
8
+ import { realpathSync } from "node:fs";
9
+ import { join, relative } from "node:path";
10
+ import { concat, filter, keys, map, pick, pipe, toArray } from "@fxts/core/index.js";
11
+
12
+ //#region src/action/configs.ts
13
+ /**
14
+ * Loads Deno configuration object with compiler options, unstable features, and tasks.
15
+ * Combines unstable features required by KV store and message queue with framework-specific options.
16
+ *
17
+ * @param param0 - Destructured initialization data containing KV, MQ, initializer, and directory
18
+ * @returns Configuration object with path and Deno-specific settings
19
+ */
20
+ const loadDenoConfig = (data) => ({
21
+ path: "deno.json",
22
+ data: {
23
+ ...pick(["compilerOptions", "tasks"], data.initializer),
24
+ unstable: getUnstable(data),
25
+ nodeModulesDir: "auto",
26
+ imports: joinDepsReg("deno")(getDependencies(data)),
27
+ lint: { plugins: ["jsr:@fedify/lint"] },
28
+ ...data.testMode ? { links: getLinks(data) } : {}
29
+ }
30
+ });
31
+ const getUnstable = ({ kv: { denoUnstable: kv = [] }, mq: { denoUnstable: mq = [] } }) => pipe(["temporal"], concat(kv), concat(mq), toArray, uniq);
32
+ const getLinks = ({ kv, mq, initializer, dir }) => pipe({ "@fedify/fedify": "" }, merge(initializer.dependencies), merge(kv.dependencies), merge(mq.dependencies), keys, filter((dep) => dep.includes("@fedify/")), map((dep) => dep.replace("@fedify/", "")), map((dep) => join(PACKAGES_PATH, dep)), map((absolutePath) => realpathSync(absolutePath)), map((realAbsolutePath) => relative(realpathSync(dir), realAbsolutePath)), toArray);
33
+ /**
34
+ * Loads TypeScript configuration object for Node.js/Bun projects.
35
+ * Uses compiler options from the framework initializer.
36
+ *
37
+ * @param param0 - Destructured initialization data containing initializer and directory
38
+ * @returns Configuration object with path and TypeScript compiler options
39
+ */
40
+ const loadTsConfig = ({ initializer, dir }) => ({
41
+ path: join(dir, "tsconfig.json"),
42
+ data: { compilerOptions: initializer.compilerOptions }
43
+ });
44
+ /**
45
+ * Loads package.json configuration object for Node.js/Bun projects.
46
+ * Sets up ES modules and includes framework-specific npm scripts.
47
+ *
48
+ * @param param0 - Destructured initialization data containing initializer and directory
49
+ * @returns Configuration object with path and package.json settings
50
+ */
51
+ const loadPackageJson = (data) => ({
52
+ path: "package.json",
53
+ data: {
54
+ type: "module",
55
+ scripts: data.initializer.tasks,
56
+ dependencies: getDependencies(data),
57
+ devDependencies: getDevDependencies(data)
58
+ }
59
+ });
60
+ /**
61
+ * Configuration objects for various development tool setup files.
62
+ * Contains predefined configurations for code formatting, VS Code settings, and extensions
63
+ * based on the project type (Node.js/Bun or Deno).
64
+ */
65
+ const devToolConfigs = {
66
+ biome: {
67
+ path: join("biome.json"),
68
+ data: biome_default
69
+ },
70
+ vscExt: {
71
+ path: join(".vscode", "extensions.json"),
72
+ data: { recommendations: ["biomejs.biome", "dbaeumer.vscode-eslint"] }
73
+ },
74
+ vscSet: {
75
+ path: join(".vscode", "settings.json"),
76
+ data: vscode_settings_default
77
+ },
78
+ vscSetDeno: {
79
+ path: join(".vscode", "settings.json"),
80
+ data: vscode_settings_for_deno_default
81
+ },
82
+ vscExtDeno: {
83
+ path: join(".vscode", "extensions.json"),
84
+ data: { recommendations: ["denoland.vscode-deno"] }
85
+ }
86
+ };
87
+
88
+ //#endregion
89
+ export { devToolConfigs, loadDenoConfig, loadPackageJson, loadTsConfig };
@@ -0,0 +1,7 @@
1
+ import { join } from "node:path";
2
+
3
+ //#region src/action/const.ts
4
+ const PACKAGES_PATH = join(import.meta.dirname, "..", "..", "..");
5
+
6
+ //#endregion
7
+ export { PACKAGES_PATH };
@@ -0,0 +1,50 @@
1
+ import { merge, replace } from "../utils.mjs";
2
+ import { PACKAGE_VERSION } from "../lib.mjs";
3
+ import { PACKAGES_PATH } from "./const.mjs";
4
+ import { isDeno } from "./utils.mjs";
5
+ import { always, entries, filter, fromEntries, map, pipe, when } from "@fxts/core";
6
+ import { join as join$1 } from "node:path";
7
+
8
+ //#region src/action/deps.ts
9
+ /**
10
+ * Gathers all dependencies required for the project based on the initializer,
11
+ * key-value store, and message queue configurations.
12
+ *
13
+ * @param data - Web Framework initializer, key-value store and
14
+ * message queue descriptions
15
+ * @returns A record of dependencies with their versions
16
+ */
17
+ const getDependencies = ({ initializer, kv, mq, testMode, packageManager }) => pipe({
18
+ "@fedify/fedify": PACKAGE_VERSION,
19
+ "@fedify/vocab": PACKAGE_VERSION,
20
+ "@logtape/logtape": "^2.0.0"
21
+ }, merge(initializer.dependencies), merge(kv.dependencies), merge(mq.dependencies), when(always(testMode), isDeno({ packageManager }) ? removeFedifyDeps : addLocalFedifyDeps), normalizePackageNames(packageManager));
22
+ const removeFedifyDeps = (deps) => pipe(deps, entries, filter(([name]) => !name.includes("@fedify")), fromEntries);
23
+ const addLocalFedifyDeps = (deps) => pipe(deps, entries, map(when(([name]) => name.includes("@fedify/"), ([name, _version]) => [name, convertFedifyToLocal(name)])), fromEntries);
24
+ const convertFedifyToLocal = (name) => pipe(name, replace("@fedify/", ""), (pkg) => join$1(PACKAGES_PATH, pkg));
25
+ /** Gathers all devDependencies required for the project based on the
26
+ * initializer, key-value store, and message queue configurations,
27
+ * including Biome for linting/formatting.
28
+ *
29
+ * @param data - Web Framework initializer, key-value store
30
+ * and message queue descriptions
31
+ * @returns A record of devDependencies with their versions
32
+ */
33
+ const getDevDependencies = ({ initializer, kv, mq, packageManager }) => pipe({ "@biomejs/biome": "^2.2.4" }, merge(initializer.devDependencies), merge(kv.devDependencies), merge(mq.devDependencies), normalizePackageNames(packageManager));
34
+ /**
35
+ * Joins package names with their versions for installation dependencies.
36
+ * For Deno, it prefixes packages with 'jsr:'
37
+ * unless they already start with 'npm:' or 'jsr:'.
38
+ *
39
+ * @param data - Package manager and dependencies to be joined with versions
40
+ * @returns \{ name: `${registry}:${package}@${version}` } for deno
41
+ */
42
+ const joinDepsReg = (pm) => (dependencies) => pipe(dependencies, entries, map(([name, version]) => {
43
+ return [name.substring(4), version.startsWith("npm:") || version.startsWith("jsr:") ? version : `${name}@${getPackageVersion(pm, version)}`];
44
+ }), fromEntries);
45
+ const getPackageVersion = (pm, version) => pm !== "deno" && version.includes("+") ? version.substring(0, version.indexOf("+")) : version;
46
+ const normalizePackageNames = (pm) => (deps) => pipe(deps, entries, map(([name, version]) => [getPackageName(pm, name), version]), fromEntries);
47
+ const getPackageName = (pm, name) => pm !== "deno" ? name.startsWith("npm:") ? name.replace("npm:", "") : name : name.startsWith("npm:") ? name : `jsr:${name}`;
48
+
49
+ //#endregion
50
+ export { getDependencies, getDevDependencies, joinDepsReg };
@@ -0,0 +1,13 @@
1
+ import { mkdir } from "node:fs/promises";
2
+
3
+ //#region src/action/dir.ts
4
+ /**
5
+ * Creates the target directory if it does not exist.
6
+ *
7
+ * @param data - The directory
8
+ * @returns A promise that resolves when the directory is created
9
+ */
10
+ const makeDirIfHyd = ({ dir }) => mkdir(dir, { recursive: true });
11
+
12
+ //#endregion
13
+ export { makeDirIfHyd };
@@ -0,0 +1,10 @@
1
+ import { notEmpty } from "../utils.mjs";
2
+ import { noticeConfigEnv, noticeEnvKeyValue } from "./notice.mjs";
3
+ import { entries, forEach, pipeLazy, tap, toArray, when } from "@fxts/core";
4
+
5
+ //#region src/action/env.ts
6
+ const recommendConfigEnv = pipeLazy((data) => data.env, entries, toArray, when(notEmpty, tap(noticeConfigEnv)), forEach(noticeEnvKeyValue));
7
+ var env_default = recommendConfigEnv;
8
+
9
+ //#endregion
10
+ export { env_default as default };
@@ -0,0 +1,17 @@
1
+ import { CommandError, runSubCommand } from "../utils.mjs";
2
+ import { apply, pipe } from "@fxts/core";
3
+
4
+ //#region src/action/install.ts
5
+ const installDependencies = (data) => pipe(data, ({ packageManager, dir }) => [[packageManager, "install"], { cwd: dir }], apply(runSubCommand)).catch((e) => {
6
+ if (e instanceof CommandError) {
7
+ console.error(`Failed to install dependencies using ${data.packageManager}.`);
8
+ console.error("Command:", e.commandLine);
9
+ if (e.stderr) console.error("Error:", e.stderr);
10
+ throw e;
11
+ }
12
+ throw e;
13
+ });
14
+ var install_default = installDependencies;
15
+
16
+ //#endregion
17
+ export { install_default as default };
@@ -0,0 +1,23 @@
1
+ import { InitCommand } from "../command.mjs";
2
+ import { InitCommandData } from "../types.mjs";
3
+
4
+ //#region src/action/mod.d.ts
5
+
6
+ /**
7
+ * Execution flow of the `runInit` function:
8
+ *
9
+ * 1. Receives options of type `InitCommand`.
10
+ * 2. Prints a dinosaur ASCII art via `drawDinosaur`.
11
+ * 3. Prompts the user for options via `askOptions`,
12
+ * converting `InitCommand` into `InitCommandOptions`.
13
+ * 4. Displays the selected options via `noticeOptions`.
14
+ * 5. Converts `InitCommandOptions` into `InitCommandData` via `setData`.
15
+ * 6. Branches based on `isDry`:
16
+ * - If dry run, executes `handleDryRun`.
17
+ * - Otherwise, executes `handleHydRun`.
18
+ * 7. Recommends configuration environment via `recommendConfigEnv`.
19
+ * 8. Shows how to run the project via `noticeHowToRun`.
20
+ */
21
+ declare const runInit: (options: InitCommand) => Promise<InitCommandData>;
22
+ //#endregion
23
+ export { runInit };
@@ -0,0 +1,38 @@
1
+ import { set } from "../utils.mjs";
2
+ import ask_default from "../ask/mod.mjs";
3
+ import { makeDirIfHyd } from "./dir.mjs";
4
+ import { drawDinosaur, noticeHowToRun, noticeOptions, noticePrecommand } from "./notice.mjs";
5
+ import env_default from "./env.mjs";
6
+ import install_default from "./install.mjs";
7
+ import { hasCommand, isDry } from "./utils.mjs";
8
+ import { patchFiles, recommendPatchFiles } from "./patch.mjs";
9
+ import precommand_default from "./precommand.mjs";
10
+ import recommend_default from "./recommend.mjs";
11
+ import set_default from "./set.mjs";
12
+ import { pipe, tap, unless, when } from "@fxts/core";
13
+ import process from "node:process";
14
+
15
+ //#region src/action/mod.ts
16
+ /**
17
+ * Execution flow of the `runInit` function:
18
+ *
19
+ * 1. Receives options of type `InitCommand`.
20
+ * 2. Prints a dinosaur ASCII art via `drawDinosaur`.
21
+ * 3. Prompts the user for options via `askOptions`,
22
+ * converting `InitCommand` into `InitCommandOptions`.
23
+ * 4. Displays the selected options via `noticeOptions`.
24
+ * 5. Converts `InitCommandOptions` into `InitCommandData` via `setData`.
25
+ * 6. Branches based on `isDry`:
26
+ * - If dry run, executes `handleDryRun`.
27
+ * - Otherwise, executes `handleHydRun`.
28
+ * 7. Recommends configuration environment via `recommendConfigEnv`.
29
+ * 8. Shows how to run the project via `noticeHowToRun`.
30
+ */
31
+ const runInit = (options) => pipe(options, tap(drawDinosaur), setTestMode, ask_default, tap(noticeOptions), set_default, when(isDry, handleDryRun), unless(isDry, handleHydRun), tap(env_default), tap(noticeHowToRun));
32
+ var action_default = runInit;
33
+ const setTestMode = set("testMode", () => Boolean(process.env["FEDIFY_TEST_MODE"]));
34
+ const handleDryRun = (data) => pipe(data, tap(when(hasCommand, noticePrecommand)), tap(recommendPatchFiles), tap(recommend_default));
35
+ const handleHydRun = (data) => pipe(data, tap(makeDirIfHyd), tap(when(hasCommand, precommand_default)), tap(patchFiles), tap(install_default));
36
+
37
+ //#endregion
38
+ export { action_default as default };
@@ -0,0 +1,52 @@
1
+ import { colors, printMessage } from "../utils.mjs";
2
+ import { text } from "@optique/core";
3
+ import { flow } from "es-toolkit";
4
+
5
+ //#region src/action/notice.ts
6
+ function drawDinosaur() {
7
+ const d = flow(colors.bgBlue, colors.black);
8
+ const f = colors.blue;
9
+ console.error(`\
10
+ ${d(" ___ ")} ${f(" _____ _ _ __")}
11
+ ${d(" /'_') ")} ${f("| ___|__ __| (_)/ _|_ _")}
12
+ ${d(" .-^^^-/ / ")} ${f("| |_ / _ \\/ _` | | |_| | | |")}
13
+ ${d(" __/ / ")} ${f("| _| __/ (_| | | _| |_| |")}
14
+ ${d(" <__.|_|-|_| ")} ${f("|_| \\___|\\__,_|_|_| \\__, |")}
15
+ ${d(" ")} ${f(" |___/")}
16
+ `);
17
+ }
18
+ const noticeOptions = ({ packageManager, webFramework, kvStore, messageQueue }) => printMessage`
19
+ Package manager: ${packageManager};
20
+ Web framework: ${webFramework};
21
+ Key–value store: ${kvStore};
22
+ Message queue: ${messageQueue};
23
+ `;
24
+ function noticePrecommand({ initializer: { command: command$1 }, dir }) {
25
+ printMessage`📦 Would run command:`;
26
+ printMessage` cd ${dir}`;
27
+ printMessage` ${command$1.join(" ")}\n`;
28
+ }
29
+ const noticeFilesToCreate = () => printMessage`📄 Would create files:\n`;
30
+ const noticeFilesToInsert = () => printMessage`Would create/update JSON files:\n`;
31
+ const noticeDepsIfExist = () => printMessage`📦 Would install dependencies:`;
32
+ const noticeDevDepsIfExist = () => printMessage`📦 Would install dev dependencies:`;
33
+ const noticeDeps = ([name, version]) => printMessage`${name}@${version}`;
34
+ function displayFile(path, content, emoji = "📄") {
35
+ printMessage`${emoji} ${path}`;
36
+ printMessage`${"─".repeat(60)}`;
37
+ printMessage`${content}`;
38
+ printMessage`${"─".repeat(60)}\n`;
39
+ }
40
+ const noticeConfigEnv = () => printMessage`Note that you probably want to edit the ${".env"} file.
41
+ It currently contains the following values:\n`;
42
+ const noticeEnvKeyValue = ([key, value]) => {
43
+ printMessage`${text(` ${key}='${value}'`)}`;
44
+ };
45
+ const noticeHowToRun = ({ initializer: { instruction, federationFile } }) => printMessage`
46
+ ${instruction}
47
+
48
+ Start by editing the ${text(federationFile)} file to define your federation!
49
+ `;
50
+
51
+ //#endregion
52
+ export { displayFile, drawDinosaur, noticeConfigEnv, noticeDeps, noticeDepsIfExist, noticeDevDepsIfExist, noticeEnvKeyValue, noticeFilesToCreate, noticeFilesToInsert, noticeHowToRun, noticeOptions, noticePrecommand };
@@ -0,0 +1,143 @@
1
+ import { formatJson, merge, replaceAll, set } from "../utils.mjs";
2
+ import { createFile, throwUnlessNotExists } from "../lib.mjs";
3
+ import { displayFile, noticeFilesToCreate, noticeFilesToInsert } from "./notice.mjs";
4
+ import { joinDir, stringifyEnvs } from "./utils.mjs";
5
+ import { devToolConfigs, loadDenoConfig, loadPackageJson, loadTsConfig } from "./configs.mjs";
6
+ import { getImports, loadFederation, loadLogging } from "./templates.mjs";
7
+ import { always, apply, entries, map, pipe, pipeLazy, tap } from "@fxts/core";
8
+ import { toMerged } from "es-toolkit";
9
+ import { readFile } from "node:fs/promises";
10
+
11
+ //#region src/action/patch.ts
12
+ /**
13
+ * Main function that initializes the project by creating necessary files and configurations.
14
+ * Handles both dry-run mode (recommending files) and actual file creation.
15
+ * Orchestrates the entire file generation and writing process.
16
+ *
17
+ * @param data - The initialization command data containing project
18
+ * configuration
19
+ * @returns A processed data object with files and JSONs ready for creation
20
+ */
21
+ const patchFiles = (data) => pipe(data, set("files", getFiles), set("jsons", getJsons), createFiles);
22
+ const recommendPatchFiles = (data) => pipe(data, set("files", getFiles), set("jsons", getJsons), recommendFiles);
23
+ /**
24
+ * Generates text-based files (TypeScript, environment files) for the project.
25
+ * Creates federation configuration, logging setup, environment variables, and
26
+ * framework-specific files by processing templates and combining them with
27
+ * project-specific data.
28
+ *
29
+ * @param data - The initialization command data
30
+ * @returns A record of file paths to their string content
31
+ */
32
+ const getFiles = (data) => ({
33
+ [data.initializer.federationFile]: loadFederation({
34
+ imports: getImports(data),
35
+ ...data
36
+ }),
37
+ [data.initializer.loggingFile]: loadLogging(data),
38
+ ".env": stringifyEnvs(data.env),
39
+ ...data.initializer.files
40
+ });
41
+ /**
42
+ * Generates JSON configuration files based on the package manager type.
43
+ * Creates different sets of configuration files for Deno vs other environments,
44
+ * including compiler configs, package manifests, and development
45
+ * tool configurations.
46
+ *
47
+ * @param data - The initialization command data
48
+ * @returns A record of file paths to their JSON object content
49
+ */
50
+ const getJsons = (data) => data.packageManager === "deno" ? {
51
+ "deno.json": loadDenoConfig(data).data,
52
+ [devToolConfigs["vscSetDeno"].path]: devToolConfigs["vscSetDeno"].data,
53
+ [devToolConfigs["vscExtDeno"].path]: devToolConfigs["vscExtDeno"].data
54
+ } : {
55
+ ...data.initializer.compilerOptions ? { "tsconfig.json": loadTsConfig(data).data } : {},
56
+ "package.json": loadPackageJson(data).data,
57
+ [devToolConfigs["biome"].path]: devToolConfigs["biome"].data,
58
+ [devToolConfigs["vscSet"].path]: devToolConfigs["vscSet"].data,
59
+ [devToolConfigs["vscExt"].path]: devToolConfigs["vscExt"].data
60
+ };
61
+ /**
62
+ * Handles dry-run mode by recommending files to be created without actually
63
+ * creating them.
64
+ * Displays what files would be created and shows their content for user review,
65
+ * so users can preview the initialization process before committing to it.
66
+ *
67
+ * @param data - The initialization command data with files and JSONs prepared
68
+ * @returns The processed data with recommendations displayed
69
+ */
70
+ const recommendFiles = (data) => pipe(data, tap(noticeFilesToCreate), tap(processAllFiles(displayFile)), tap(noticeFilesToInsert), set("files", ({ jsons }) => jsons), tap(processAllFiles(displayFile)));
71
+ /**
72
+ * Actually creates files on the filesystem during normal execution.
73
+ * Merges text files and JSON files together and writes them to disk.
74
+ * This performs the actual file system operations to initialize the project.
75
+ *
76
+ * @param data - The initialization command data with files and JSONs prepared
77
+ * @returns The processed data after files have been created
78
+ */
79
+ const createFiles = (data) => pipe(data, set("files", ({ jsons, files }) => toMerged(files, jsons)), tap(processAllFiles(createFile)));
80
+ /**
81
+ * Processes all files with a given processing function.
82
+ * Takes a processor (either display or create) and applies it to all files
83
+ * in the target directory, handling path resolution and content patching.
84
+ *
85
+ * @param process - Function to process each file (either display or create)
86
+ * @returns A function that processes all files in the given directory with the provided processor
87
+ */
88
+ const processAllFiles = (process) => ({ dir, files }) => pipe(files, entries, map(pipeLazy(joinDir(dir), apply(patchContent), apply(process))), Array.fromAsync);
89
+ /**
90
+ * Patches file content by either merging JSON or appending text content.
91
+ * Handles existing files by reading their current content and intelligently
92
+ * combining it with new content based on the content type (JSON vs text).
93
+ *
94
+ * @param path - The file path to patch
95
+ * @param content - The new content (either string or object)
96
+ * @returns A tuple containing the file path and the final content string
97
+ */
98
+ async function patchContent(path, content) {
99
+ const prev = await readFileIfExists(path);
100
+ return [path, typeof content === "object" ? mergeJson(prev, content) : appendText(prev, content)];
101
+ }
102
+ /**
103
+ * Merges new JSON data with existing JSON content and formats the result.
104
+ * Parses existing JSON content (if any) and deep merges it with new data,
105
+ * then formats the result for consistent output.
106
+ * Supports JSONC (JSON with Comments) by removing comments before parsing.
107
+ *
108
+ * @param prev - The previous JSON content as string
109
+ * @param data - The new data object to merge
110
+ * @returns Formatted JSON string with merged content
111
+ */
112
+ const mergeJson = (prev, data) => pipe(prev ? JSON.parse(removeJsonComments(prev)) : {}, merge(data), formatJson);
113
+ /**
114
+ * Removes single-line (//) and multi-line (/* *\/) comments from JSON string.
115
+ * This allows parsing JSONC (JSON with Comments) files.
116
+ *
117
+ * @param jsonString - The JSON string potentially containing comments
118
+ * @returns JSON string with comments removed
119
+ */
120
+ const removeJsonComments = (jsonString) => pipe(jsonString, replaceAll(/\/\/.*$/gm, ""), replaceAll(/\/\*[\s\S]*?\*\//g, ""));
121
+ /**
122
+ * Appends new text content to existing text content line by line.
123
+ * Concatenates new content lines with existing content lines,
124
+ * preserving line structure and formatting.
125
+ *
126
+ * @param prev - The previous text content
127
+ * @param data - The new text content to append
128
+ * @returns Combined text content as a single string
129
+ */
130
+ const appendText = (prev, data) => prev ? `${prev}\n${data}` : data;
131
+ /**
132
+ * Safely reads a file if it exists, returns empty string if it doesn't exist.
133
+ * Provides error handling to distinguish between "file not found" and other
134
+ * file system errors, throwing only for unexpected errors.
135
+ *
136
+ * @param path - The file path to read
137
+ * @returns The file content as string, or empty string if file doesn't exist
138
+ * @throws Error if file access fails for reasons other than file not existing
139
+ */
140
+ const readFileIfExists = (path) => readFile(path, "utf8").catch(pipeLazy(tap(throwUnlessNotExists), always("")));
141
+
142
+ //#endregion
143
+ export { patchFiles, recommendPatchFiles };
@@ -0,0 +1,25 @@
1
+ import { CommandError, exit, runSubCommand } from "../utils.mjs";
2
+
3
+ //#region src/action/precommand.ts
4
+ /**
5
+ * Runs the precommand specified in the initializer to set up the project.
6
+ *
7
+ * @param data - The initialization command data containing the initializer command and directory
8
+ * @returns A promise that resolves when the precommand has been executed
9
+ */
10
+ const runPrecommand = ({ initializer: { command }, dir }) => runSubCommand(command, {
11
+ cwd: dir,
12
+ stdio: "inherit"
13
+ }).catch((e) => {
14
+ if (e instanceof CommandError) {
15
+ console.error("Failed to run the precommand.");
16
+ console.error("Command:", e.commandLine);
17
+ if (e.stderr) console.error("Error:", e.stderr);
18
+ if (e.stdout) console.error("Output:", e.stdout);
19
+ } else console.error("Failed to run the precommand:", e);
20
+ exit(1);
21
+ });
22
+ var precommand_default = runPrecommand;
23
+
24
+ //#endregion
25
+ export { precommand_default as default };
@@ -0,0 +1,21 @@
1
+ import { notEmpty } from "../utils.mjs";
2
+ import { noticeDeps, noticeDepsIfExist, noticeDevDepsIfExist } from "./notice.mjs";
3
+ import { isDeno } from "./utils.mjs";
4
+ import { getDependencies, getDevDependencies } from "./deps.mjs";
5
+ import { map, peek, pipeLazy, tap, unless, when } from "@fxts/core";
6
+
7
+ //#region src/action/recommend.ts
8
+ const recommendDeps = pipeLazy(getDependencies, Object.entries, when(notEmpty, tap(noticeDepsIfExist)), peek(noticeDeps));
9
+ const recommendDevDeps = pipeLazy(getDevDependencies, Object.entries, when(notEmpty, tap(noticeDevDepsIfExist)), map(noticeDeps));
10
+ /**
11
+ * Recommends dependencies and devDependencies to be added to package.json.
12
+ * Skips devDependencies recommendation if the package manager is Deno.
13
+ *
14
+ * @param data - The initialization command data
15
+ * @returns An InitCommandIo function that performs the recommendation
16
+ */
17
+ const recommendDependencies = pipeLazy(tap(recommendDeps), unless(isDeno, tap(recommendDevDeps)));
18
+ var recommend_default = recommendDependencies;
19
+
20
+ //#endregion
21
+ export { recommend_default as default };
@@ -0,0 +1,28 @@
1
+ import { merge, set } from "../utils.mjs";
2
+ import { kvStores, messageQueues } from "../lib.mjs";
3
+ import webframeworks_default from "../webframeworks.mjs";
4
+ import { pipe } from "@fxts/core";
5
+ import { existsSync } from "node:fs";
6
+ import { realpath } from "node:fs/promises";
7
+ import { basename, normalize } from "node:path";
8
+
9
+ //#region src/action/set.ts
10
+ /**
11
+ * Set all necessary data for initializing the project.
12
+ * This function orchestrates the setting of project name, initializer,
13
+ * key-value store, message queue, and environment variables by calling
14
+ * individual setter functions for each piece of data.
15
+ *
16
+ * @param data - The initial command options provided by the user
17
+ * @returns A promise resolving to a complete InitCommandData object
18
+ */
19
+ const setData = (data) => pipe(data, setProjectName, setInitializer, setKv, setMq, setEnv);
20
+ var set_default = setData;
21
+ const setProjectName = set("projectName", async ({ dir }) => basename(existsSync(dir) ? await realpath(dir) : normalize(dir)));
22
+ const setInitializer = set("initializer", (data) => webframeworks_default[data.webFramework].init(data));
23
+ const setKv = set("kv", ({ kvStore }) => kvStores[kvStore]);
24
+ const setMq = set("mq", ({ messageQueue }) => messageQueues[messageQueue]);
25
+ const setEnv = set("env", ({ kv, mq }) => merge(kv.env)(mq.env));
26
+
27
+ //#endregion
28
+ export { set_default as default };