@fedify/cli 1.8.11 → 2.0.0-dev.1757

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 (130) hide show
  1. package/deno.json +72 -0
  2. package/dist/cache.js +17 -0
  3. package/dist/deno.js +72 -0
  4. package/dist/docloader.js +52 -0
  5. package/dist/globals.js +49 -0
  6. package/dist/imagerenderer.js +105 -0
  7. package/dist/inbox/rendercode.js +57 -0
  8. package/dist/inbox/view.js +508 -0
  9. package/dist/inbox.js +315 -0
  10. package/dist/init/action/configs.js +81 -0
  11. package/dist/init/action/deps.js +52 -0
  12. package/dist/init/action/dir.js +16 -0
  13. package/dist/init/action/env.js +13 -0
  14. package/dist/init/action/install.js +22 -0
  15. package/dist/init/action/mod.js +39 -0
  16. package/dist/init/action/notice.js +62 -0
  17. package/dist/init/action/patch.js +141 -0
  18. package/dist/init/action/precommand.js +23 -0
  19. package/dist/init/action/recommend.js +24 -0
  20. package/dist/init/action/set.js +31 -0
  21. package/dist/init/action/templates.js +57 -0
  22. package/dist/init/action/utils.js +50 -0
  23. package/dist/init/ask/dir.js +82 -0
  24. package/dist/init/ask/kv.js +33 -0
  25. package/dist/init/ask/mod.js +16 -0
  26. package/dist/init/ask/mq.js +33 -0
  27. package/dist/init/ask/pm.js +49 -0
  28. package/dist/init/ask/wf.js +29 -0
  29. package/dist/init/command.js +25 -0
  30. package/dist/init/const.js +31 -0
  31. package/dist/init/json/biome.js +24 -0
  32. package/dist/init/json/kv.js +53 -0
  33. package/dist/init/json/mq.js +72 -0
  34. package/dist/init/json/pm.js +44 -0
  35. package/dist/init/json/rt.js +39 -0
  36. package/dist/init/json/vscode-settings-for-deno.js +53 -0
  37. package/dist/init/json/vscode-settings.js +49 -0
  38. package/dist/init/lib.js +129 -0
  39. package/dist/init/mod.js +5 -0
  40. package/dist/init/webframeworks.js +133 -0
  41. package/dist/kv.bun.js +17 -0
  42. package/dist/kv.node.js +17 -0
  43. package/dist/log.js +52 -0
  44. package/dist/lookup.js +287 -0
  45. package/dist/mod.js +34 -0
  46. package/dist/nodeinfo.js +261 -0
  47. package/dist/table.js +24 -0
  48. package/dist/tempserver.js +71 -0
  49. package/dist/tunnel.js +21 -0
  50. package/dist/utils.js +67 -0
  51. package/dist/webfinger/action.js +44 -0
  52. package/dist/webfinger/command.js +20 -0
  53. package/dist/webfinger/error.js +47 -0
  54. package/dist/webfinger/lib.js +45 -0
  55. package/dist/webfinger/mod.js +5 -0
  56. package/package.json +64 -24
  57. package/scripts/pack.ts +64 -0
  58. package/src/cache.ts +17 -0
  59. package/src/docloader.ts +67 -0
  60. package/src/globals.ts +43 -0
  61. package/src/imagerenderer.ts +149 -0
  62. package/src/inbox/entry.ts +10 -0
  63. package/src/inbox/rendercode.ts +68 -0
  64. package/src/inbox/view.tsx +598 -0
  65. package/src/inbox.tsx +535 -0
  66. package/src/init/action/configs.ts +88 -0
  67. package/src/init/action/deps.ts +93 -0
  68. package/src/init/action/dir.ts +11 -0
  69. package/src/init/action/env.ts +14 -0
  70. package/src/init/action/install.ts +59 -0
  71. package/src/init/action/mod.ts +66 -0
  72. package/src/init/action/notice.ts +101 -0
  73. package/src/init/action/patch.ts +212 -0
  74. package/src/init/action/precommand.ts +22 -0
  75. package/src/init/action/recommend.ts +38 -0
  76. package/src/init/action/set.ts +78 -0
  77. package/src/init/action/templates.ts +95 -0
  78. package/src/init/action/utils.ts +64 -0
  79. package/src/init/ask/dir.ts +98 -0
  80. package/src/init/ask/kv.ts +39 -0
  81. package/src/init/ask/mod.ts +23 -0
  82. package/src/init/ask/mq.ts +37 -0
  83. package/src/init/ask/pm.ts +58 -0
  84. package/src/init/ask/wf.ts +27 -0
  85. package/src/init/command.ts +64 -0
  86. package/src/init/const.ts +4 -0
  87. package/src/init/json/biome.json +17 -0
  88. package/src/init/json/kv.json +39 -0
  89. package/src/init/json/mq.json +95 -0
  90. package/src/init/json/pm.json +47 -0
  91. package/src/init/json/rt.json +42 -0
  92. package/src/init/json/vscode-settings-for-deno.json +43 -0
  93. package/src/init/json/vscode-settings.json +41 -0
  94. package/src/init/lib.ts +220 -0
  95. package/src/init/mod.ts +2 -0
  96. package/src/init/templates/defaults/federation.ts.tpl +23 -0
  97. package/src/init/templates/defaults/logging.ts.tpl +23 -0
  98. package/src/init/templates/express/app.ts.tpl +16 -0
  99. package/src/init/templates/express/index.ts.tpl +6 -0
  100. package/src/init/templates/hono/app.tsx.tpl +14 -0
  101. package/src/init/templates/hono/index/bun.ts.tpl +10 -0
  102. package/src/init/templates/hono/index/deno.ts.tpl +13 -0
  103. package/src/init/templates/hono/index/node.ts.tpl +14 -0
  104. package/src/init/templates/next/middleware.ts.tpl +45 -0
  105. package/src/init/templates/nitro/nitro.config.ts.tpl +5 -0
  106. package/src/init/templates/nitro/server/error.ts.tpl +3 -0
  107. package/src/init/templates/nitro/server/middleware/federation.ts.tpl +8 -0
  108. package/src/init/types.ts +88 -0
  109. package/src/init/webframeworks.ts +151 -0
  110. package/src/kv.bun.ts +12 -0
  111. package/src/kv.node.ts +11 -0
  112. package/src/log.ts +64 -0
  113. package/src/lookup.test.ts +182 -0
  114. package/src/lookup.ts +558 -0
  115. package/src/mod.ts +45 -0
  116. package/src/nodeinfo.test.ts +229 -0
  117. package/src/nodeinfo.ts +447 -0
  118. package/src/table.ts +17 -0
  119. package/src/tempserver.ts +87 -0
  120. package/src/tunnel.ts +32 -0
  121. package/src/utils.ts +136 -0
  122. package/src/webfinger/action.ts +50 -0
  123. package/src/webfinger/command.ts +59 -0
  124. package/src/webfinger/error.ts +47 -0
  125. package/src/webfinger/lib.ts +37 -0
  126. package/src/webfinger/mod.test.ts +79 -0
  127. package/src/webfinger/mod.ts +2 -0
  128. package/tsdown.config.ts +24 -0
  129. package/src/install.mjs +0 -189
  130. package/src/run.mjs +0 -22
