@fedify/cli 2.0.0-pr.474.1879 → 2.0.0-pr.479.1900

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 (63) hide show
  1. package/deno.json +1 -1
  2. package/dist/deno.js +1 -1
  3. package/dist/imagerenderer.js +1 -1
  4. package/dist/inbox.js +2 -5
  5. package/dist/init/action/configs.js +22 -13
  6. package/dist/init/action/const.js +10 -0
  7. package/dist/init/action/deps.js +26 -28
  8. package/dist/init/action/install.js +11 -13
  9. package/dist/init/action/mod.js +1 -1
  10. package/dist/init/action/notice.js +12 -19
  11. package/dist/init/action/patch.js +33 -27
  12. package/dist/init/action/precommand.js +7 -2
  13. package/dist/init/action/recommend.js +1 -1
  14. package/dist/init/action/set.js +1 -1
  15. package/dist/init/action/templates.js +2 -1
  16. package/dist/init/ask/kv.js +24 -13
  17. package/dist/init/ask/mq.js +26 -13
  18. package/dist/init/command.js +20 -3
  19. package/dist/init/lib.js +20 -13
  20. package/dist/init/mod.js +2 -1
  21. package/dist/init/templates/nitro/.env.test.tpl +1 -0
  22. package/dist/init/templates/nitro/nitro.config.ts.tpl +12 -3
  23. package/dist/init/test/action.js +15 -0
  24. package/dist/init/test/create.js +100 -0
  25. package/dist/init/test/fill.js +26 -0
  26. package/dist/init/test/lookup.js +189 -0
  27. package/dist/init/test/run.js +26 -0
  28. package/dist/init/test/utils.js +17 -0
  29. package/dist/init/webframeworks.js +31 -28
  30. package/dist/mod.js +4 -2
  31. package/dist/nodeinfo.js +6 -6
  32. package/dist/utils.js +75 -8
  33. package/package.json +5 -5
  34. package/src/inbox.tsx +1 -15
  35. package/src/init/action/configs.ts +56 -13
  36. package/src/init/action/const.ts +9 -0
  37. package/src/init/action/deps.ts +98 -30
  38. package/src/init/action/install.ts +17 -52
  39. package/src/init/action/notice.ts +16 -14
  40. package/src/init/action/patch.ts +48 -27
  41. package/src/init/action/precommand.ts +9 -2
  42. package/src/init/action/set.ts +2 -15
  43. package/src/init/action/templates.ts +3 -2
  44. package/src/init/action/utils.ts +1 -1
  45. package/src/init/ask/kv.ts +64 -21
  46. package/src/init/ask/mq.ts +69 -20
  47. package/src/init/command.ts +39 -1
  48. package/src/init/lib.ts +31 -28
  49. package/src/init/mod.ts +2 -1
  50. package/src/init/templates/nitro/.env.test.tpl +1 -0
  51. package/src/init/templates/nitro/nitro.config.ts.tpl +12 -3
  52. package/src/init/test/action.ts +25 -0
  53. package/src/init/test/create.ts +137 -0
  54. package/src/init/test/fill.ts +61 -0
  55. package/src/init/test/lookup.ts +253 -0
  56. package/src/init/test/run.ts +42 -0
  57. package/src/init/test/types.ts +34 -0
  58. package/src/init/test/utils.ts +21 -0
  59. package/src/init/types.ts +4 -3
  60. package/src/init/webframeworks.ts +35 -18
  61. package/src/mod.ts +10 -1
  62. package/src/nodeinfo.ts +2 -1
  63. package/src/utils.ts +128 -10
@@ -1,7 +1,7 @@
1
- import { apply, entries, forEach, pipe, pipeLazy, tap } from "@fxts/core";
1
+ import { always, apply, entries, map, pipe, pipeLazy, tap } from "@fxts/core";
2
2
  import { toMerged } from "es-toolkit";
3
3
  import { readFile } from "node:fs/promises";
4
- import { formatJson, merge, set } from "../../utils.ts";
4
+ import { formatJson, merge, replaceAll, set } from "../../utils.ts";
5
5
  import { createFile, throwUnlessNotExists } from "../lib.ts";
