@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
package/dist/inbox.js ADDED
@@ -0,0 +1,315 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import deno_default from "./deno.js";
5
+ import { getDocumentLoader } from "./docloader.js";
6
+ import { recordingSink } from "./log.js";
7
+ import { configureLogging, debugOption } from "./globals.js";
8
+ import { ActivityEntryPage, ActivityListPage } from "./inbox/view.js";
9
+ import { tableStyle } from "./table.js";
10
+ import { spawnTemporaryServer } from "./tempserver.js";
11
+ import { colors } from "./utils.js";
12
+ import { command, constant, merge, message, multiple, object, option, optional, string, withDefault } from "@optique/core";
13
+ import { Accept, Activity, Application, Delete, Endpoints, Follow, Image, MemoryKvStore, PUBLIC_COLLECTION, createFederation, generateCryptoKeyPair, getActorHandle, isActor, lookupObject } from "@fedify/fedify";
14
+ import { getLogger } from "@logtape/logtape";
15
+ import Table from "cli-table3";
16
+ import { Hono } from "hono";
17
+ import process from "node:process";
18
+ import ora from "ora";
19
+ import { jsx } from "hono/jsx/jsx-runtime";
20
+
21
+ //#region src/inbox.tsx
22
+ const logger = getLogger([
23
+ "fedify",
24
+ "cli",
25
+ "inbox"
26
+ ]);
27
+ const inboxCommand = command("inbox", merge(object("Inbox options", {
28
+ command: constant("inbox"),
29
+ follow: optional(multiple(option("-f", "--follow", string({ metavar: "URI" }), { description: message`Follow the given actor. The argument can be either an actor URI or a handle. Can be specified multiple times.` }))),
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
+ noTunnel: option("-T", "--no-tunnel", { description: message`Do not tunnel the ephemeral ActivityPub server to the public Internet.` }),
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
+ }), debugOption), { 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.` });
35
+ async function runInbox(command$1) {
36
+ const fetch = createFetchHandler({
37
+ actorName: command$1.actorName,
38
+ actorSummary: command$1.actorSummary
39
+ });
40
+ const sendDeleteToPeers = createSendDeleteToPeers({
41
+ actorName: command$1.actorName,
42
+ actorSummary: command$1.actorSummary
43
+ });
44
+ if (command$1.debug) await configureLogging();
45
+ const spinner = ora({
46
+ text: "Spinning up an ephemeral ActivityPub server...",
47
+ discardStdin: false
48
+ }).start();
49
+ const server = await spawnTemporaryServer(fetch, { noTunnel: command$1.noTunnel });
50
+ spinner.succeed(`The ephemeral ActivityPub server is up and running: ${colors.green(server.url.href)}`);
51
+ process.on("SIGINT", () => {
52
+ spinner.stop();
53
+ const peersCnt = Object.keys(peers).length;
54
+ spinner.start(`Sending Delete(Application) activities to the ${peersCnt} ${peersCnt === 1 ? "peer" : "peers"}...`);
55
+ sendDeleteToPeers(server).then(() => {
56
+ spinner.text = "Stopping server...";
57
+ server.close().then(() => {
58
+ spinner.succeed("Server stopped.");
59
+ process.exit(0);
60
+ });
61
+ });
62
+ });
63
+ spinner.start();
64
+ const fedCtx = federation.createContext(server.url, {
65
+ activityIndex: -1,
66
+ actorName: command$1.actorName,
67
+ actorSummary: command$1.actorSummary
68
+ });
69
+ if (command$1.acceptFollow != null && command$1.acceptFollow.length > 0) acceptFollows.push(...command$1.acceptFollow ?? []);
70
+ if (command$1.follow != null && command$1.follow.length > 0) {
71
+ spinner.text = "Following actors...";
72
+ const documentLoader = await fedCtx.getDocumentLoader({ identifier: "i" });
73
+ for (const uri of command$1.follow) {
74
+ spinner.text = `Following ${colors.green(uri)}...`;
75
+ const actor = await lookupObject(uri, { documentLoader });
76
+ if (!isActor(actor)) {
77
+ spinner.fail(`Not an actor: ${colors.red(uri)}`);
78
+ spinner.start();
79
+ continue;
80
+ }
81
+ if (actor.id != null) peers[actor.id?.href] = actor;
82
+ await fedCtx.sendActivity({ identifier: "i" }, actor, new Follow({
83
+ id: new URL(`#follows/${actor.id?.href}`, fedCtx.getActorUri("i")),
84
+ actor: fedCtx.getActorUri("i"),
85
+ object: actor.id
86
+ }));
87
+ spinner.succeed(`Sent follow request to ${colors.green(uri)}.`);
88
+ spinner.start();
89
+ }
90
+ }
91
+ spinner.stop();
92
+ printServerInfo(fedCtx);
93
+ }
94
+ const federationDocumentLoader = await getDocumentLoader();
95
+ const federation = createFederation({
96
+ kv: new MemoryKvStore(),
97
+ documentLoaderFactory: () => {
98
+ return federationDocumentLoader;
99
+ }
100
+ });
101
+ const time = Temporal.Now.instant();
102
+ let actorKeyPairs = void 0;
103
+ federation.setActorDispatcher("/{identifier}", async (ctx, identifier) => {
104
+ if (identifier !== "i") return null;
105
+ return new Application({
106
+ id: ctx.getActorUri(identifier),
107
+ preferredUsername: identifier,
108
+ name: ctx.data.actorName,
109
+ summary: ctx.data.actorSummary,
110
+ inbox: ctx.getInboxUri(identifier),
111
+ endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri() }),
112
+ followers: ctx.getFollowersUri(identifier),
113
+ following: ctx.getFollowingUri(identifier),
114
+ outbox: ctx.getOutboxUri(identifier),
115
+ manuallyApprovesFollowers: true,
116
+ published: time,
117
+ icon: new Image({
118
+ url: new URL("https://fedify.dev/logo.png"),
119
+ mediaType: "image/png"
120
+ }),
121
+ publicKey: (await ctx.getActorKeyPairs(identifier))[0].cryptographicKey,
122
+ assertionMethods: (await ctx.getActorKeyPairs(identifier)).map((pair) => pair.multikey),
123
+ url: ctx.getActorUri(identifier)
124
+ });
125
+ }).setKeyPairsDispatcher(async (_ctxData, identifier) => {
126
+ if (identifier !== "i") return [];
127
+ if (actorKeyPairs == null) actorKeyPairs = [await generateCryptoKeyPair("RSASSA-PKCS1-v1_5"), await generateCryptoKeyPair("Ed25519")];
128
+ return actorKeyPairs;
129
+ });
130
+ const activities = [];
131
+ const acceptFollows = [];
132
+ async function acceptsFollowFrom(actor) {
133
+ const actorUri = actor.id;
134
+ let actorHandle = void 0;
135
+ if (actorUri == null) return false;
136
+ for (let uri of acceptFollows) {
137
+ if (uri === "*") return true;
138
+ if (uri.startsWith("http:") || uri.startsWith("https:")) {
139
+ uri = new URL(uri).href;
140
+ if (uri === actorUri.href) return true;
141
+ }
142
+ if (actorHandle == null) actorHandle = await getActorHandle(actor);
143
+ if (actorHandle === uri) return true;
144
+ }
145
+ return false;
146
+ }
147
+ const peers = {};
148
+ function createSendDeleteToPeers(actorOptions) {
149
+ return async function sendDeleteToPeers(server) {
150
+ const ctx = federation.createContext(new Request(server.url), {
151
+ activityIndex: -1,
152
+ actorName: actorOptions.actorName,
153
+ actorSummary: actorOptions.actorSummary
154
+ });
155
+ const actor = await ctx.getActor("i");
156
+ try {
157
+ await ctx.sendActivity({ identifier: "i" }, Object.values(peers), new Delete({
158
+ id: new URL(`#delete`, actor.id),
159
+ actor: actor.id,
160
+ to: PUBLIC_COLLECTION,
161
+ object: actor
162
+ }));
163
+ } catch (error) {
164
+ logger.error("Failed to send Delete(Application) activities to peers:\n{error}", { error });
165
+ }
166
+ };
167
+ }
168
+ const followers = {};
169
+ federation.setInboxListeners("/{identifier}/inbox", "/inbox").setSharedKeyDispatcher((_) => ({ identifier: "i" })).on(Activity, async (ctx, activity) => {
170
+ activities[ctx.data.activityIndex].activity = activity;
171
+ for await (const actor of activity.getActors()) if (actor.id != null) peers[actor.id.href] = actor;
172
+ for await (const actor of activity.getAttributions()) if (actor.id != null) peers[actor.id.href] = actor;
173
+ if (activity instanceof Follow) {
174
+ if (acceptFollows.length < 1) return;
175
+ const objectId = activity.objectId;
176
+ if (objectId == null) return;
177
+ const parsed = ctx.parseUri(objectId);
178
+ if (parsed?.type !== "actor" || parsed.identifier !== "i") return;
179
+ const { identifier } = parsed;
180
+ const follower = await activity.getActor();
181
+ if (!isActor(follower)) return;
182
+ const accepts = await acceptsFollowFrom(follower);
183
+ if (!accepts || activity.id == null) {
184
+ logger.debug("Does not accept follow from {actor}.", { actor: follower.id?.href });
185
+ return;
186
+ }
187
+ logger.debug("Accepting follow from {actor}.", { actor: follower.id?.href });
188
+ followers[activity.id.href] = follower;
189
+ await ctx.sendActivity({ identifier }, follower, new Accept({
190
+ id: new URL(`#accepts/${follower.id?.href}`, ctx.getActorUri("i")),
191
+ actor: ctx.getActorUri(identifier),
192
+ object: activity.id
193
+ }));
194
+ }
195
+ });
196
+ federation.setFollowersDispatcher("/{identifier}/followers", (_ctx, identifier) => {
197
+ if (identifier !== "i") return null;
198
+ const items = [];
199
+ for (const follower of Object.values(followers)) {
200
+ if (follower.id == null) continue;
201
+ items.push(follower);
202
+ }
203
+ return { items };
204
+ }).setCounter((_ctx, identifier) => {
205
+ if (identifier !== "i") return null;
206
+ return Object.keys(followers).length;
207
+ });
208
+ federation.setFollowingDispatcher("/{identifier}/following", (_ctx, _identifier) => null).setCounter((_ctx, _identifier) => 0);
209
+ federation.setOutboxDispatcher("/{identifier}/outbox", (_ctx, _identifier) => null).setCounter((_ctx, _identifier) => 0);
210
+ federation.setNodeInfoDispatcher("/nodeinfo/2.1", (_ctx) => {
211
+ return {
212
+ software: {
213
+ name: "fedify-cli",
214
+ version: deno_default.version,
215
+ repository: new URL("https://github.com/fedify-dev/fedify")
216
+ },
217
+ protocols: ["activitypub"],
218
+ usage: {
219
+ users: {
220
+ total: 1,
221
+ activeMonth: 1,
222
+ activeHalfyear: 1
223
+ },
224
+ localComments: 0,
225
+ localPosts: 0
226
+ }
227
+ };
228
+ });
229
+ function printServerInfo(fedCtx) {
230
+ const table = new Table({
231
+ chars: tableStyle,
232
+ style: {
233
+ head: [],
234
+ border: []
235
+ }
236
+ });
237
+ table.push({ "Actor handle:": colors.green(`i@${fedCtx.getActorUri("i").host}`) }, { "Actor URI:": colors.green(fedCtx.getActorUri("i").href) }, { "Actor inbox:": colors.green(fedCtx.getInboxUri("i").href) }, { "Shared inbox:": colors.green(fedCtx.getInboxUri().href) });
238
+ console.log(table.toString());
239
+ }
240
+ async function printActivityEntry(idx, entry) {
241
+ const request = entry.request.clone();
242
+ const response = entry.response?.clone();
243
+ const url = new URL(request.url);
244
+ const activity = entry.activity;
245
+ const object$1 = await activity?.getObject();
246
+ const table = new Table({
247
+ chars: tableStyle,
248
+ style: {
249
+ head: [],
250
+ border: []
251
+ }
252
+ });
253
+ table.push({ "Request #:": colors.bold(idx.toString()) }, { "Activity type:": activity == null ? colors.red("failed to parse") : colors.green(`${activity.constructor.name}(${object$1?.constructor.name})`) }, { "HTTP request:": `${request.method === "POST" ? colors.green("POST") : colors.red(request.method)} ${url.pathname + url.search}` }, ...response == null ? [] : [{ "HTTP response:": `${response.ok ? colors.green(response.status.toString()) : colors.red(response.status.toString())} ${response.statusText}` }], { "Details": new URL(`/r/${idx}`, url).href });
254
+ console.log(table.toString());
255
+ }
256
+ function getHandle(c) {
257
+ const url = new URL(c.req.url);
258
+ return `@i@${url.host}`;
259
+ }
260
+ const app = new Hono();
261
+ app.get("/", (c) => c.redirect("/r"));
262
+ app.get("/r", (c) => c.html(/* @__PURE__ */ jsx(ActivityListPage, {
263
+ handle: getHandle(c),
264
+ entries: activities
265
+ })));
266
+ app.get("/r/:idx{[0-9]+}", (c) => {
267
+ const idx = parseInt(c.req.param("idx"));
268
+ const tab = c.req.query("tab") ?? "request";
269
+ const activity = activities[idx];
270
+ if (activity == null) return c.notFound();
271
+ if (tab !== "request" && tab !== "response" && tab !== "raw-activity" && tab !== "compact-activity" && tab !== "expanded-activity" && tab !== "logs") return c.notFound();
272
+ return c.html(/* @__PURE__ */ jsx(ActivityEntryPage, {
273
+ handle: getHandle(c),
274
+ idx,
275
+ entry: activity,
276
+ tabPage: tab
277
+ }));
278
+ });
279
+ function createFetchHandler(actorOptions) {
280
+ return async function fetch(request) {
281
+ const timestamp = Temporal.Now.instant();
282
+ const idx = activities.length;
283
+ const pathname = new URL(request.url).pathname;
284
+ if (pathname === "/r" || pathname.startsWith("/r/")) return app.fetch(request);
285
+ const inboxRequest = pathname === "/inbox" || pathname.startsWith("/i/inbox");
286
+ if (inboxRequest) {
287
+ recordingSink.startRecording();
288
+ activities.push({
289
+ timestamp,
290
+ request: request.clone(),
291
+ logs: []
292
+ });
293
+ }
294
+ const response = await federation.fetch(request, {
295
+ contextData: {
296
+ activityIndex: inboxRequest ? idx : -1,
297
+ actorName: actorOptions.actorName,
298
+ actorSummary: actorOptions.actorSummary
299
+ },
300
+ onNotAcceptable: app.fetch.bind(app),
301
+ onNotFound: app.fetch.bind(app),
302
+ onUnauthorized: app.fetch.bind(app)
303
+ });
304
+ if (inboxRequest) {
305
+ recordingSink.stopRecording();
306
+ activities[idx].response = response.clone();
307
+ activities[idx].logs = recordingSink.getRecords();
308
+ await printActivityEntry(idx, activities[idx]);
309
+ }
310
+ return response;
311
+ };
312
+ }
313
+
314
+ //#endregion
315
+ export { inboxCommand, runInbox };
@@ -0,0 +1,81 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import biome_default from "../json/biome.js";
5
+ import vscode_settings_for_deno_default from "../json/vscode-settings-for-deno.js";
6
+ import vscode_settings_default from "../json/vscode-settings.js";
7
+ import { join } from "node:path";
8
+
9
+ //#region src/init/action/configs.ts
10
+ /**
11
+ * Loads Deno configuration object with compiler options, unstable features, and tasks.
12
+ * Combines unstable features required by KV store and message queue with framework-specific options.
13
+ *
14
+ * @param param0 - Destructured initialization data containing KV, MQ, initializer, and directory
15
+ * @returns Configuration object with path and Deno-specific settings
16
+ */
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
26
+ });
27
+ /**
28
+ * Loads TypeScript configuration object for Node.js/Bun projects.
29
+ * Uses compiler options from the framework initializer.
30
+ *
31
+ * @param param0 - Destructured initialization data containing initializer and directory
32
+ * @returns Configuration object with path and TypeScript compiler options
33
+ */
34
+ const loadTsConfig = ({ initializer, dir }) => ({
35
+ path: join(dir, "tsconfig.json"),
36
+ data: { compilerOptions: initializer.compilerOptions }
37
+ });
38
+ /**
39
+ * Loads package.json configuration object for Node.js/Bun projects.
40
+ * Sets up ES modules and includes framework-specific npm scripts.
41
+ *
42
+ * @param param0 - Destructured initialization data containing initializer and directory
43
+ * @returns Configuration object with path and package.json settings
44
+ */
45
+ const loadPackageJson = ({ initializer, dir }) => ({
46
+ path: join(dir, "package.json"),
47
+ data: {
48
+ type: "module",
49
+ scripts: initializer.tasks
50
+ }
51
+ });
52
+ /**
53
+ * Configuration objects for various development tool setup files.
54
+ * Contains predefined configurations for code formatting, VS Code settings, and extensions
55
+ * based on the project type (Node.js/Bun or Deno).
56
+ */
57
+ const devToolConfigs = {
58
+ biome: {
59
+ path: join("biome.json"),
60
+ data: biome_default
61
+ },
62
+ vscExt: {
63
+ path: join(".vscode", "extensions.json"),
64
+ data: { recommendations: ["biomejs.biome"] }
65
+ },
66
+ vscSet: {
67
+ path: join(".vscode", "settings.json"),
68
+ data: vscode_settings_default
69
+ },
70
+ vscSetDeno: {
71
+ path: join(".vscode", "settings.json"),
72
+ data: vscode_settings_for_deno_default
73
+ },
74
+ vscExtDeno: {
75
+ path: join(".vscode", "extensions.json"),
76
+ data: { recommendations: ["denoland.vscode-deno"] }
77
+ }
78
+ };
79
+
80
+ //#endregion
81
+ export { devToolConfigs, loadDenoConfig, loadPackageJson, loadTsConfig };
@@ -0,0 +1,52 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { merge } from "../../utils.js";
5
+ import { PACKAGE_VERSION } from "../lib.js";
6
+ import { entries, map, pipe, toArray } from "@fxts/core";
7
+
8
+ //#region src/init/action/deps.ts
9
+ /**
10
+ * Gathers all dependencies required for the project based on the initializer,
11
+ * key-value store, and message queue configurations.
12
+ *
13
+ * @param data - Web Framework initializer, key-value store and message queue descriptions
14
+ * @returns A record of dependencies with their versions
15
+ */
16
+ const getDependencies = ({ initializer, kv, mq }) => pipe({
17
+ "@fedify/fedify": PACKAGE_VERSION,
18
+ "@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.
22
+ *
23
+ * @param data - Web Framework initializer, key-value store and message queue descriptions
24
+ * @returns A record of devDependencies with their versions
25
+ */
26
+ const getDevDependencies = ({ initializer, kv, mq }) => pipe({ "@biomejs/biome": "^2.2.4" }, merge(initializer.devDependencies), merge(kv.devDependencies), merge(mq.devDependencies));
27
+ /**
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:'.
43
+ *
44
+ * @param data - Package manager and dependencies to be joined with versions
45
+ * @returns `${registry}:${package}@${version}`[] for deno or `${package}@${version}`[] for others
46
+ */
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;
49
+ const getPackageVersion = (pm, version) => pm !== "deno" && version.includes("+") ? version.substring(0, version.indexOf("+")) : version;
50
+
51
+ //#endregion
52
+ export { getAddDepsArgs, getDependencies, getDevDependencies, joinDepsVer };
@@ -0,0 +1,16 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { mkdir } from "node:fs/promises";
5
+
6
+ //#region src/init/action/dir.ts
7
+ /**
8
+ * Creates the target directory if it does not exist.
9
+ *
10
+ * @param data - The directory
11
+ * @returns A promise that resolves when the directory is created
12
+ */
13
+ const makeDirIfHyd = ({ dir }) => mkdir(dir, { recursive: true });
14
+
15
+ //#endregion
16
+ export { makeDirIfHyd };
@@ -0,0 +1,13 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { notEmpty } from "../../utils.js";
5
+ import { noticeConfigEnv, noticeEnvKeyValue } from "./notice.js";
6
+ import { entries, forEach, pipeLazy, tap, toArray, when } from "@fxts/core";
7
+
8
+ //#region src/init/action/env.ts
9
+ const recommendConfigEnv = pipeLazy((data) => data.env, entries, toArray, when(notEmpty, tap(noticeConfigEnv)), forEach(noticeEnvKeyValue));
10
+ var env_default = recommendConfigEnv;
11
+
12
+ //#endregion
13
+ export { env_default as default };
@@ -0,0 +1,22 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
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";
9
+
10
+ //#region src/init/action/install.ts
11
+ const installDependencies = (data) => pipe(data, tap(installDeps), unless(isDeno, tap(installDevDeps)));
12
+ 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
+
21
+ //#endregion
22
+ export { install_default as default };
@@ -0,0 +1,39 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import ask_default from "../ask/mod.js";
5
+ import { makeDirIfHyd } from "./dir.js";
6
+ import { drawDinosaur, noticeHowToRun, noticeOptions, noticePrecommand } from "./notice.js";
7
+ import env_default from "./env.js";
8
+ import { hasCommand, isDry } from "./utils.js";
9
+ import install_default from "./install.js";
10
+ import { patchFiles, recommendPatchFiles } from "./patch.js";
11
+ import precommand_default from "./precommand.js";
12
+ import recommend_default from "./recommend.js";
13
+ import set_default from "./set.js";
14
+ import { pipe, tap, unless, when } from "@fxts/core";
15
+
16
+ //#region src/init/action/mod.ts
17
+ /**
18
+ * options: InitCommand
19
+ * ├ drawDinosaur
20
+ * ┌─────┴──────┐
21
+ * │ askOptions │ InitCommand -> InitCommandOptions
22
+ * └─────┬──────┘
23
+ * ├ noticeOptions
24
+ * ┌────┴────┐
25
+ * │ setData │ InitCommandOptions -> InitCommandData
26
+ * └────┬────┘
27
+ * ┌─┴─┐ isDry
28
+ * handleDryRun ┤ ├ handleHydRun
29
+ * └─┬─┘
30
+ * ├ recommendConfigEnv
31
+ * ├ noticeHowToRun
32
+ */
33
+ const runInit = (options) => pipe(options, tap(drawDinosaur), ask_default, tap(noticeOptions), set_default, when(isDry, handleDryRun), unless(isDry, handleHydRun), tap(env_default), tap(noticeHowToRun));
34
+ var action_default = runInit;
35
+ const handleDryRun = (data) => pipe(data, tap(when(hasCommand, noticePrecommand)), tap(recommendPatchFiles), tap(recommend_default));
36
+ const handleHydRun = (data) => pipe(data, tap(makeDirIfHyd), tap(when(hasCommand, precommand_default)), tap(patchFiles), tap(install_default));
37
+
38
+ //#endregion
39
+ export { action_default as default };
@@ -0,0 +1,62 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { colors } from "../../utils.js";
5
+ import { message } from "@optique/core";
6
+ import { print, printError } from "@optique/run";
7
+ import { flow } from "es-toolkit";
8
+
9
+ //#region src/init/action/notice.ts
10
+ const printMessage = flow(message, print);
11
+ const printErrorMessage = flow(message, printError);
12
+ function drawDinosaur() {
13
+ const d = flow(colors.bgBlue, colors.black);
14
+ const f = colors.blue;
15
+ console.error(`\
16
+ ${d(" ___ ")} ${f(" _____ _ _ __")}
17
+ ${d(" /'_') ")} ${f("| ___|__ __| (_)/ _|_ _")}
18
+ ${d(" .-^^^-/ / ")} ${f("| |_ / _ \\/ _` | | |_| | | |")}
19
+ ${d(" __/ / ")} ${f("| _| __/ (_| | | _| |_| |")}
20
+ ${d(" <__.|_|-|_| ")} ${f("|_| \\___|\\__,_|_|_| \\__, |")}
21
+ ${d(" ")} ${f(" |___/")}
22
+ `);
23
+ }
24
+ const noticeOptions = ({ packageManager, webFramework, kvStore, messageQueue }) => printMessage`
25
+ Package manager: ${packageManager};
26
+ Web framework: ${webFramework};
27
+ Key–value store: ${kvStore};
28
+ Message queue: ${messageQueue};
29
+ `;
30
+ function noticePrecommand({ initializer: { command: command$1 }, dir }) {
31
+ printMessage`📦 Would run command:`;
32
+ printMessage` cd ${dir}`;
33
+ printMessage` ${command$1.join(" ")}\n`;
34
+ }
35
+ const noticeFilesToCreate = () => printMessage`📄 Would create files:\n`;
36
+ const noticeFilesToInsert = () => printMessage`Would create/update JSON files:\n`;
37
+ const noticeDepsIfExist = () => printMessage`📦 Would install dependencies:`;
38
+ const noticeDevDepsIfExist = () => printMessage`📦 Would install dev dependencies:`;
39
+ const noticeDeps = ([name, version]) => printMessage`${name}@${version}`;
40
+ function displayFile(path$1, content, emoji = "📄") {
41
+ printMessage`${emoji} ${path$1}`;
42
+ printMessage`${"─".repeat(60)}`;
43
+ printMessage`${content}`;
44
+ printMessage`${"─".repeat(60)}\n`;
45
+ }
46
+ const noticeConfigEnv = () => printMessage`Note that you probably want to edit the ${".env"} file.
47
+ 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!
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
+
61
+ //#endregion
62
+ export { displayFile, drawDinosaur, noticeConfigEnv, noticeDeps, noticeDepsIfExist, noticeDevDepsIfExist, noticeEnvKeyValue, noticeErrorWhileAddDeps, noticeFilesToCreate, noticeFilesToInsert, noticeHowToRun, noticeOptions, noticePrecommand };