@@ -0,0 +1,141 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { formatJson, merge, set } from "../../utils.js";
5
+ import { createFile, throwUnlessNotExists } from "../lib.js";
6
+ import { displayFile, noticeFilesToCreate, noticeFilesToInsert } from "./notice.js";
7
+ import { joinDir, stringifyEnvs } from "./utils.js";
8
+ import { devToolConfigs, loadDenoConfig, loadPackageJson, loadTsConfig } from "./configs.js";
9
+ import { getImports, loadFederation, loadLogging } from "./templates.js";
10
+ import { readFile } from "node:fs/promises";
11
+ import { apply, entries, forEach, pipe, pipeLazy, tap } from "@fxts/core";
12
+ import { toMerged } from "es-toolkit";
13
+
14
+ //#region src/init/action/patch.ts
15
+ /**
16
+ * Main function that initializes the project by creating necessary files and configurations.
17
+ * Handles both dry-run mode (recommending files) and actual file creation.
18
+ * Orchestrates the entire file generation and writing process.
19
+ *
20
+ * @param data - The initialization command data containing project configuration
21
+ * @returns A processed data object with files and JSONs ready for creation
22
+ */
23
+ const patchFiles = (data) => pipe(data, set("files", getFiles), set("jsons", getJsons), createFiles);
24
+ const recommendPatchFiles = (data) => pipe(data, set("files", getFiles), set("jsons", getJsons), recommendFiles);
25
+ /**
26
+ * Generates text-based files (TypeScript, environment files) for the project.
27
+ * Creates federation configuration, logging setup, environment variables, and framework-specific files
28
+ * by processing templates and combining them with project-specific data.
29
+ *
30
+ * @param data - The initialization command data
31
+ * @returns A record of file paths to their string content
32
+ */
33
+ const getFiles = (data) => ({
34
+ [data.initializer.federationFile]: loadFederation({
35
+ imports: getImports(data),
36
+ ...data
37
+ }),
38
+ [data.initializer.loggingFile]: loadLogging(data),
39
+ ".env": stringifyEnvs(data.env),
40
+ ...data.initializer.files
41
+ });
42
+ /**
43
+ * Generates JSON configuration files based on the package manager type.
44
+ * Creates different sets of configuration files for Deno vs Node.js/Bun environments,
45
+ * including compiler configs, package manifests, and development 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
+ "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 creating them.
63
+ * Displays what files would be created and shows their content for user review.
64
+ * This allows users to preview the initialization process before committing to it.
65
+ *
66
+ * @param data - The initialization command data with files and JSONs prepared
67
+ * @returns The processed data with recommendations displayed
68
+ */
69
+ const recommendFiles = (data) => pipe(data, tap(noticeFilesToCreate), tap(processAllFiles(displayFile)), tap(noticeFilesToInsert), set("files", ({ jsons }) => jsons), tap(processAllFiles(displayFile)));
70
+ /**
71
+ * Actually creates files on the filesystem during normal (non-dry-run) execution.
72
+ * Merges text files and JSON files together and writes them to disk.
73
+ * This performs the actual file system operations to initialize the project.
74
+ *
75
+ * @param data - The initialization command data with files and JSONs prepared
76
+ * @returns The processed data after files have been created
77
+ */
78
+ const createFiles = (data) => pipe(data, set("files", ({ jsons, files }) => toMerged(files, jsons)), tap(processAllFiles(createFile)));
79
+ /**
80
+ * Higher-order function that processes all files with a given processing function.
81
+ * Takes a processing function (either display or create) and applies it to all files
82
+ * in the target directory, handling path resolution and content patching.
83
+ *
84
+ * @param process - Function to process each file (either display or create)
85
+ * @returns A function that processes all files in the given directory with the provided processor
86
+ */
87
+ const processAllFiles = (process) => ({ dir, files }) => pipe(files, entries, forEach(pipeLazy(joinDir(dir), apply(patchContent), apply(process))));
88
+ /**
89
+ * Patches file content by either merging JSON objects or appending text content.
90
+ * Handles existing files by reading their current content and intelligently combining
91
+ * it with new content based on the content type (JSON vs text).
92
+ *
93
+ * @param path - The file path to patch
94
+ * @param content - The new content (either string or object)
95
+ * @returns A tuple containing the file path and the final content string
96
+ */
97
+ async function patchContent(path, content) {
98
+ const prev = await readFileIfExists(path);
99
+ const data = typeof content === "object" ? mergeJson(prev, content) : appendText(prev, content);
100
+ return [path, data];
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
+ *
107
+ * @param prev - The previous JSON content as string
108
+ * @param data - The new data object to merge
109
+ * @returns Formatted JSON string with merged content
110
+ */
111
+ const mergeJson = (prev, data) => pipe(prev ? JSON.parse(prev) : {}, merge(data), formatJson);
112
+ /**
113
+ * Appends new text content to existing text content line by line.
114
+ * Concatenates new content lines with existing content lines,
115
+ * preserving line structure and formatting.
116
+ *
117
+ * @param prev - The previous text content
118
+ * @param data - The new text content to append
119
+ * @returns Combined text content as a single string
120
+ */
121
+ const appendText = (prev, data) => prev ? `${prev}\n${data}` : data;
122
+ /**
123
+ * Safely reads a file if it exists, returns empty string if it doesn't exist.
124
+ * Provides error handling to distinguish between "file not found" and other
125
+ * file system errors, throwing only for unexpected errors.
126
+ *
127
+ * @param path - The file path to read
128
+ * @returns The file content as string, or empty string if file doesn't exist
129
+ * @throws Error if file access fails for reasons other than file not existing
130
+ */
131
+ async function readFileIfExists(path) {
132
+ try {
133
+ return await readFile(path, "utf8");
134
+ } catch (e) {
135
+ throwUnlessNotExists(e);
136
+ return "";
137
+ }
138
+ }
139
+
140
+ //#endregion
141
+ export { patchFiles, recommendPatchFiles };
@@ -0,0 +1,23 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { exit, runSubCommand } from "../../utils.js";
5
+
6
+ //#region src/init/action/precommand.ts
7
+ /**
8
+ * Runs the precommand specified in the initializer to set up the project.
9
+ *
10
+ * @param data - The initialization command data containing the initializer command and directory
11
+ * @returns A promise that resolves when the precommand has been executed
12
+ */
13
+ const runPrecommand = ({ initializer: { command }, dir }) => runSubCommand(command, {
14
+ cwd: dir,
15
+ stdio: "inherit"
16
+ }).catch((e) => {
17
+ console.error("Failed to run the precommand:", e);
18
+ exit(1);
19
+ });
20
+ var precommand_default = runPrecommand;
21
+
22
+ //#endregion
23
+ export { precommand_default as default };
@@ -0,0 +1,24 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { notEmpty } from "../../utils.js";
5
+ import { noticeDeps, noticeDepsIfExist, noticeDevDepsIfExist } from "./notice.js";
6
+ import { getDependencies, getDevDependencies } from "./deps.js";
7
+ import { isDeno } from "./utils.js";
8
+ import { map, peek, pipeLazy, tap, unless, when } from "@fxts/core";
9
+
10
+ //#region src/init/action/recommend.ts
11
+ const recommendDeps = pipeLazy(getDependencies, Object.entries, when(notEmpty, tap(noticeDepsIfExist)), peek(noticeDeps));
12
+ const recommendDevDeps = pipeLazy(getDevDependencies, Object.entries, when(notEmpty, tap(noticeDevDepsIfExist)), map(noticeDeps));
13
+ /**
14
+ * Recommends dependencies and devDependencies to be added to package.json.
15
+ * Skips devDependencies recommendation if the package manager is Deno.
16
+ *
17
+ * @param data - The initialization command data
18
+ * @returns An InitCommandIo function that performs the recommendation
19
+ */
20
+ const recommendDependencies = pipeLazy(tap(recommendDeps), unless(isDeno, tap(recommendDevDeps)));
21
+ var recommend_default = recommendDependencies;
22
+
23
+ //#endregion
24
+ export { recommend_default as default };
@@ -0,0 +1,31 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { merge, set } from "../../utils.js";
5
+ import webframeworks_default from "../webframeworks.js";
6
+ import { kvStores, messageQueues } from "../lib.js";
7
+ import { basename, normalize } from "node:path";
8
+ import { realpath } from "node:fs/promises";
9
+ import { pipe } from "@fxts/core";
10
+ import { existsSync } from "node:fs";
11
+
12
+ //#region src/init/action/set.ts
13
+ /**
14
+ * Set all necessary data for initializing the project.
15
+ * This function orchestrates the setting of project name, initializer,
16
+ * key-value store, message queue, and environment variables by calling
17
+ * individual setter functions for each piece of data.
18
+ *
19
+ * @param data - The initial command options provided by the user
20
+ * @returns A promise resolving to a complete InitCommandData object
21
+ */
22
+ const setData = (data) => pipe(data, setProjectName, setInitializer, setKv, setMq, setEnv);
23
+ var set_default = setData;
24
+ const setProjectName = set("projectName", async ({ dir }) => basename(existsSync(dir) ? await realpath(dir) : normalize(dir)));
25
+ const setInitializer = set("initializer", ({ webFramework, projectName, packageManager }) => webframeworks_default[webFramework].init(projectName, packageManager));
26
+ const setKv = set("kv", ({ kvStore }) => kvStores[kvStore]);
27
+ const setMq = set("mq", ({ messageQueue }) => messageQueues[messageQueue]);
28
+ const setEnv = set("env", ({ kv, mq }) => merge(kv.env)(mq.env));
29
+
30
+ //#endregion
31
+ export { set_default as default };
@@ -0,0 +1,57 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { replace } from "../../utils.js";
5
+ import { readTemplate } from "../lib.js";
6
+ import { entries, join, map, pipe } from "@fxts/core";
7
+ import { toMerged } from "es-toolkit";
8
+
9
+ //#region src/init/action/templates.ts
10
+ /**
11
+ * Loads the federation configuration file content from template.
12
+ * Reads the default federation template and replaces placeholders with actual configuration values.
13
+ *
14
+ * @param param0 - Configuration object containing imports, project name, KV store, message queue, and package manager
15
+ * @returns The complete federation configuration file content as a string
16
+ */
17
+ const loadFederation = ({ imports, projectName, kv, mq, packageManager }) => pipe("defaults/federation.ts", readTemplate, replace(/\/\* imports \*\//, imports), replace(/\/\* logger \*\//, JSON.stringify(projectName)), replace(/\/\* kv \*\//, convertEnv(kv.object, packageManager)), replace(/\/\* queue \*\//, convertEnv(mq.object, packageManager)));
18
+ /**
19
+ * Loads the logging configuration file content from template.
20
+ * Reads the default logging template and replaces the project name placeholder.
21
+ *
22
+ * @param param0 - Destructured object containing the project name
23
+ * @returns The complete logging configuration file content as a string
24
+ */
25
+ const loadLogging = ({ projectName }) => pipe("defaults/logging.ts", readTemplate, replace(/\/\* project name \*\//, JSON.stringify(projectName)));
26
+ /**
27
+ * Generates import statements for KV store and message queue dependencies.
28
+ * Merges imports from both KV and MQ configurations and creates proper ES module import syntax.
29
+ *
30
+ * @param param0 - Destructured object containing kv and mq configurations
31
+ * @returns A multi-line string containing all necessary import statements
32
+ */
33
+ const getImports = ({ kv, mq }) => pipe(toMerged(kv.imports, mq.imports), entries, map(([module, { "default": defaultImport = "",...imports }]) => [
34
+ module,
35
+ defaultImport,
36
+ getAlias(imports)
37
+ ]), map(([module, defaultImport, namedImports]) => `import ${[defaultImport, namedImports.length > 0 ? `{ ${namedImports} }` : ""].filter((x) => x.length > 0).join(", ")} from ${JSON.stringify(module)};`), join("\n"));
38
+ /**
39
+ * Converts import mappings to named import string with aliases.
40
+ * Creates proper ES module named import syntax, using aliases when the import name differs from the local name.
41
+ *
42
+ * @param imports - A record mapping import names to their local aliases
43
+ * @returns A comma-separated string of named imports with aliases where needed
44
+ */
45
+ const getAlias = (imports) => pipe(imports, entries, map(([name, alias]) => name === alias ? name : `${name} as ${alias}`), join(", "));
46
+ /**
47
+ * Converts Node.js environment variable access to Deno-compatible syntax when needed.
48
+ * Transforms `process.env.VAR_NAME` to `Deno.env.get("VAR_NAME")` for Deno projects.
49
+ *
50
+ * @param obj - The object string containing potential environment variable references
51
+ * @param pm - The package manager (runtime) being used
52
+ * @returns The converted object string with appropriate environment variable access syntax
53
+ */
54
+ const convertEnv = (obj, pm) => pm === "deno" && /process\.env\.(\w+)/.test(obj) ? obj.replaceAll(/process\.env\.(\w+)/, (_, g1) => `Deno.env.get("${g1}")`) : obj;
55
+
56
+ //#endregion
57
+ export { getImports, loadFederation, loadLogging };
@@ -0,0 +1,50 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { join } from "node:path";
5
+
6
+ //#region src/init/action/utils.ts
7
+ const isDry = ({ dryRun }) => dryRun;
8
+ const hasCommand = (data) => !!data.initializer.command;
9
+ const isDeno = ({ packageManager }) => packageManager === "deno";
10
+ const joinDir = (dir) => ([filename, content]) => [join(dir, ...filename.split("/")), content];
11
+ /**
12
+ * Stringify an object into a valid `.env` file format.
13
+ * From `@std/dotenv/stringify`.
14
+ *
15
+ * @example Usage
16
+ * ```ts
17
+ * import { stringifyEnvs } from "./utils.ts";
18
+ * import { assertEquals } from "@std/assert";
19
+ *
20
+ * const object = { GREETING: "hello world" };
21
+ * assertEquals(stringifyEnvs(object), "GREETING='hello world'");
22
+ * ```
23
+ *
24
+ * @param object object to be stringified
25
+ * @returns string of object
26
+ */
27
+ function stringifyEnvs(object) {
28
+ const lines = [];
29
+ for (const [key, value] of Object.entries(object)) {
30
+ let quote;
31
+ let escapedValue = value ?? "";
32
+ if (key.startsWith("#")) {
33
+ console.warn(`key starts with a '#' indicates a comment and is ignored: '${key}'`);
34
+ continue;
35
+ } else if (escapedValue.includes("\n") || escapedValue.includes("'")) {
36
+ escapedValue = escapedValue.replaceAll("\n", "\\n");
37
+ quote = `"`;
38
+ } else if (escapedValue.match(/\W/)) quote = "'";
39
+ if (quote) {
40
+ escapedValue = escapedValue.replaceAll(quote, `\\${quote}`);
41
+ escapedValue = `${quote}${escapedValue}${quote}`;
42
+ }
43
+ const line = `${key}=${escapedValue}`;
44
+ lines.push(line);
45
+ }
46
+ return lines.join("\n");
47
+ }
48
+
49
+ //#endregion
50
+ export { hasCommand, isDeno, isDry, joinDir, stringifyEnvs };
@@ -0,0 +1,82 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { getCwd, getOsType, runSubCommand } from "../../utils.js";
5
+ import { isDirectoryEmpty, logger } from "../lib.js";
6
+ import { printError } from "@optique/run";
7
+ import { identity, pipe, when } from "@fxts/core";
8
+ import { input } from "@inquirer/prompts";
9
+ import { message } from "@optique/core/message";
10
+ import toggle from "inquirer-toggle";
11
+
12
+ //#region src/init/ask/dir.ts
13
+ /**
14
+ * Fills in the project directory by prompting the user if not provided.
15
+ * If the directory is not empty, asks the user whether to use it anyway.
16
+ * If the user agrees, offers to move existing contents to trash.
17
+ * Else, recursively prompts for a new directory until a valid one is provided.
18
+ *
19
+ * @param options - Initialization options possibly containing a directory
20
+ * @returns A promise resolving to options with a guaranteed directory
21
+ */
22
+ const fillDir = async (options) => {
23
+ const dir = options.dir ?? await askDir(getCwd());
24
+ return await askIfNonEmpty(dir) ? {
25
+ ...options,
26
+ dir
27
+ } : await fillDir(options);
28
+ };
29
+ var dir_default = fillDir;
30
+ const askDir = (cwd) => input({
31
+ message: "Project directory:",
32
+ default: cwd
33
+ });
34
+ const askIfNonEmpty = async (dir) => {
35
+ if (await isDirectoryEmpty(dir)) return true;
36
+ if (await askNonEmpty(dir)) return await moveDirToTrash(dir);
37
+ return false;
38
+ };
39
+ const askNonEmpty = (dir) => toggle.default({
40
+ message: `Directory "${dir}" is not empty.
41
+ Do you want to use it anyway?`,
42
+ default: false
43
+ });
44
+ const moveDirToTrash = (dir) => pipe(dir, askMoveToTrash, when(identity, moveToTrash(dir)));
45
+ const askMoveToTrash = (dir) => toggle.default({
46
+ message: `Do you want to move the contents of "${dir}" to the trash?
47
+ If you choose "No", you should choose another directory.`,
48
+ default: false
49
+ });
50
+ const moveToTrash = (dir) => () => pipe(getOsType(), getTrashCommand, (fn) => fn(dir), (cmd) => runSubCommand(cmd, { stdio: "ignore" }), () => true).catch((e) => {
51
+ logger.error(e);
52
+ printError(message`Failed to move ${dir} to trash.
53
+ Please move it manually.`);
54
+ return false;
55
+ });
56
+ const getTrashCommand = (os) => trashCommands[os] ?? trashCommands.linux;
57
+ const trashCommands = {
58
+ darwin: (dir) => ["trash", dir],
59
+ win32: (dir) => [
60
+ "powershell",
61
+ "-Command",
62
+ getPowershellTrashCommand(dir)
63
+ ],
64
+ linux: (dir) => [
65
+ "rm",
66
+ "-rf",
67
+ dir
68
+ ]
69
+ };
70
+ const getPowershellTrashCommand = (dir) => [
71
+ "Add-Type",
72
+ "-AssemblyName",
73
+ "Microsoft.VisualBasic;",
74
+ "[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteDirectory('",
75
+ dir,
76
+ "',",
77
+ "'OnlyErrorDialogs',",
78
+ "'SendToRecycleBin')"
79
+ ].join(" ");
80
+
81
+ //#endregion
82
+ export { dir_default as default };
@@ -0,0 +1,33 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { KV_STORE } from "../const.js";
5
+ import { kvStores } from "../lib.js";
6
+ import { select } from "@inquirer/prompts";
7
+
8
+ //#region src/init/ask/kv.ts
9
+ /**
10
+ * Fills in the key-value store by prompting the user if not provided.
11
+ * Ensures the selected KV store is compatible with the chosen package manager.
12
+ *
13
+ * @param options - Initialization options possibly containing a kvStore and packageManager
14
+ * @returns A promise resolving to options with a guaranteed kvStore
15
+ */
16
+ const fillKvStore = async (options) => ({
17
+ ...options,
18
+ kvStore: options.kvStore ?? await askKvStore(options.packageManager)
19
+ });
20
+ var kv_default = fillKvStore;
21
+ const askKvStore = (pm) => select({
22
+ message: "Choose the key-value store to use",
23
+ choices: KV_STORE.map(choiceKvStore(pm))
24
+ });
25
+ const choiceKvStore = (pm) => (value) => ({
26
+ name: isKvSupportsPm(value, pm) ? value : `${value} (not supported with ${pm})`,
27
+ value,
28
+ disabled: !isKvSupportsPm(value, pm)
29
+ });
30
+ const isKvSupportsPm = (kv, pm) => kvStores[kv].packageManagers.includes(pm);
31
+
32
+ //#endregion
33
+ export { kv_default as default };
@@ -0,0 +1,16 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import dir_default from "./dir.js";
5
+ import kv_default from "./kv.js";
6
+ import mq_default from "./mq.js";
7
+ import pm_default from "./pm.js";
8
+ import wf_default from "./wf.js";
9
+ import { pipe } from "@fxts/core";
10
+
11
+ //#region src/init/ask/mod.ts
12
+ const askOptions = (options) => pipe(options, dir_default, wf_default, pm_default, mq_default, kv_default);
13
+ var ask_default = askOptions;
14
+
15
+ //#endregion
16
+ export { ask_default as default };
@@ -0,0 +1,33 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { MESSAGE_QUEUE } from "../const.js";
5
+ import { messageQueues } from "../lib.js";
6
+ import { select } from "@inquirer/prompts";
7
+
8
+ //#region src/init/ask/mq.ts
9
+ /**
10
+ * Fills in the message queue by prompting the user if not provided.
11
+ * Ensures the selected message queue is compatible with the chosen package manager.
12
+ *
13
+ * @param options - Initialization options possibly containing a messageQueue and packageManager
14
+ * @returns A promise resolving to options with a guaranteed messageQueue
15
+ */
16
+ const fillMessageQueue = async (options) => ({
17
+ ...options,
18
+ messageQueue: options.messageQueue ?? await askMessageQueue(options.packageManager)
19
+ });
20
+ var mq_default = fillMessageQueue;
21
+ const askMessageQueue = (pm) => select({
22
+ message: "Choose the message queue to use",
23
+ choices: MESSAGE_QUEUE.map(choiceMessageQueue(pm))
24
+ });
25
+ const choiceMessageQueue = (pm) => (value) => ({
26
+ name: isMqSupportsPm(value, pm) ? value : `${value} (not supported with ${pm})`,
27
+ value,
28
+ disabled: !isMqSupportsPm(value, pm)
29
+ });
30
+ const isMqSupportsPm = (mq, pm) => messageQueues[mq].packageManagers.includes(pm);
31
+
32
+ //#endregion
33
+ export { mq_default as default };
@@ -0,0 +1,49 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { PACKAGE_MANAGER } from "../const.js";
5
+ import webframeworks_default from "../webframeworks.js";
6
+ import { getInstallUrl, getLabel, isPackageManagerAvailable } from "../lib.js";
7
+ import { print } from "@optique/run";
8
+ import { select } from "@inquirer/prompts";
9
+ import { message } from "@optique/core/message";
10
+
11
+ //#region src/init/ask/pm.ts
12
+ /**
13
+ * Fills in the package manager by prompting the user if not provided.
14
+ * Ensures the selected package manager is compatible with the chosen web framework.
15
+ * If the selected package manager is not installed, informs the user and prompts again.
16
+ *
17
+ * @param options - Initialization options possibly containing a packageManager and webFramework
18
+ * @returns A promise resolving to options with a guaranteed packageManager
19
+ */
20
+ const fillPackageManager = async ({ packageManager,...options }) => {
21
+ const pm = packageManager ?? await askPackageManager(options.webFramework);
22
+ if (await isPackageManagerAvailable(pm)) return {
23
+ ...options,
24
+ packageManager: pm
25
+ };
26
+ noticeInstallUrl(pm);
27
+ return await fillPackageManager(options);
28
+ };
29
+ var pm_default = fillPackageManager;
30
+ const askPackageManager = (wf) => select({
31
+ message: "Choose the package manager to use",
32
+ choices: PACKAGE_MANAGER.map(choicePackageManager(wf))
33
+ });
34
+ const choicePackageManager = (wf) => (value) => ({
35
+ name: isWfSupportsPm(wf, value) ? value : `${value} (not supported with ${webframeworks_default[wf].label})`,
36
+ value,
37
+ disabled: !isWfSupportsPm(wf, value)
38
+ });
39
+ const isWfSupportsPm = (wf, pm) => webframeworks_default[wf].packageManagers.includes(pm);
40
+ const noticeInstallUrl = (pm) => {
41
+ const label = getLabel(pm);
42
+ const url = getInstallUrl(pm);
43
+ print(message` Package manager ${label} is not installed.`);
44
+ print(message` You can install it from following link: ${url}`);
45
+ print(message` or choose another package manager:`);
46
+ };
47
+
48
+ //#endregion
49
+ export { pm_default as default };
@@ -0,0 +1,29 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { WEB_FRAMEWORK } from "../const.js";
5
+ import webframeworks_default from "../webframeworks.js";
6
+ import { select } from "@inquirer/prompts";
7
+
8
+ //#region src/init/ask/wf.ts
9
+ /**
10
+ * Fills in the web framework by prompting the user if not provided.
11
+ *
12
+ * @param options - Initialization options possibly containing a webFramework
13
+ * @returns A promise resolving to options with a guaranteed webFramework
14
+ */
15
+ const fillWebFramework = async (options) => ({
16
+ ...options,
17
+ webFramework: options.webFramework ?? await askWebFramework()
18
+ });
19
+ var wf_default = fillWebFramework;
20
+ const askWebFramework = () => select({
21
+ message: "Choose the web framework to use",
22
+ choices: WEB_FRAMEWORK.map((value) => ({
23
+ name: webframeworks_default[value].label,
24
+ value
25
+ }))
26
+ });
27
+
28
+ //#endregion
29
+ export { wf_default as default };
@@ -0,0 +1,25 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { KV_STORE, MESSAGE_QUEUE, PACKAGE_MANAGER, WEB_FRAMEWORK } from "./const.js";
5
+ import { argument, choice, command, constant, message, object, option, optional } from "@optique/core";
6
+ import { path } from "@optique/run";
7
+
8
+ //#region src/init/command.ts
9
+ const joinSep = (str) => str.join(" | ");
10
+ const webFramework = optional(option("-w", "--web-framework", choice(WEB_FRAMEWORK, { metavar: `WEB_FRAMEWORK: ${joinSep(WEB_FRAMEWORK)}` })));
11
+ const packageManager = optional(option("-p", "--package-manager", choice(PACKAGE_MANAGER, { metavar: `PACKAGE_MANAGER: ${joinSep(PACKAGE_MANAGER)}` })));
12
+ const kvStore = optional(option("-k", "--kv-store", choice(KV_STORE, { metavar: `KV_STORE: ${joinSep(KV_STORE)}` })));
13
+ const messageQueue = optional(option("-m", "--message-queue", choice(MESSAGE_QUEUE, { metavar: `MESSAGE_QUEUE: ${joinSep(MESSAGE_QUEUE)}` })));
14
+ const initCommand = command("init", object({
15
+ command: constant("init"),
16
+ dir: optional(argument(path({ metavar: "DIRECTORY" }))),
17
+ webFramework,
18
+ packageManager,
19
+ kvStore,
20
+ messageQueue,
21
+ dryRun: option("-d", "--dry-run")
22
+ }), { description: message`Initialize a new Fedify project directory.` });
23
+
24
+ //#endregion
25
+ export { initCommand };
@@ -0,0 +1,31 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ //#region src/init/const.ts
5
+ const PACKAGE_MANAGER = [
6
+ "deno",
7
+ "pnpm",
8
+ "bun",
9
+ "yarn",
10
+ "npm"
11
+ ];
12
+ const WEB_FRAMEWORK = [
13
+ "hono",
14
+ "nitro",
15
+ "next",
16
+ "express"
17
+ ];
18
+ const MESSAGE_QUEUE = [
19
+ "denokv",
20
+ "redis",
21
+ "postgres",
22
+ "amqp"
23
+ ];
24
+ const KV_STORE = [
25
+ "denokv",
26
+ "redis",
27
+ "postgres"
28
+ ];
29
+
30
+ //#endregion
31
+ export { KV_STORE, MESSAGE_QUEUE, PACKAGE_MANAGER, WEB_FRAMEWORK };
@@ -0,0 +1,24 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ //#region src/init/json/biome.json
5
+ var $schema = "https://biomejs.dev/schemas/1.8.3/schema.json";
6
+ var organizeImports = { "enabled": true };
7
+ var formatter = {
8
+ "enabled": true,
9
+ "indentStyle": "space",
10
+ "indentWidth": 2
11
+ };
12
+ var linter = {
13
+ "enabled": true,
14
+ "rules": { "recommended": true }
15
+ };
16
+ var biome_default = {
17
+ $schema,
18
+ organizeImports,
19
+ formatter,
20
+ linter
21
+ };
22
+
23
+ //#endregion
24
+ export { biome_default as default };