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

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
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/cli",
3
- "version": "2.0.0-pr.474.1879+be9e2989",
3
+ "version": "2.0.0-pr.479.1902+5ac1b5d2",
4
4
  "license": "MIT",
5
5
  "exports": "./src/mod.ts",
6
6
  "imports": {
package/dist/deno.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  //#region deno.json
5
5
  var name = "@fedify/cli";
6
- var version = "2.0.0-pr.474.1879+be9e2989";
6
+ var version = "2.0.0-pr.479.1902+5ac1b5d2";
7
7
  var license = "MIT";
8
8
  var exports = "./src/mod.ts";
9
9
  var imports = {
@@ -5,8 +5,8 @@ import { Jimp } from "./nodeinfo.js";
5
5
  import path from "node:path";
6
6
  import fs from "node:fs/promises";
7
7
  import process from "node:process";
8
- import { encodeBase64 } from "byte-encodings/base64";
9
8
  import os from "node:os";
9
+ import { encodeBase64 } from "byte-encodings/base64";
10
10
 
11
11
  //#region src/imagerenderer.ts
12
12
  const KITTY_IDENTIFIERS = [
package/dist/inbox.js CHANGED
@@ -30,8 +30,7 @@ const inboxCommand = command("inbox", merge(object("Inbox options", {
30
30
  acceptFollow: optional(multiple(option("-a", "--accept-follow", string({ metavar: "URI" }), { description: message`Accept follow requests from the given actor. The argument can be either an actor URI or a handle, or a wildcard (${"*"}). Can be specified multiple times. If a wildcard is specified, all follow requests will be accepted.` }))),
31
31
  noTunnel: option("-T", "--no-tunnel", { description: message`Do not tunnel the ephemeral ActivityPub server to the public Internet.` }),
32
32
  actorName: withDefault(option("--actor-name", string({ metavar: "NAME" }), { description: message`Customize the actor display name.` }), "Fedify Ephemeral Inbox"),
33
- actorSummary: withDefault(option("--actor-summary", string({ metavar: "SUMMARY" }), { description: message`Customize the actor description.` }), "An ephemeral ActivityPub inbox for testing purposes."),
34
- authorizedFetch: option("-A", "--authorized-fetch", { description: message`Require HTTP Signatures for all incoming requests. Returns 401 for unsigned requests.` })
33
+ actorSummary: withDefault(option("--actor-summary", string({ metavar: "SUMMARY" }), { description: message`Customize the actor description.` }), "An ephemeral ActivityPub inbox for testing purposes.")
35
34
  }), debugOption), {
36
35
  brief: message`Run an ephemeral ActivityPub inbox server.`,
37
36
  description: message`Spins up an ephemeral server that serves the ActivityPub inbox with an one-time actor, through a short-lived public DNS with HTTPS. You can monitor the incoming activities in real-time.`
@@ -39,8 +38,7 @@ const inboxCommand = command("inbox", merge(object("Inbox options", {
39
38
  async function runInbox(command$1) {
40
39
  const fetch = createFetchHandler({
41
40
  actorName: command$1.actorName,
42
- actorSummary: command$1.actorSummary,
43
- requireHttpSignature: command$1.authorizedFetch
41
+ actorSummary: command$1.actorSummary
44
42
  });
45
43
  const sendDeleteToPeers = createSendDeleteToPeers({
46
44
  actorName: command$1.actorName,
@@ -302,7 +300,6 @@ function createFetchHandler(actorOptions) {
302
300
  actorName: actorOptions.actorName,
303
301
  actorSummary: actorOptions.actorSummary
304
302
  },
305
- requireHttpSignature: actorOptions.requireHttpSignature,
306
303
  onNotAcceptable: app.fetch.bind(app),
307
304
  onNotFound: app.fetch.bind(app),
308
305
  onUnauthorized: app.fetch.bind(app)
@@ -1,10 +1,15 @@
1
1
 
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
 
4
+ import { merge } from "../../utils.js";
4
5
  import biome_default from "../json/biome.js";
5
6
  import vscode_settings_for_deno_default from "../json/vscode-settings-for-deno.js";
6
7
  import vscode_settings_default from "../json/vscode-settings.js";
7
- import { join } from "node:path";
8
+ import { PACKAGES_PATH } from "./const.js";
9
+ import { getDependencies, getDevDependencies, joinDepsReg } from "./deps.js";
10
+ import { join, relative } from "node:path";
11
+ import { uniq } from "es-toolkit";
12
+ import { concat, filter, keys, map, pick, pipe, toArray } from "@fxts/core/index.js";
8
13
 
9
14
  //#region src/init/action/configs.ts
10
15
  /**
@@ -14,16 +19,18 @@ import { join } from "node:path";
14
19
  * @param param0 - Destructured initialization data containing KV, MQ, initializer, and directory
15
20
  * @returns Configuration object with path and Deno-specific settings
16
21
  */
17
- const loadDenoConfig = ({ kv, mq, initializer, dir }) => ({
18
- path: join(dir, "deno.json"),
19
- data: { compilerOptions: initializer.compilerOptions },
20
- unstable: [
21
- "temporal",
22
- ...kv.denoUnstable ?? [],
23
- ...mq.denoUnstable ?? []
24
- ],
25
- tasks: initializer.tasks
22
+ const loadDenoConfig = (data) => ({
23
+ path: "deno.json",
24
+ data: {
25
+ ...pick(["compilerOptions", "tasks"], data.initializer),
26
+ unstable: getUnstable(data),
27
+ nodeModulesDir: "auto",
28
+ imports: joinDepsReg("deno")(getDependencies(data)),
29
+ ...data.testMode ? { links: getLinks(data) } : {}
30
+ }
26
31
  });
32
+ const getUnstable = ({ kv: { denoUnstable: kv = [] }, mq: { denoUnstable: mq = [] } }) => pipe(["temporal"], concat(kv), concat(mq), toArray, uniq);
33
+ 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) => relative(dir, absolutePath)), toArray);
27
34
  /**
28
35
  * Loads TypeScript configuration object for Node.js/Bun projects.
29
36
  * Uses compiler options from the framework initializer.
@@ -42,11 +49,13 @@ const loadTsConfig = ({ initializer, dir }) => ({
42
49
  * @param param0 - Destructured initialization data containing initializer and directory
43
50
  * @returns Configuration object with path and package.json settings
44
51
  */
45
- const loadPackageJson = ({ initializer, dir }) => ({
46
- path: join(dir, "package.json"),
52
+ const loadPackageJson = (data) => ({
53
+ path: "package.json",
47
54
  data: {
48
55
  type: "module",
49
- scripts: initializer.tasks
56
+ scripts: data.initializer.tasks,
57
+ dependencies: getDependencies(data),
58
+ devDependencies: getDevDependencies(data)
50
59
  }
51
60
  });
52
61
  /**
@@ -0,0 +1,10 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { join } from "node:path";
5
+
6
+ //#region src/init/action/const.ts
7
+ const PACKAGES_PATH = join(import.meta.dirname, "..", "..", "..", "..");
8
+
9
+ //#endregion
10
+ export { PACKAGES_PATH };
@@ -1,52 +1,50 @@
1
1
 
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
 
4
- import { merge } from "../../utils.js";
4
+ import { merge, replace } from "../../utils.js";
5
5
  import { PACKAGE_VERSION } from "../lib.js";
6
- import { entries, map, pipe, toArray } from "@fxts/core";
6
+ import { PACKAGES_PATH } from "./const.js";
7
+ import { isDeno } from "./utils.js";
8
+ import { join } from "node:path";
9
+ import { always, entries, filter, fromEntries, map, pipe, when } from "@fxts/core";
7
10
 
8
11
  //#region src/init/action/deps.ts
9
12
  /**
10
13
  * Gathers all dependencies required for the project based on the initializer,
11
14
  * key-value store, and message queue configurations.
12
15
  *
13
- * @param data - Web Framework initializer, key-value store and message queue descriptions
16
+ * @param data - Web Framework initializer, key-value store and
17
+ * message queue descriptions
14
18
  * @returns A record of dependencies with their versions
15
19
  */
16
- const getDependencies = ({ initializer, kv, mq }) => pipe({
20
+ const getDependencies = ({ initializer, kv, mq, testMode, packageManager }) => pipe({
17
21
  "@fedify/fedify": PACKAGE_VERSION,
18
22
  "@logtape/logtape": "^1.1.0"
19
- }, merge(initializer.dependencies), merge(kv.dependencies), merge(mq.dependencies));
20
- /** Gathers all devDependencies required for the project based on the initializer,
21
- * key-value store, and message queue configurations, including Biome for linting/formatting.
23
+ }, merge(initializer.dependencies), merge(kv.dependencies), merge(mq.dependencies), when(always(testMode), isDeno({ packageManager }) ? removeFedifyDeps : addLocalFedifyDeps), normalizePackageNames(packageManager));
24
+ const removeFedifyDeps = (deps) => pipe(deps, entries, filter(([name]) => !name.includes("@fedify")), fromEntries);
25
+ const addLocalFedifyDeps = (deps) => pipe(deps, entries, map(when(([name]) => name.includes("@fedify/"), ([name, _version]) => [name, convertFedifyToLocal(name)])), fromEntries);
26
+ const convertFedifyToLocal = (name) => pipe(name, replace("@fedify/", ""), (pkg) => join(PACKAGES_PATH, pkg));
27
+ /** Gathers all devDependencies required for the project based on the
28
+ * initializer, key-value store, and message queue configurations,
29
+ * including Biome for linting/formatting.
22
30
  *
23
- * @param data - Web Framework initializer, key-value store and message queue descriptions
31
+ * @param data - Web Framework initializer, key-value store
32
+ * and message queue descriptions
24
33
  * @returns A record of devDependencies with their versions
25
34
  */
26
- const getDevDependencies = ({ initializer, kv, mq }) => pipe({ "@biomejs/biome": "^2.2.4" }, merge(initializer.devDependencies), merge(kv.devDependencies), merge(mq.devDependencies));
35
+ const getDevDependencies = ({ initializer, kv, mq, packageManager }) => pipe({ "@biomejs/biome": "^2.2.4" }, merge(initializer.devDependencies), merge(kv.devDependencies), merge(mq.devDependencies), normalizePackageNames(packageManager));
27
36
  /**
28
- * Generates the command-line arguments needed to add dependencies or devDependencies
29
- * using the specified package manager.
30
- * If it is devDependencies, the '-D' flag is included.
31
- *
32
- * @param param0 - Object containing the package manager and a boolean indicating if dev dependencies are to be added
33
- * @yields The command-line arguments as strings
34
- */
35
- function* getAddDepsArgs({ packageManager, dev = false }) {
36
- yield packageManager;
37
- yield "add";
38
- if (dev) yield "-D";
39
- }
40
- /**
41
- * Joins package names with their versions for installation commands.
42
- * For Deno, it prefixes packages with 'jsr:' unless they already start with 'npm:'.
37
+ * Joins package names with their versions for installation dependencies.
38
+ * For Deno, it prefixes packages with 'jsr:'
39
+ * unless they already start with 'npm:' or 'jsr:'.
43
40
  *
44
41
  * @param data - Package manager and dependencies to be joined with versions
45
- * @returns `${registry}:${package}@${version}`[] for deno or `${package}@${version}`[] for others
42
+ * @returns \{ name: `${registry}:${package}@${version}` } for deno
46
43
  */
47
- const joinDepsVer = ({ packageManager: pm, dependencies }) => pipe(dependencies, entries, map(([name, version]) => `${getPackageName(pm, name)}@${getPackageVersion(pm, version)}`), toArray);
48
- const getPackageName = (pm, name) => pm !== "deno" ? name : name.startsWith("npm:") ? name.substring(4) : !name.startsWith("npm:") ? `jsr:${name}` : name;
44
+ const joinDepsReg = (pm) => (dependencies) => pipe(dependencies, entries, map(([name, version]) => [name.substring(4), `${name}@${getPackageVersion(pm, version)}`]), fromEntries);
49
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}`;
50
48
 
51
49
  //#endregion
52
- export { getAddDepsArgs, getDependencies, getDevDependencies, joinDepsVer };
50
+ export { getDependencies, getDevDependencies, joinDepsReg };
@@ -1,22 +1,20 @@
1
1
 
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
 
4
- import { notEmpty, runSubCommand, set } from "../../utils.js";
5
- import { noticeErrorWhileAddDeps } from "./notice.js";
6
- import { getAddDepsArgs, getDependencies, getDevDependencies, joinDepsVer } from "./deps.js";
7
- import { isDeno } from "./utils.js";
8
- import { always, concat, pipe, tap, toArray, unless, when } from "@fxts/core";
4
+ import { CommandError, runSubCommand } from "../../utils.js";
5
+ import { apply, pipe } from "@fxts/core";
9
6
 
10
7
  //#region src/init/action/install.ts
11
- const installDependencies = (data) => pipe(data, tap(installDeps), unless(isDeno, tap(installDevDeps)));
8
+ const installDependencies = (data) => pipe(data, ({ packageManager, dir }) => [[packageManager, "install"], { cwd: dir }], apply(runSubCommand)).catch((e) => {
9
+ if (e instanceof CommandError) {
10
+ console.error(`Failed to install dependencies using ${data.packageManager}.`);
11
+ console.error("Command:", e.commandLine);
12
+ if (e.stderr) console.error("Error:", e.stderr);
13
+ throw e;
14
+ }
15
+ throw e;
16
+ });
12
17
  var install_default = installDependencies;
13
- const installDeps = (data) => pipe(data, set("dependencies", getDependencies), getAddDepsCommand, when(notEmpty, runAddDeps(data)));
14
- const installDevDeps = (data) => pipe(data, set("dependencies", getDevDependencies), set("dev", always(true)), getAddDepsCommand, when(notEmpty, runAddDeps(data)));
15
- const getAddDepsCommand = (data) => pipe(data, joinDepsVer, when(notEmpty, concat(getAddDepsArgs(data))), toArray);
16
- const runAddDeps = ({ dir }) => (command) => runSubCommand(command, {
17
- cwd: dir,
18
- stdio: "inherit"
19
- }).catch(noticeErrorWhileAddDeps(command));
20
18
 
21
19
  //#endregion
22
20
  export { install_default as default };
@@ -5,8 +5,8 @@ import ask_default from "../ask/mod.js";
5
5
  import { makeDirIfHyd } from "./dir.js";
6
6
  import { drawDinosaur, noticeHowToRun, noticeOptions, noticePrecommand } from "./notice.js";
7
7
  import env_default from "./env.js";
8
- import { hasCommand, isDry } from "./utils.js";
9
8
  import install_default from "./install.js";
9
+ import { hasCommand, isDry } from "./utils.js";
10
10
  import { patchFiles, recommendPatchFiles } from "./patch.js";
11
11
  import precommand_default from "./precommand.js";
12
12
  import recommend_default from "./recommend.js";
@@ -1,14 +1,11 @@
1
1
 
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
 
4
- import { colors } from "../../utils.js";
5
- import { message } from "@optique/core";
6
- import { print, printError } from "@optique/run";
4
+ import { colors, printMessage } from "../../utils.js";
5
+ import { text } from "@optique/core";
7
6
  import { flow } from "es-toolkit";
8
7
 
9
8
  //#region src/init/action/notice.ts
10
- const printMessage = flow(message, print);
11
- const printErrorMessage = flow(message, printError);
12
9
  function drawDinosaur() {
13
10
  const d = flow(colors.bgBlue, colors.black);
14
11
  const f = colors.blue;
@@ -37,26 +34,22 @@ const noticeFilesToInsert = () => printMessage`Would create/update JSON files:\n
37
34
  const noticeDepsIfExist = () => printMessage`📦 Would install dependencies:`;
38
35
  const noticeDevDepsIfExist = () => printMessage`📦 Would install dev dependencies:`;
39
36
  const noticeDeps = ([name, version]) => printMessage`${name}@${version}`;
40
- function displayFile(path$1, content, emoji = "📄") {
41
- printMessage`${emoji} ${path$1}`;
37
+ function displayFile(path, content, emoji = "📄") {
38
+ printMessage`${emoji} ${path}`;
42
39
  printMessage`${"─".repeat(60)}`;
43
40
  printMessage`${content}`;
44
41
  printMessage`${"─".repeat(60)}\n`;
45
42
  }
46
43
  const noticeConfigEnv = () => printMessage`Note that you probably want to edit the ${".env"} file.
47
44
  It currently contains the following values:\n`;
48
- const noticeEnvKeyValue = ([key, value]) => printMessage` ${key}=${value}`;
49
- function noticeHowToRun({ initializer: { instruction, federationFile } }) {
50
- printMessage`${instruction}`;
51
- printMessage`Start by editing the ${federationFile} file to define your federation!
45
+ const noticeEnvKeyValue = ([key, value]) => {
46
+ printMessage`${text(` ${key}='${value}'`)}`;
47
+ };
48
+ const noticeHowToRun = ({ initializer: { instruction, federationFile } }) => printMessage`
49
+ ${instruction}
50
+
51
+ Start by editing the ${text(federationFile)} file to define your federation!
52
52
  `;
53
- }
54
- function noticeErrorWhileAddDeps(command$1) {
55
- return (error) => {
56
- printErrorMessage`The command ${command$1.join(" ")} failed with the error: ${String(error)}`;
57
- throw new Error("Failed to add dependencies.");
58
- };
59
- }
60
53
 
61
54
  //#endregion
62
- export { displayFile, drawDinosaur, noticeConfigEnv, noticeDeps, noticeDepsIfExist, noticeDevDepsIfExist, noticeEnvKeyValue, noticeErrorWhileAddDeps, noticeFilesToCreate, noticeFilesToInsert, noticeHowToRun, noticeOptions, noticePrecommand };
55
+ export { displayFile, drawDinosaur, noticeConfigEnv, noticeDeps, noticeDepsIfExist, noticeDevDepsIfExist, noticeEnvKeyValue, noticeFilesToCreate, noticeFilesToInsert, noticeHowToRun, noticeOptions, noticePrecommand };
@@ -1,14 +1,14 @@
1
1
 
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
 
4
- import { formatJson, merge, set } from "../../utils.js";
4
+ import { formatJson, merge, replaceAll, set } from "../../utils.js";
5
5
  import { createFile, throwUnlessNotExists } from "../lib.js";
6
6
  import { displayFile, noticeFilesToCreate, noticeFilesToInsert } from "./notice.js";
7
7
  import { joinDir, stringifyEnvs } from "./utils.js";
8
8
  import { devToolConfigs, loadDenoConfig, loadPackageJson, loadTsConfig } from "./configs.js";
9
9
  import { getImports, loadFederation, loadLogging } from "./templates.js";
10
10
  import { readFile } from "node:fs/promises";
11
- import { apply, entries, forEach, pipe, pipeLazy, tap } from "@fxts/core";
11
+ import { always, apply, entries, map, pipe, pipeLazy, tap } from "@fxts/core";
12
12
  import { toMerged } from "es-toolkit";
13
13
 
14
14
  //#region src/init/action/patch.ts
@@ -17,15 +17,17 @@ import { toMerged } from "es-toolkit";
17
17
  * Handles both dry-run mode (recommending files) and actual file creation.
18
18
  * Orchestrates the entire file generation and writing process.
19
19
  *
20
- * @param data - The initialization command data containing project configuration
20
+ * @param data - The initialization command data containing project
21
+ * configuration
21
22
  * @returns A processed data object with files and JSONs ready for creation
22
23
  */
23
24
  const patchFiles = (data) => pipe(data, set("files", getFiles), set("jsons", getJsons), createFiles);
24
25
  const recommendPatchFiles = (data) => pipe(data, set("files", getFiles), set("jsons", getJsons), recommendFiles);
25
26
  /**
26
27
  * 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.
28
+ * Creates federation configuration, logging setup, environment variables, and
29
+ * framework-specific files by processing templates and combining them with
30
+ * project-specific data.
29
31
  *
30
32
  * @param data - The initialization command data
31
33
  * @returns A record of file paths to their string content
@@ -41,8 +43,9 @@ const getFiles = (data) => ({
41
43
  });
42
44
  /**
43
45
  * 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
+ * Creates different sets of configuration files for Deno vs other environments,
47
+ * including compiler configs, package manifests, and development
48
+ * tool configurations.
46
49
  *
47
50
  * @param data - The initialization command data
48
51
  * @returns A record of file paths to their JSON object content
@@ -52,23 +55,24 @@ const getJsons = (data) => data.packageManager === "deno" ? {
52
55
  [devToolConfigs["vscSetDeno"].path]: devToolConfigs["vscSetDeno"].data,
53
56
  [devToolConfigs["vscExtDeno"].path]: devToolConfigs["vscExtDeno"].data
54
57
  } : {
55
- "tsconfig.json": loadTsConfig(data).data,
58
+ ...data.initializer.compilerOptions ? { "tsconfig.json": loadTsConfig(data).data } : {},
56
59
  "package.json": loadPackageJson(data).data,
57
60
  [devToolConfigs["biome"].path]: devToolConfigs["biome"].data,
58
61
  [devToolConfigs["vscSet"].path]: devToolConfigs["vscSet"].data,
59
62
  [devToolConfigs["vscExt"].path]: devToolConfigs["vscExt"].data
60
63
  };
61
64
  /**
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
+ * Handles dry-run mode by recommending files to be created without actually
66
+ * creating them.
67
+ * Displays what files would be created and shows their content for user review,
68
+ * so users can preview the initialization process before committing to it.
65
69
  *
66
70
  * @param data - The initialization command data with files and JSONs prepared
67
71
  * @returns The processed data with recommendations displayed
68
72
  */
69
73
  const recommendFiles = (data) => pipe(data, tap(noticeFilesToCreate), tap(processAllFiles(displayFile)), tap(noticeFilesToInsert), set("files", ({ jsons }) => jsons), tap(processAllFiles(displayFile)));
70
74
  /**
71
- * Actually creates files on the filesystem during normal (non-dry-run) execution.
75
+ * Actually creates files on the filesystem during normal execution.
72
76
  * Merges text files and JSON files together and writes them to disk.
73
77
  * This performs the actual file system operations to initialize the project.
74
78
  *
@@ -77,18 +81,18 @@ const recommendFiles = (data) => pipe(data, tap(noticeFilesToCreate), tap(proces
77
81
  */
78
82
  const createFiles = (data) => pipe(data, set("files", ({ jsons, files }) => toMerged(files, jsons)), tap(processAllFiles(createFile)));
79
83
  /**
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
84
+ * Processes all files with a given processing function.
85
+ * Takes a processor (either display or create) and applies it to all files
82
86
  * in the target directory, handling path resolution and content patching.
83
87
  *
84
88
  * @param process - Function to process each file (either display or create)
85
89
  * @returns A function that processes all files in the given directory with the provided processor
86
90
  */
87
- const processAllFiles = (process) => ({ dir, files }) => pipe(files, entries, forEach(pipeLazy(joinDir(dir), apply(patchContent), apply(process))));
91
+ const processAllFiles = (process) => ({ dir, files }) => pipe(files, entries, map(pipeLazy(joinDir(dir), apply(patchContent), apply(process))), Array.fromAsync);
88
92
  /**
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).
93
+ * Patches file content by either merging JSON or appending text content.
94
+ * Handles existing files by reading their current content and intelligently
95
+ * combining it with new content based on the content type (JSON vs text).
92
96
  *
93
97
  * @param path - The file path to patch
94
98
  * @param content - The new content (either string or object)
@@ -103,12 +107,21 @@ async function patchContent(path, content) {
103
107
  * Merges new JSON data with existing JSON content and formats the result.
104
108
  * Parses existing JSON content (if any) and deep merges it with new data,
105
109
  * then formats the result for consistent output.
110
+ * Supports JSONC (JSON with Comments) by removing comments before parsing.
106
111
  *
107
112
  * @param prev - The previous JSON content as string
108
113
  * @param data - The new data object to merge
109
114
  * @returns Formatted JSON string with merged content
110
115
  */
111
- const mergeJson = (prev, data) => pipe(prev ? JSON.parse(prev) : {}, merge(data), formatJson);
116
+ const mergeJson = (prev, data) => pipe(prev ? JSON.parse(removeJsonComments(prev)) : {}, merge(data), formatJson);
117
+ /**
118
+ * Removes single-line (//) and multi-line (/* *\/) comments from JSON string.
119
+ * This allows parsing JSONC (JSON with Comments) files.
120
+ *
121
+ * @param jsonString - The JSON string potentially containing comments
122
+ * @returns JSON string with comments removed
123
+ */
124
+ const removeJsonComments = (jsonString) => pipe(jsonString, replaceAll(/\/\/.*$/gm, ""), replaceAll(/\/\*[\s\S]*?\*\//g, ""));
112
125
  /**
113
126
  * Appends new text content to existing text content line by line.
114
127
  * Concatenates new content lines with existing content lines,
@@ -128,14 +141,7 @@ const appendText = (prev, data) => prev ? `${prev}\n${data}` : data;
128
141
  * @returns The file content as string, or empty string if file doesn't exist
129
142
  * @throws Error if file access fails for reasons other than file not existing
130
143
  */
131
- async function readFileIfExists(path) {
132
- try {
133
- return await readFile(path, "utf8");
134
- } catch (e) {
135
- throwUnlessNotExists(e);
136
- return "";
137
- }
138
- }
144
+ const readFileIfExists = (path) => readFile(path, "utf8").catch(pipeLazy(tap(throwUnlessNotExists), always("")));
139
145
 
140
146
  //#endregion
141
147
  export { patchFiles, recommendPatchFiles };
@@ -1,7 +1,7 @@
1
1
 
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
 
4
- import { exit, runSubCommand } from "../../utils.js";
4
+ import { CommandError, exit, runSubCommand } from "../../utils.js";
5
5
 
6
6
  //#region src/init/action/precommand.ts
7
7
  /**
@@ -14,7 +14,12 @@ const runPrecommand = ({ initializer: { command }, dir }) => runSubCommand(comma
14
14
  cwd: dir,
15
15
  stdio: "inherit"
16
16
  }).catch((e) => {
17
- console.error("Failed to run the precommand:", e);
17
+ if (e instanceof CommandError) {
18
+ console.error("Failed to run the precommand.");
19
+ console.error("Command:", e.commandLine);
20
+ if (e.stderr) console.error("Error:", e.stderr);
21
+ if (e.stdout) console.error("Output:", e.stdout);
22
+ } else console.error("Failed to run the precommand:", e);
18
23
  exit(1);
19
24
  });
20
25
  var precommand_default = runPrecommand;
@@ -3,8 +3,8 @@
3
3
 
4
4
  import { notEmpty } from "../../utils.js";
5
5
  import { noticeDeps, noticeDepsIfExist, noticeDevDepsIfExist } from "./notice.js";
6
- import { getDependencies, getDevDependencies } from "./deps.js";
7
6
  import { isDeno } from "./utils.js";
7
+ import { getDependencies, getDevDependencies } from "./deps.js";
8
8
  import { map, peek, pipeLazy, tap, unless, when } from "@fxts/core";
9
9
 
10
10
  //#region src/init/action/recommend.ts
@@ -22,7 +22,7 @@ import { existsSync } from "node:fs";
22
22
  const setData = (data) => pipe(data, setProjectName, setInitializer, setKv, setMq, setEnv);
23
23
  var set_default = setData;
24
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));
25
+ const setInitializer = set("initializer", (data) => webframeworks_default[data.webFramework].init(data));
26
26
  const setKv = set("kv", ({ kvStore }) => kvStores[kvStore]);
27
27
  const setMq = set("mq", ({ messageQueue }) => messageQueues[messageQueue]);
28
28
  const setEnv = set("env", ({ kv, mq }) => merge(kv.env)(mq.env));
@@ -43,6 +43,7 @@ const getImports = ({ kv, mq }) => pipe(toMerged(kv.imports, mq.imports), entrie
43
43
  * @returns A comma-separated string of named imports with aliases where needed
44
44
  */
45
45
  const getAlias = (imports) => pipe(imports, entries, map(([name, alias]) => name === alias ? name : `${name} as ${alias}`), join(", "));
46
+ const ENV_REG_EXP = /process\.env\.(\w+)/g;
46
47
  /**
47
48
  * Converts Node.js environment variable access to Deno-compatible syntax when needed.
48
49
  * Transforms `process.env.VAR_NAME` to `Deno.env.get("VAR_NAME")` for Deno projects.
@@ -51,7 +52,7 @@ const getAlias = (imports) => pipe(imports, entries, map(([name, alias]) => name
51
52
  * @param pm - The package manager (runtime) being used
52
53
  * @returns The converted object string with appropriate environment variable access syntax
53
54
  */
54
- const convertEnv = (obj, pm) => pm === "deno" && /process\.env\.(\w+)/.test(obj) ? obj.replaceAll(/process\.env\.(\w+)/, (_, g1) => `Deno.env.get("${g1}")`) : obj;
55
+ const convertEnv = (obj, pm) => pm === "deno" && ENV_REG_EXP.test(obj) ? obj.replaceAll(ENV_REG_EXP, (_, g1) => `Deno.env.get("${g1}")`) : obj;
55
56
 
56
57
  //#endregion
57
58
  export { getImports, loadFederation, loadLogging };
@@ -1,9 +1,11 @@
1
1
 
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
 
4
+ import { printErrorMessage } from "../../utils.js";
4
5
  import { KV_STORE } from "../const.js";
5
- import { kvStores } from "../lib.js";
6
+ import { isTest, kvStores } from "../lib.js";
6
7
  import { select } from "@inquirer/prompts";
8
+ import { pipe, tap, throwError, unless, when } from "@fxts/core/index.js";
7
9
 
8
10
  //#region src/init/ask/kv.ts
9
11
  /**
@@ -13,21 +15,30 @@ import { select } from "@inquirer/prompts";
13
15
  * @param options - Initialization options possibly containing a kvStore and packageManager
14
16
  * @returns A promise resolving to options with a guaranteed kvStore
15
17
  */
16
- const fillKvStore = async (options) => ({
17
- ...options,
18
- kvStore: options.kvStore ?? await askKvStore(options.packageManager)
19
- });
18
+ const fillKvStore = (options) => pipe(options, when(isKvStoreEmpty, askKvStore), unless(isKvSupportsPm, (opt) => pipe(opt, when(isTest, throwError(unmatchedWhileTesting)), tap(noticeUnmatched), fillKvStore)));
20
19
  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))
20
+ const isKvStoreEmpty = (options) => !options.kvStore;
21
+ const askKvStore = async (data) => ({
22
+ ...data,
23
+ kvStore: await select({
24
+ message: "Choose the key-value store to use",
25
+ choices: KV_STORE.map(choiceKvStore(data.packageManager))
26
+ })
24
27
  });
25
- const choiceKvStore = (pm) => (value) => ({
26
- name: isKvSupportsPm(value, pm) ? value : `${value} (not supported with ${pm})`,
27
- value,
28
- disabled: !isKvSupportsPm(value, pm)
28
+ const unmatchedWhileTesting = ({ kvStore: kv, packageManager: pm }) => /* @__PURE__ */ new Error(`Key-value store '${kv}' is not compatible with package manager '${pm}'`);
29
+ const noticeUnmatched = ({ kvStore: kv, packageManager: pm }) => printErrorMessage`Error: Key-value store '${kv}' is not compatible with package manager '${pm}'`;
30
+ const choiceKvStore = (pm) => (kv) => ({
31
+ name: isKvSupportsPm({
32
+ kvStore: kv,
33
+ packageManager: pm
34
+ }) ? kv : `${kv} (not supported with ${pm})`,
35
+ value: kv,
36
+ disabled: !isKvSupportsPm({
37
+ kvStore: kv,
38
+ packageManager: pm
39
+ })
29
40
  });
30
- const isKvSupportsPm = (kv, pm) => kvStores[kv].packageManagers.includes(pm);
41
+ const isKvSupportsPm = ({ kvStore, packageManager }) => kvStores[kvStore].packageManagers.includes(packageManager);
31
42
 
32
43
  //#endregion
33
44
  export { kv_default as default };
@@ -1,9 +1,11 @@
1
1
 
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
 
4
+ import { printErrorMessage } from "../../utils.js";
4
5
  import { MESSAGE_QUEUE } from "../const.js";
5
- import { messageQueues } from "../lib.js";
6
+ import { isTest, messageQueues } from "../lib.js";
6
7
  import { select } from "@inquirer/prompts";
8
+ import { pipe, tap, throwError, unless, when } from "@fxts/core/index.js";
7
9
 
8
10
  //#region src/init/ask/mq.ts
9
11
  /**
@@ -13,21 +15,32 @@ import { select } from "@inquirer/prompts";
13
15
  * @param options - Initialization options possibly containing a messageQueue and packageManager
14
16
  * @returns A promise resolving to options with a guaranteed messageQueue
15
17
  */
16
- const fillMessageQueue = async (options) => ({
17
- ...options,
18
- messageQueue: options.messageQueue ?? await askMessageQueue(options.packageManager)
19
- });
18
+ const fillMessageQueue = (options) => pipe(options, when(isMessageQueueEmpty, askMessageQueue), unless(isMqSupportsPm, (opt) => pipe(opt, when(isTest, throwError(unmatchedWhileTesting)), tap(noticeUnmatched), fillMessageQueue)));
20
19
  var mq_default = fillMessageQueue;
21
- const askMessageQueue = (pm) => select({
22
- message: "Choose the message queue to use",
23
- choices: MESSAGE_QUEUE.map(choiceMessageQueue(pm))
20
+ const isMessageQueueEmpty = (options) => !options.messageQueue;
21
+ const askMessageQueue = async (data) => ({
22
+ ...data,
23
+ messageQueue: await select({
24
+ message: "Choose the message queue to use",
25
+ choices: MESSAGE_QUEUE.map(choiceMessageQueue(data.packageManager))
26
+ })
24
27
  });
25
- const choiceMessageQueue = (pm) => (value) => ({
26
- name: isMqSupportsPm(value, pm) ? value : `${value} (not supported with ${pm})`,
27
- value,
28
- disabled: !isMqSupportsPm(value, pm)
28
+ const unmatchedWhileTesting = ({ messageQueue: mq, packageManager: pm }) => /* @__PURE__ */ new Error(`Message queue '${mq}' is not compatible with package manager '${pm}'`);
29
+ const noticeUnmatched = ({ messageQueue: mq, packageManager: pm }) => {
30
+ printErrorMessage`Error: Message queue '${mq}' is not compatible with package manager '${pm}'`;
31
+ };
32
+ const choiceMessageQueue = (packageManager) => (messageQueue) => ({
33
+ name: isMqSupportsPm({
34
+ messageQueue,
35
+ packageManager
36
+ }) ? messageQueue : `${messageQueue} (not supported with ${packageManager})`,
37
+ value: messageQueue,
38
+ disabled: !isMqSupportsPm({
39
+ messageQueue,
40
+ packageManager
41
+ })
29
42
  });
30
- const isMqSupportsPm = (mq, pm) => messageQueues[mq].packageManagers.includes(pm);
43
+ const isMqSupportsPm = ({ messageQueue, packageManager }) => messageQueues[messageQueue].packageManagers.includes(packageManager);
31
44
 
32
45
  //#endregion
33
46
  export { mq_default as default };