6
6
  import type { InitCommandData } from "../types.ts";
7
7
  import {
@@ -23,7 +23,8 @@ import { joinDir, stringifyEnvs } from "./utils.ts";
23
23
  * Handles both dry-run mode (recommending files) and actual file creation.
24
24
  * Orchestrates the entire file generation and writing process.
25
25
  *
26
- * @param data - The initialization command data containing project configuration
26
+ * @param data - The initialization command data containing project
27
+ * configuration
27
28
  * @returns A processed data object with files and JSONs ready for creation
28
29
  */
29
30
  export const patchFiles = (data: InitCommandData) =>
@@ -44,8 +45,9 @@ export const recommendPatchFiles = (data: InitCommandData) =>
44
45
 
45
46
  /**
46
47
  * Generates text-based files (TypeScript, environment files) for the project.
47
- * Creates federation configuration, logging setup, environment variables, and framework-specific files
48
- * by processing templates and combining them with project-specific data.
48
+ * Creates federation configuration, logging setup, environment variables, and
49
+ * framework-specific files by processing templates and combining them with
50
+ * project-specific data.
49
51
  *
50
52
  * @param data - The initialization command data
51
53
  * @returns A record of file paths to their string content
@@ -64,8 +66,9 @@ const getFiles = <
64
66
 
65
67
  /**
66
68
  * Generates JSON configuration files based on the package manager type.
67
- * Creates different sets of configuration files for Deno vs Node.js/Bun environments,
68
- * including compiler configs, package manifests, and development tool configurations.
69
+ * Creates different sets of configuration files for Deno vs other environments,
70
+ * including compiler configs, package manifests, and development
71
+ * tool configurations.
69
72
  *
70
73
  * @param data - The initialization command data
71
74
  * @returns A record of file paths to their JSON object content
@@ -80,7 +83,9 @@ const getJsons = <
80
83
  [devToolConfigs["vscExtDeno"].path]: devToolConfigs["vscExtDeno"].data,
81
84
  }
82
85
  : {
83
- "tsconfig.json": loadTsConfig(data).data,
86
+ ...(data.initializer.compilerOptions
87
+ ? { "tsconfig.json": loadTsConfig(data).data }
88
+ : {}),
84
89
  "package.json": loadPackageJson(data).data,
85
90
  [devToolConfigs["biome"].path]: devToolConfigs["biome"].data,
86
91
  [devToolConfigs["vscSet"].path]: devToolConfigs["vscSet"].data,
@@ -88,9 +93,10 @@ const getJsons = <
88
93
  };
89
94
 
90
95
  /**
91
- * Handles dry-run mode by recommending files to be created without actually creating them.
92
- * Displays what files would be created and shows their content for user review.
93
- * This allows users to preview the initialization process before committing to it.
96
+ * Handles dry-run mode by recommending files to be created without actually
97
+ * creating them.
98
+ * Displays what files would be created and shows their content for user review,
99
+ * so users can preview the initialization process before committing to it.
94
100
  *
95
101
  * @param data - The initialization command data with files and JSONs prepared
96
102
  * @returns The processed data with recommendations displayed
@@ -106,7 +112,7 @@ const recommendFiles = (data: InitCommandWithFiles) =>
106
112
  );
107
113
 
108
114
  /**
109
- * Actually creates files on the filesystem during normal (non-dry-run) execution.
115
+ * Actually creates files on the filesystem during normal execution.
110
116
  * Merges text files and JSON files together and writes them to disk.
111
117
  * This performs the actual file system operations to initialize the project.
112
118
  *
@@ -126,8 +132,8 @@ interface InitCommandWithFiles extends InitCommandData {
126
132
  }
127
133
 
128
134
  /**
129
- * Higher-order function that processes all files with a given processing function.
130
- * Takes a processing function (either display or create) and applies it to all files
135
+ * Processes all files with a given processing function.
136
+ * Takes a processor (either display or create) and applies it to all files
131
137
  * in the target directory, handling path resolution and content patching.
132
138
  *
133
139
  * @param process - Function to process each file (either display or create)
@@ -140,19 +146,20 @@ const processAllFiles = (
140
146
  pipe(
141
147
  files,
142
148
  entries,
143
- forEach(
149
+ map(
144
150
  pipeLazy(
145
151
  joinDir(dir),
146
152
  apply(patchContent),
147
153
  apply(process),
148
154
  ),
149
155
  ),
156
+ Array.fromAsync,
150
157
  );
151
158
 
152
159
  /**
153
- * Patches file content by either merging JSON objects or appending text content.
154
- * Handles existing files by reading their current content and intelligently combining
155
- * it with new content based on the content type (JSON vs text).
160
+ * Patches file content by either merging JSON or appending text content.
161
+ * Handles existing files by reading their current content and intelligently
162
+ * combining it with new content based on the content type (JSON vs text).
156
163
  *
157
164
  * @param path - The file path to patch
158
165
  * @param content - The new content (either string or object)
@@ -173,13 +180,32 @@ async function patchContent(
173
180
  * Merges new JSON data with existing JSON content and formats the result.
174
181
  * Parses existing JSON content (if any) and deep merges it with new data,
175
182
  * then formats the result for consistent output.
183
+ * Supports JSONC (JSON with Comments) by removing comments before parsing.
176
184
  *
177
185
  * @param prev - The previous JSON content as string
178
186
  * @param data - The new data object to merge
179
187
  * @returns Formatted JSON string with merged content
180
188
  */
181
189
  const mergeJson = (prev: string, data: object): string =>
182
- pipe(prev ? JSON.parse(prev) : {}, merge(data), formatJson);
190
+ pipe(
191
+ prev ? JSON.parse(removeJsonComments(prev)) : {},
192
+ merge(data),
193
+ formatJson,
194
+ );
195
+
196
+ /**
197
+ * Removes single-line (//) and multi-line (/* *\/) comments from JSON string.
198
+ * This allows parsing JSONC (JSON with Comments) files.
199
+ *
200
+ * @param jsonString - The JSON string potentially containing comments
201
+ * @returns JSON string with comments removed
202
+ */
203
+ const removeJsonComments = (jsonString: string): string =>
204
+ pipe(
205
+ jsonString,
206
+ replaceAll(/\/\/.*$/gm, ""),
207
+ replaceAll(/\/\*[\s\S]*?\*\//g, ""),
208
+ );
183
209
 
184
210
  /**
185
211
  * Appends new text content to existing text content line by line.
@@ -202,11 +228,6 @@ const appendText = (prev: string, data: string) =>
202
228
  * @returns The file content as string, or empty string if file doesn't exist
203
229
  * @throws Error if file access fails for reasons other than file not existing
204
230
  */
205
- async function readFileIfExists(path: string): Promise<string> {
206
- try {
207
- return await readFile(path, "utf8");
208
- } catch (e) {
209
- throwUnlessNotExists(e);
210
- return "";
211
- }
212
- }
231
+ const readFileIfExists = (path: string): Promise<string> =>
232
+ readFile(path, "utf8")
233
+ .catch(pipeLazy(tap(throwUnlessNotExists), always("")));
@@ -1,4 +1,4 @@
1
- import { exit, runSubCommand } from "../../utils.ts";
1
+ import { CommandError, exit, runSubCommand } from "../../utils.ts";
2
2
  import type { InitCommandData } from "../types.ts";
3
3
 
4
4
  /**
@@ -15,7 +15,14 @@ const runPrecommand = ({
15
15
  cwd: dir,
16
16
  stdio: "inherit",
17
17
  }).catch((e) => {
18
- console.error("Failed to run the precommand:", e);
18
+ if (e instanceof CommandError) {
19
+ console.error("Failed to run the precommand.");
20
+ console.error("Command:", e.commandLine);
21
+ if (e.stderr) console.error("Error:", e.stderr);
22
+ if (e.stdout) console.error("Output:", e.stdout);
23
+ } else {
24
+ console.error("Failed to run the precommand:", e);
25
+ }
19
26
  exit(1);
20
27
  });
21
28
 
@@ -11,7 +11,6 @@ import type {
11
11
  KvStoreDescription,
12
12
  MessageQueue,
13
13
  MessageQueueDescription,
14
- PackageManager,
15
14
  } from "../types.ts";
16
15
  import webFrameworks from "../webframeworks.ts";
17
16
 
@@ -45,20 +44,8 @@ const setProjectName = set(
45
44
  );
46
45
 
47
46
  const setInitializer = set("initializer", <
48
- T extends {
49
- webFramework: keyof typeof webFrameworks;
50
- projectName: string;
51
- packageManager: PackageManager;
52
- },
53
- >({
54
- webFramework,
55
- projectName,
56
- packageManager,
57
- }: T) =>
58
- webFrameworks[webFramework].init(
59
- projectName,
60
- packageManager,
61
- ));
47
+ T extends InitCommandOptions & { projectName: string },
48
+ >(data: T) => webFrameworks[data.webFramework].init(data));
62
49
 
63
50
  const setKv = set("kv", <
64
51
  T extends { kvStore: KvStore },
@@ -81,6 +81,7 @@ export const getAlias = (imports: Record<string, string>) =>
81
81
  join(", "),
82
82
  );
83
83
 
84
+ const ENV_REG_EXP = /process\.env\.(\w+)/g;
84
85
  /**
85
86
  * Converts Node.js environment variable access to Deno-compatible syntax when needed.
86
87
  * Transforms `process.env.VAR_NAME` to `Deno.env.get("VAR_NAME")` for Deno projects.
@@ -90,6 +91,6 @@ export const getAlias = (imports: Record<string, string>) =>
90
91
  * @returns The converted object string with appropriate environment variable access syntax
91
92
  */
92
93
  export const convertEnv = (obj: string, pm: PackageManager) =>
93
- pm === "deno" && /process\.env\.(\w+)/.test(obj)
94
- ? obj.replaceAll(/process\.env\.(\w+)/, (_, g1) => `Deno.env.get("${g1}")`)
94
+ pm === "deno" && ENV_REG_EXP.test(obj)
95
+ ? obj.replaceAll(ENV_REG_EXP, (_, g1) => `Deno.env.get("${g1}")`)
95
96
  : obj;
@@ -6,7 +6,7 @@ export const isDry = ({ dryRun }: InitCommandData) => dryRun;
6
6
  export const hasCommand = (data: InitCommandData) => !!data.initializer.command;
7
7
 
8
8
  export const isDeno = (
9
- { packageManager }: InitCommandData,
9
+ { packageManager }: Pick<InitCommandData, "packageManager">,
10
10
  ) => packageManager === "deno";
11
11
 
12
12
  export const joinDir =
@@ -1,6 +1,8 @@
1
+ import { pipe, tap, throwError, unless, when } from "@fxts/core/index.js";
1
2
  import { select } from "@inquirer/prompts";
3
+ import { printErrorMessage } from "../../utils.ts";
2
4
  import { KV_STORE } from "../const.ts";
3
- import { kvStores } from "../lib.ts";
5
+ import { isTest, kvStores } from "../lib.ts";
4
6
  import type { KvStore, PackageManager } from "../types.ts";
5
7
 
6
8
  /**
@@ -11,29 +13,70 @@ import type { KvStore, PackageManager } from "../types.ts";
11
13
  * @returns A promise resolving to options with a guaranteed kvStore
12
14
  */
13
15
  const fillKvStore: //
14
- <T extends { kvStore?: KvStore; packageManager: PackageManager }>(
15
- options: T,
16
- ) => Promise<T & { kvStore: KvStore }> = async (options) => //
17
- ({
18
- ...options,
19
- kvStore: options.kvStore ?? await askKvStore(options.packageManager),
20
- });
16
+ <
17
+ T extends {
18
+ kvStore?: KvStore;
19
+ packageManager: PackageManager;
20
+ testMode: boolean;
21
+ },
22
+ >(options: T) => Promise<KvDefined<T>> //
23
+ = (options) =>
24
+ pipe(
25
+ options,
26
+ when(isKvStoreEmpty, askKvStore) as <
27
+ T extends { kvStore?: KvStore; packageManager: PackageManager },
28
+ >(options: T) => KvDefined<T>,
29
+ unless(
30
+ isKvSupportsPm,
31
+ (opt: KvDefined<typeof options>) =>
32
+ pipe(
33
+ opt,
34
+ when(isTest, throwError(unmatchedWhileTesting)),
35
+ tap(noticeUnmatched),
36
+ fillKvStore,
37
+ ),
38
+ ),
39
+ ) as Promise<KvDefined<typeof options>>;
21
40
 
22
41
  export default fillKvStore;
23
42
 
24
- const askKvStore = (pm: PackageManager) =>
25
- select<KvStore>({
43
+ type KvDefined<T> = Omit<T, "kvStore"> & { kvStore: KvStore };
44
+
45
+ const isKvStoreEmpty = <T extends { kvStore?: KvStore }>(
46
+ options: T,
47
+ ): options is T & { kvStore: undefined } => !options.kvStore;
48
+
49
+ const askKvStore = async <
50
+ T extends { packageManager: PackageManager },
51
+ >(data: T): Promise<Omit<T, "kvStore"> & { kvStore: KvStore }> => ({
52
+ ...data,
53
+ kvStore: await select<KvStore>({
26
54
  message: "Choose the key-value store to use",
27
- choices: KV_STORE.map(choiceKvStore(pm)),
28
- });
29
-
30
- const choiceKvStore = (pm: PackageManager) => (value: KvStore) => ({
31
- name: isKvSupportsPm(value, pm)
32
- ? value
33
- : `${value} (not supported with ${pm})`,
34
- value,
35
- disabled: !isKvSupportsPm(value, pm),
55
+ choices: KV_STORE.map(choiceKvStore(data.packageManager)),
56
+ }),
57
+ });
58
+
59
+ const unmatchedWhileTesting = <
60
+ T extends { kvStore: KvStore; packageManager: PackageManager },
61
+ >({ kvStore: kv, packageManager: pm }: T) =>
62
+ new Error(
63
+ `Key-value store '${kv}' is not compatible with package manager '${pm}'`,
64
+ );
65
+
66
+ const noticeUnmatched = <
67
+ T extends { kvStore: KvStore; packageManager: PackageManager },
68
+ >({ kvStore: kv, packageManager: pm }: T) =>
69
+ printErrorMessage`Error: Key-value store '${kv}' is not compatible with package manager '${pm}'`;
70
+
71
+ const choiceKvStore = (pm: PackageManager) => (kv: KvStore) => ({
72
+ name: isKvSupportsPm({ kvStore: kv, packageManager: pm })
73
+ ? kv
74
+ : `${kv} (not supported with ${pm})`,
75
+ value: kv,
76
+ disabled: !isKvSupportsPm({ kvStore: kv, packageManager: pm }),
36
77
  });
37
78
 
38
- const isKvSupportsPm = (kv: KvStore, pm: PackageManager) =>
39
- kvStores[kv].packageManagers.includes(pm);
79
+ const isKvSupportsPm = <
80
+ T extends { kvStore: KvStore; packageManager: PackageManager },
81
+ >({ kvStore, packageManager }: T) =>
82
+ kvStores[kvStore].packageManagers.includes(packageManager);
@@ -1,6 +1,8 @@
1
+ import { pipe, tap, throwError, unless, when } from "@fxts/core/index.js";
1
2
  import { select } from "@inquirer/prompts";
3
+ import { printErrorMessage } from "../../utils.ts";
2
4
  import { MESSAGE_QUEUE } from "../const.ts";
3
- import { messageQueues } from "../lib.ts";
5
+ import { isTest, messageQueues } from "../lib.ts";
4
6
  import type { MessageQueue, PackageManager } from "../types.ts";
5
7
 
6
8
  /**
@@ -11,27 +13,74 @@ import type { MessageQueue, PackageManager } from "../types.ts";
11
13
  * @returns A promise resolving to options with a guaranteed messageQueue
12
14
  */
13
15
  const fillMessageQueue: //
14
- <T extends { messageQueue?: MessageQueue; packageManager: PackageManager }> //
15
- (options: T) => Promise<T & { messageQueue: MessageQueue }> = //
16
- async (options) => ({
17
- ...options,
18
- messageQueue: options.messageQueue ??
19
- await askMessageQueue(options.packageManager),
20
- });
16
+ <
17
+ T extends {
18
+ messageQueue?: MessageQueue;
19
+ packageManager: PackageManager;
20
+ testMode: boolean;
21
+ },
22
+ >(options: T) => Promise<MqDefined<T>> //
23
+ = (options) =>
24
+ pipe(
25
+ options,
26
+ when(isMessageQueueEmpty, askMessageQueue) as <
27
+ T extends { messageQueue?: MessageQueue; packageManager: PackageManager },
28
+ >(options: T) => MqDefined<T>,
29
+ unless(
30
+ isMqSupportsPm,
31
+ (opt: MqDefined<typeof options>) =>
32
+ pipe(
33
+ opt,
34
+ when(isTest, throwError(unmatchedWhileTesting)),
35
+ tap(noticeUnmatched),
36
+ fillMessageQueue,
37
+ ),
38
+ ),
39
+ ) as Promise<MqDefined<typeof options>>;
21
40
 
22
41
  export default fillMessageQueue;
23
42
 
24
- const askMessageQueue = (pm: PackageManager) =>
25
- select<MessageQueue>({
43
+ type MqDefined<T> = Omit<T, "messageQueue"> & { messageQueue: MessageQueue };
44
+
45
+ const isMessageQueueEmpty = <T extends { messageQueue?: MessageQueue }>(
46
+ options: T,
47
+ ): options is T & { messageQueue: undefined } => !options.messageQueue;
48
+
49
+ const askMessageQueue = async <
50
+ T extends { packageManager: PackageManager },
51
+ >(
52
+ data: T,
53
+ ): Promise<Omit<T, "messageQueue"> & { messageQueue: MessageQueue }> => ({
54
+ ...data,
55
+ messageQueue: await select<MessageQueue>({
26
56
  message: "Choose the message queue to use",
27
- choices: MESSAGE_QUEUE.map(choiceMessageQueue(pm)),
28
- });
29
- const choiceMessageQueue = (pm: PackageManager) => (value: MessageQueue) => ({
30
- name: isMqSupportsPm(value, pm)
31
- ? value
32
- : `${value} (not supported with ${pm})`,
33
- value,
34
- disabled: !isMqSupportsPm(value, pm),
57
+ choices: MESSAGE_QUEUE.map(choiceMessageQueue(data.packageManager)),
58
+ }),
35
59
  });
36
- const isMqSupportsPm = (mq: MessageQueue, pm: PackageManager) =>
37
- messageQueues[mq].packageManagers.includes(pm);
60
+
61
+ const unmatchedWhileTesting = <
62
+ T extends { messageQueue: MessageQueue; packageManager: PackageManager },
63
+ >({ messageQueue: mq, packageManager: pm }: T) =>
64
+ new Error(
65
+ `Message queue '${mq}' is not compatible with package manager '${pm}'`,
66
+ );
67
+
68
+ const noticeUnmatched = <
69
+ T extends { messageQueue: MessageQueue; packageManager: PackageManager },
70
+ >({ messageQueue: mq, packageManager: pm }: T) => {
71
+ printErrorMessage`Error: Message queue '${mq}' is not compatible with package manager '${pm}'`;
72
+ };
73
+
74
+ const choiceMessageQueue =
75
+ (packageManager: PackageManager) => (messageQueue: MessageQueue) => ({
76
+ name: isMqSupportsPm({ messageQueue, packageManager })
77
+ ? messageQueue
78
+ : `${messageQueue} (not supported with ${packageManager})`,
79
+ value: messageQueue,
80
+ disabled: !isMqSupportsPm({ messageQueue, packageManager }),
81
+ });
82
+
83
+ const isMqSupportsPm = <
84
+ T extends { messageQueue: MessageQueue; packageManager: PackageManager },
85
+ >({ messageQueue, packageManager }: T) =>
86
+ messageQueues[messageQueue].packageManagers.includes(packageManager);
@@ -5,19 +5,20 @@ import {
5
5
  constant,
6
6
  type InferValue,
7
7
  message,
8
+ multiple,
8
9
  object,
9
10
  option,
10
11
  optional,
11
12
  optionNames,
12
13
  } from "@optique/core";
13
14
  import { path } from "@optique/run";
15
+ import { debugOption } from "../globals.ts";
14
16
  import {
15
17
  KV_STORE,
16
18
  MESSAGE_QUEUE,
17
19
  PACKAGE_MANAGER,
18
20
  WEB_FRAMEWORK,
19
21
  } from "./const.ts";
20
- import { debugOption } from "../globals.ts";
21
22
 
22
23
  const webFramework = optional(option(
23
24
  "-w",
@@ -53,6 +54,12 @@ const messageQueue = optional(option(
53
54
  description: message`The message queue to use for background tasks.`,
54
55
  },
55
56
  ));
57
+ const testMode = option(
58
+ "--test-mode",
59
+ {
60
+ description: message`The test mode to use for testing purposes.`,
61
+ },
62
+ );
56
63
 
57
64
  export const initCommand = command(
58
65
  "init",
@@ -70,6 +77,7 @@ export const initCommand = command(
70
77
  description: message`Perform a trial run with no changes made.`,
71
78
  }),
72
79
  debugOption,
80
+ testMode,
73
81
  }),
74
82
  {
75
83
  brief: message`Initialize a new Fedify project directory.`,
@@ -86,3 +94,33 @@ Unless you specify all options (${optionNames(["-w", "--web-framework"])}, ${
86
94
  );
87
95
 
88
96
  export type InitCommand = InferValue<typeof initCommand>;
97
+
98
+ export const testInitCommand = command(
99
+ "test-init",
100
+ object("Initialization options", {
101
+ command: constant("test-init"),
102
+ webFramework: multiple(webFramework),
103
+ packageManager: multiple(packageManager),
104
+ kvStore: multiple(kvStore),
105
+ messageQueue: multiple(messageQueue),
106
+ hydRun: option("-h", "--hyd-run", {
107
+ description: message`Test with files creations and installations.`,
108
+ }),
109
+ dryRun: option("-d", "--dry-run", {
110
+ description: message`Log outputs without creating files.`,
111
+ }),
112
+ debugOption,
113
+ }),
114
+ {
115
+ brief: message`Test an initializing command .`,
116
+ description: message`Test an initializing command on temporary directories.
117
+
118
+ Unless you specify all options (${optionNames(["-w", "--web-framework"])}, ${
119
+ optionNames(["-p", "--package-manager"])
120
+ }, ${optionNames(["-k", "--kv-store"])}, and ${
121
+ optionNames(["-m", "--message-queue"])
122
+ }), it will test all combinations of the options.`,
123
+ },
124
+ );
125
+
126
+ export type TestInitCommand = InferValue<typeof testInitCommand>;
package/src/init/lib.ts CHANGED
@@ -10,13 +10,15 @@ import {
10
10
  when,
11
11
  } from "@fxts/core";
12
12
  import { getLogger } from "@logtape/logtape";
13
- import { dirname, join as joinPath } from "node:path";
13
+ import type { Message } from "@optique/core";
14
+ import { commandLine, message } from "@optique/core/message";
14
15
  import { toMerged } from "es-toolkit";
15
16
  import { readFileSync } from "node:fs";
16
17
  import { mkdir, readdir, writeFile } from "node:fs/promises";
18
+ import { dirname, join as joinPath } from "node:path";
17
19
  import process from "node:process";
18
20
  import metadata from "../../deno.json" with { type: "json" };
19
- import { colors, isNotFoundError, runSubCommand } from "../utils.ts";
21
+ import { isNotFoundError, runSubCommand } from "../utils.ts";
20
22
  import kv from "./json/kv.json" with { type: "json" };
21
23
  import mq from "./json/mq.json" with { type: "json" };
22
24
  import pm from "./json/pm.json" with { type: "json" };
@@ -108,31 +110,21 @@ export const readTemplate: (templatePath: string) => string = (
108
110
 
109
111
  export const getInstruction: (
110
112
  packageManager: PackageManager,
111
- ) => string = (pm) => `
113
+ port: number,
114
+ ) => Message = (pm, port) =>
115
+ message`
112
116
  To start the server, run the following command:
113
117
 
114
- ${getDevCommand(pm)}
118
+ ${commandLine(getDevCommand(pm))}
115
119
 
116
120
  Then, try look up an actor from your server:
117
121
 
118
- ${
119
- colors.bold(colors.green(
120
- "fedify lookup http://localhost:8000/users/john",
121
- ))
122
- }
122
+ ${commandLine(`fedify lookup http://localhost:${port}/users/john`)}
123
123
 
124
124
  `;
125
125
 
126
- const getDevCommand = (pm: PackageManager) =>
127
- colors.bold(
128
- colors.green(
129
- pm === "deno"
130
- ? "deno task dev"
131
- : pm === "bun"
132
- ? "bun dev"
133
- : `${pm} run dev`,
134
- ),
135
- );
126
+ export const getDevCommand = (pm: PackageManager) =>
127
+ pm === "deno" ? "deno task dev" : pm === "bun" ? "bun dev" : `${pm} run dev`;
136
128
 
137
129
  async function isCommandAvailable(
138
130
  { checkCommand, outputPattern }: {
@@ -181,20 +173,23 @@ export const isDirectoryEmpty = async (
181
173
  }
182
174
  };
183
175
 
176
+ /**
177
+ * Converts a package manager to its corresponding runtime.
178
+ * @param pm - The package manager (deno, bun, npm, yarn, pnpm)
179
+ * @returns The runtime name (deno, bun, or node)
180
+ */
181
+ export const packageManagerToRuntime = (
182
+ pm: PackageManager,
183
+ ): "deno" | "bun" | "node" =>
184
+ pm === "deno" ? "deno" : pm === "bun" ? "bun" : "node";
185
+
184
186
  export const getNextInitCommand = (
185
187
  pm: PackageManager,
186
- ): string[] => [
187
- ...createNextAppCommand(pm),
188
- ".",
189
- "--ts",
190
- "--app",
191
- "--biome",
192
- "--skip-install",
193
- ];
188
+ ): string[] => [...createNextAppCommand(pm), ".", "--yes"];
194
189
 
195
190
  const createNextAppCommand = (pm: PackageManager): string[] =>
196
191
  pm === "deno"
197
- ? ["deno", "run", "-A", "npm:create-next-app@latest"]
192
+ ? ["deno", "-Ar", "npm:create-next-app@latest"]
198
193
  : pm === "bun"
199
194
  ? ["bun", "create", "next-app"]
200
195
  : pm === "npm"
@@ -208,6 +203,10 @@ export const getNitroInitCommand = (
208
203
  pm === "deno" ? "npm:giget@latest" : "giget@latest",
209
204
  "nitro",
210
205
  ".",
206
+ "&&",
207
+ "rm",
208
+ "nitro.config.ts", // Remove default nitro config file
209
+ // This file will be created from template
211
210
  ];
212
211
 
213
212
  const createNitroAppCommand = (pm: PackageManager): string[] =>
@@ -218,3 +217,7 @@ const createNitroAppCommand = (pm: PackageManager): string[] =>
218
217
  : pm === "npm"
219
218
  ? ["npx"]
220
219
  : [pm, "dlx"];
220
+
221
+ export const isTest: <
222
+ T extends { testMode: boolean },
223
+ >({ testMode }: T) => boolean = ({ testMode }) => testMode;
package/src/init/mod.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { default as runInit } from "./action/mod.ts";
2
- export { initCommand } from "./command.ts";
2
+ export { initCommand, testInitCommand } from "./command.ts";
3
+ export { default as runTestInit } from "./test/action.ts";
@@ -0,0 +1 @@
1
+ HOST=127.0.0.1
@@ -1,5 +1,14 @@
1
- // https://nitro.unjs.io/config
1
+ import { defineNitroConfig } from "nitropack/config"
2
+
3
+ // https://nitro.build/config
2
4
  export default defineNitroConfig({
5
+ errorHandler: "~/error",
6
+ esbuild: {
7
+ options: {
8
+ target: "esnext",
9
+ },
10
+ },
11
+ compatibilityDate: "latest",
3
12
  srcDir: "server",
4
- errorHandler: "~/error"
5
- });
13
+ imports: false
14
+ });