@fedify/cli 2.0.0-pr.479.1922 → 2.0.1

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 (160) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +3 -3
  3. package/dist/cache.js +17 -3
  4. package/dist/config.js +105 -0
  5. package/dist/deno.js +18 -8
  6. package/dist/generate-vocab/action.js +1 -1
  7. package/dist/imagerenderer.js +1 -1
  8. package/dist/inbox/rendercode.js +11 -21
  9. package/dist/inbox.js +162 -132
  10. package/dist/init/mod.js +3 -3
  11. package/dist/log.js +35 -1
  12. package/dist/lookup.js +55 -23
  13. package/dist/mod.js +95 -18
  14. package/dist/nodeinfo.js +39 -22
  15. package/dist/options.js +84 -0
  16. package/dist/relay.js +136 -0
  17. package/dist/tempserver.js +15 -8
  18. package/dist/tunnel.js +6 -10
  19. package/dist/utils.js +19 -108
  20. package/dist/webfinger/action.js +1 -1
  21. package/dist/webfinger/command.js +17 -9
  22. package/dist/webfinger/lib.js +3 -3
  23. package/package.json +50 -28
  24. package/deno.json +0 -71
  25. package/dist/globals.js +0 -49
  26. package/dist/init/action/configs.js +0 -91
  27. package/dist/init/action/const.js +0 -10
  28. package/dist/init/action/deps.js +0 -50
  29. package/dist/init/action/dir.js +0 -16
  30. package/dist/init/action/env.js +0 -13
  31. package/dist/init/action/install.js +0 -20
  32. package/dist/init/action/mod.js +0 -39
  33. package/dist/init/action/notice.js +0 -55
  34. package/dist/init/action/patch.js +0 -147
  35. package/dist/init/action/precommand.js +0 -28
  36. package/dist/init/action/recommend.js +0 -24
  37. package/dist/init/action/set.js +0 -31
  38. package/dist/init/action/templates.js +0 -58
  39. package/dist/init/action/utils.js +0 -50
  40. package/dist/init/ask/dir.js +0 -82
  41. package/dist/init/ask/kv.js +0 -44
  42. package/dist/init/ask/mod.js +0 -16
  43. package/dist/init/ask/mq.js +0 -46
  44. package/dist/init/ask/pm.js +0 -49
  45. package/dist/init/ask/wf.js +0 -29
  46. package/dist/init/command.js +0 -50
  47. package/dist/init/const.js +0 -31
  48. package/dist/init/json/biome.js +0 -24
  49. package/dist/init/json/kv.js +0 -53
  50. package/dist/init/json/mq.js +0 -72
  51. package/dist/init/json/pm.js +0 -44
  52. package/dist/init/json/rt.js +0 -39
  53. package/dist/init/json/vscode-settings-for-deno.js +0 -53
  54. package/dist/init/json/vscode-settings.js +0 -49
  55. package/dist/init/lib.js +0 -136
  56. package/dist/init/templates/defaults/federation.ts.tpl +0 -23
  57. package/dist/init/templates/defaults/logging.ts.tpl +0 -23
  58. package/dist/init/templates/express/app.ts.tpl +0 -16
  59. package/dist/init/templates/express/index.ts.tpl +0 -6
  60. package/dist/init/templates/hono/app.tsx.tpl +0 -14
  61. package/dist/init/templates/hono/index/bun.ts.tpl +0 -10
  62. package/dist/init/templates/hono/index/deno.ts.tpl +0 -13
  63. package/dist/init/templates/hono/index/node.ts.tpl +0 -14
  64. package/dist/init/templates/next/middleware.ts.tpl +0 -45
  65. package/dist/init/templates/nitro/.env.test.tpl +0 -1
  66. package/dist/init/templates/nitro/nitro.config.ts.tpl +0 -14
  67. package/dist/init/templates/nitro/server/error.ts.tpl +0 -3
  68. package/dist/init/templates/nitro/server/middleware/federation.ts.tpl +0 -8
  69. package/dist/init/test/action.js +0 -17
  70. package/dist/init/test/create.js +0 -100
  71. package/dist/init/test/fill.js +0 -32
  72. package/dist/init/test/lookup.js +0 -190
  73. package/dist/init/test/run.js +0 -25
  74. package/dist/init/test/utils.js +0 -17
  75. package/dist/init/webframeworks.js +0 -136
  76. package/scripts/pack.ts +0 -71
  77. package/src/cache.ts +0 -17
  78. package/src/docloader.ts +0 -67
  79. package/src/generate-vocab/action.ts +0 -17
  80. package/src/generate-vocab/command.ts +0 -44
  81. package/src/generate-vocab/mod.ts +0 -2
  82. package/src/globals.ts +0 -43
  83. package/src/imagerenderer.ts +0 -149
  84. package/src/inbox/entry.ts +0 -10
  85. package/src/inbox/rendercode.ts +0 -68
  86. package/src/inbox/view.tsx +0 -598
  87. package/src/inbox.tsx +0 -536
  88. package/src/init/action/configs.ts +0 -133
  89. package/src/init/action/const.ts +0 -9
  90. package/src/init/action/deps.ts +0 -161
  91. package/src/init/action/dir.ts +0 -11
  92. package/src/init/action/env.ts +0 -14
  93. package/src/init/action/install.ts +0 -24
  94. package/src/init/action/mod.ts +0 -66
  95. package/src/init/action/notice.ts +0 -103
  96. package/src/init/action/patch.ts +0 -233
  97. package/src/init/action/precommand.ts +0 -29
  98. package/src/init/action/recommend.ts +0 -38
  99. package/src/init/action/set.ts +0 -65
  100. package/src/init/action/templates.ts +0 -96
  101. package/src/init/action/utils.ts +0 -64
  102. package/src/init/ask/dir.ts +0 -98
  103. package/src/init/ask/kv.ts +0 -82
  104. package/src/init/ask/mod.ts +0 -23
  105. package/src/init/ask/mq.ts +0 -86
  106. package/src/init/ask/pm.ts +0 -58
  107. package/src/init/ask/wf.ts +0 -27
  108. package/src/init/command.ts +0 -135
  109. package/src/init/const.ts +0 -4
  110. package/src/init/json/biome.json +0 -17
  111. package/src/init/json/kv.json +0 -39
  112. package/src/init/json/mq.json +0 -95
  113. package/src/init/json/pm.json +0 -47
  114. package/src/init/json/rt.json +0 -42
  115. package/src/init/json/vscode-settings-for-deno.json +0 -43
  116. package/src/init/json/vscode-settings.json +0 -41
  117. package/src/init/lib.ts +0 -223
  118. package/src/init/mod.ts +0 -3
  119. package/src/init/templates/defaults/federation.ts.tpl +0 -23
  120. package/src/init/templates/defaults/logging.ts.tpl +0 -23
  121. package/src/init/templates/express/app.ts.tpl +0 -16
  122. package/src/init/templates/express/index.ts.tpl +0 -6
  123. package/src/init/templates/hono/app.tsx.tpl +0 -14
  124. package/src/init/templates/hono/index/bun.ts.tpl +0 -10
  125. package/src/init/templates/hono/index/deno.ts.tpl +0 -13
  126. package/src/init/templates/hono/index/node.ts.tpl +0 -14
  127. package/src/init/templates/next/middleware.ts.tpl +0 -45
  128. package/src/init/templates/nitro/.env.test.tpl +0 -1
  129. package/src/init/templates/nitro/nitro.config.ts.tpl +0 -14
  130. package/src/init/templates/nitro/server/error.ts.tpl +0 -3
  131. package/src/init/templates/nitro/server/middleware/federation.ts.tpl +0 -8
  132. package/src/init/test/action.ts +0 -28
  133. package/src/init/test/create.ts +0 -137
  134. package/src/init/test/fill.ts +0 -67
  135. package/src/init/test/lookup.ts +0 -254
  136. package/src/init/test/run.ts +0 -39
  137. package/src/init/test/types.ts +0 -27
  138. package/src/init/test/utils.ts +0 -21
  139. package/src/init/types.ts +0 -89
  140. package/src/init/webframeworks.ts +0 -168
  141. package/src/kv.bun.ts +0 -12
  142. package/src/kv.node.ts +0 -11
  143. package/src/log.ts +0 -64
  144. package/src/lookup.test.ts +0 -182
  145. package/src/lookup.ts +0 -563
  146. package/src/mod.ts +0 -62
  147. package/src/nodeinfo.test.ts +0 -229
  148. package/src/nodeinfo.ts +0 -454
  149. package/src/table.ts +0 -17
  150. package/src/tempserver.ts +0 -87
  151. package/src/tunnel.test.ts +0 -157
  152. package/src/tunnel.ts +0 -94
  153. package/src/utils.ts +0 -254
  154. package/src/webfinger/action.ts +0 -50
  155. package/src/webfinger/command.ts +0 -64
  156. package/src/webfinger/error.ts +0 -47
  157. package/src/webfinger/lib.ts +0 -37
  158. package/src/webfinger/mod.test.ts +0 -79
  159. package/src/webfinger/mod.ts +0 -2
  160. package/tsdown.config.ts +0 -35
@@ -1,229 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { test } from "node:test";
3
- import { Chalk } from "chalk";
4
- import fetchMock from "fetch-mock";
5
- import { getAsciiArt, getFaviconUrl, Jimp, rgbTo256Color } from "./nodeinfo.ts";
6
-
7
- const HTML_WITH_SMALL_ICON = `
8
- <!DOCTYPE html>
9
- <html>
10
- <head>
11
- <title>Test Site</title>
12
- <link rel="icon" href="/favicon.ico" sizes="32x32">
13
- <link rel="apple-touch-icon" href="/apple-touch-icon.png">
14
- </head>
15
- <body>Test</body>
16
- </html>
17
- `;
18
-
19
- test("getFaviconUrl - small favicon.ico and apple-touch-icon.png", async () => {
20
- fetchMock.spyGlobal();
21
-
22
- fetchMock.get("https://example.com/", {
23
- body: HTML_WITH_SMALL_ICON,
24
- headers: { "Content-Type": "text/html" },
25
- });
26
-
27
- const result = await getFaviconUrl("https://example.com/");
28
- assert.equal(result.href, "https://example.com/apple-touch-icon.png");
29
-
30
- fetchMock.hardReset();
31
- });
32
-
33
- const HTML_WITH_ICON = `
34
- <!DOCTYPE html>
35
- <html>
36
- <head>
37
- <title>Test Site</title>
38
- <link rel="icon" href="/favicon.ico" sizes="64x64">
39
- <link rel="apple-touch-icon" href="/apple-touch-icon.png">
40
- </head>
41
- <body>Test</body>
42
- </html>
43
- `;
44
-
45
- test("getFaviconUrl - favicon.ico and apple-touch-icon.png", async () => {
46
- fetchMock.spyGlobal();
47
-
48
- fetchMock.get("https://example.com/", {
49
- body: HTML_WITH_ICON,
50
- headers: { "Content-Type": "text/html" },
51
- });
52
-
53
- const result = await getFaviconUrl("https://example.com/");
54
- assert.equal(result.href, "https://example.com/favicon.ico");
55
-
56
- fetchMock.hardReset();
57
- });
58
-
59
- const HTML_WITH_SVG_ONLY = `
60
- <!DOCTYPE html>
61
- <html>
62
- <head>
63
- <title>Test Site</title>
64
- <link rel="icon" href="/icon.svg" type="image/svg+xml">
65
- </head>
66
- <body>Test</body>
67
- </html>
68
- `;
69
-
70
- test("getFaviconUrl - svg icons only falls back to /favicon.ico", async () => {
71
- fetchMock.spyGlobal();
72
-
73
- fetchMock.get("https://example.com/", {
74
- body: HTML_WITH_SVG_ONLY,
75
- headers: { "Content-Type": "text/html" },
76
- });
77
-
78
- const result = await getFaviconUrl("https://example.com/");
79
- assert.equal(result.href, "https://example.com/favicon.ico");
80
-
81
- fetchMock.hardReset();
82
- });
83
-
84
- const HTML_WITHOUT_ICON = `
85
- <!DOCTYPE html>
86
- <html>
87
- <head>
88
- <title>Test Site</title>
89
- </head>
90
- <body>Test</body>
91
- </html>
92
- `;
93
-
94
- test("getFaviconUrl - falls back to /favicon.ico", async () => {
95
- fetchMock.spyGlobal();
96
-
97
- fetchMock.get("https://example.com/", {
98
- body: HTML_WITHOUT_ICON,
99
- headers: { "Content-Type": "text/html" },
100
- });
101
-
102
- const result = await getFaviconUrl("https://example.com/");
103
- assert.equal(result.href, "https://example.com/favicon.ico");
104
-
105
- fetchMock.hardReset();
106
- });
107
-
108
- test("rgbTo256Color - check RGB cube", () => {
109
- const CUBE_VALUES = [0, 95, 135, 175, 215, 255];
110
- const colors: Array<{ r: number; g: number; b: number }> = [];
111
-
112
- for (let r = 0; r < 6; r++) {
113
- for (let g = 0; g < 6; g++) {
114
- for (let b = 0; b < 6; b++) {
115
- colors.push({
116
- r: CUBE_VALUES[r],
117
- g: CUBE_VALUES[g],
118
- b: CUBE_VALUES[b],
119
- });
120
- }
121
- }
122
- }
123
-
124
- // Expected color indices for the above colors (16-231)
125
- // RGB cube: 6x6x6 = 216 colors, indices 16-231
126
- const expected_color_idx = Array.from(
127
- { length: colors.length },
128
- (_, i) => 16 + i,
129
- );
130
-
131
- const results = colors.map((color) =>
132
- rgbTo256Color(color.r, color.g, color.b)
133
- );
134
- assert.deepEqual(results, expected_color_idx);
135
- });
136
-
137
- test("rgbTo256Color - check grayscale", () => {
138
- const grayscale = Array.from({ length: 24 }).map(
139
- (_, idx) => ({
140
- r: 8 + idx * 10,
141
- g: 8 + idx * 10,
142
- b: 8 + idx * 10,
143
- }),
144
- );
145
-
146
- const expected_gray_idx = Array.from(
147
- { length: grayscale.length },
148
- (_, i) => 232 + i,
149
- );
150
-
151
- const results = grayscale.map((GRAY) =>
152
- rgbTo256Color(GRAY.r, GRAY.g, GRAY.b)
153
- );
154
- assert.deepEqual(results, expected_gray_idx);
155
- });
156
-
157
- async function createTestImage(
158
- color: number,
159
- ): Promise<Awaited<ReturnType<typeof Jimp.read>>> {
160
- const image = new Jimp({ width: 1, height: 1, color });
161
- const imageBuffer = await image.getBuffer("image/png");
162
- return Jimp.read(imageBuffer);
163
- }
164
-
165
- test("getAsciiArt - Darkest Letter without color support", async () => {
166
- const blackResult = getAsciiArt(
167
- await createTestImage(0x000000ff),
168
- 1,
169
- "none",
170
- new Chalk({ level: 0 }),
171
- );
172
-
173
- assert.equal(blackResult, "█");
174
- });
175
-
176
- test("getAsciiArt - Brightest Letter without color support", async () => {
177
- const whiteResult = getAsciiArt(
178
- await createTestImage(0xffffffff),
179
- 1,
180
- "none",
181
- new Chalk({ level: 0 }),
182
- );
183
-
184
- assert.equal(whiteResult, " ");
185
- });
186
-
187
- test("getAsciiArt - Darkest Letter with 256 color support", async () => {
188
- const blackResult = getAsciiArt(
189
- await createTestImage(0x000000ff),
190
- 1,
191
- "256color",
192
- new Chalk({ level: 2 }),
193
- );
194
-
195
- assert.equal(blackResult, "\u001b[38;5;16m█\u001b[39m");
196
- });
197
-
198
- test("getAsciiArt - Brightest Letter with 256 color support", async () => {
199
- const whiteResult = getAsciiArt(
200
- await createTestImage(0xffffffff),
201
- 1,
202
- "256color",
203
- new Chalk({ level: 2 }),
204
- );
205
-
206
- assert.equal(whiteResult, "\u001b[38;5;231m \u001b[39m");
207
- });
208
-
209
- test("getAsciiArt - Darkest Letter with true color support", async () => {
210
- const blackResult = getAsciiArt(
211
- await createTestImage(0x000000ff),
212
- 1,
213
- "truecolor",
214
- new Chalk({ level: 3 }),
215
- );
216
-
217
- assert.equal(blackResult, "\u001b[38;2;0;0;0m█\u001b[39m");
218
- });
219
-
220
- test("getAsciiArt - Brightest Letter with true color support", async () => {
221
- const whiteResult = getAsciiArt(
222
- await createTestImage(0xffffffff),
223
- 1,
224
- "truecolor",
225
- new Chalk({ level: 3 }),
226
- );
227
-
228
- assert.equal(whiteResult, "\u001b[38;2;255;255;255m \u001b[39m");
229
- });
package/src/nodeinfo.ts DELETED
@@ -1,454 +0,0 @@
1
- import { getNodeInfo } from "@fedify/fedify";
2
- import { getUserAgent } from "@fedify/vocab-runtime";
3
- import { createJimp } from "@jimp/core";
4
- import webp from "@jimp/wasm-webp";
5
- import { getLogger } from "@logtape/logtape";
6
- import {
7
- argument,
8
- command as Command,
9
- constant,
10
- flag,
11
- type InferValue,
12
- merge,
13
- message,
14
- object,
15
- option,
16
- optional,
17
- or,
18
- string,
19
- text,
20
- } from "@optique/core";
21
- import { print, printError } from "@optique/run";
22
- import type { ChalkInstance } from "chalk";
23
- import { isICO, parseICO } from "icojs";
24
- import { defaultFormats, defaultPlugins, intToRGBA } from "jimp";
25
- import ora from "ora";
26
- import os from "node:os";
27
- import process from "node:process";
28
- import { debugOption } from "./globals.ts";
29
- import { colors, formatObject } from "./utils.ts";
30
-
31
- const logger = getLogger(["fedify", "cli", "nodeinfo"]);
32
-
33
- export const Jimp = createJimp({
34
- formats: [...defaultFormats, webp],
35
- plugins: defaultPlugins,
36
- });
37
-
38
- const nodeInfoOption = optional(
39
- or(
40
- object({
41
- raw: flag("-r", "--raw", {
42
- description: message`Show NodeInfo document in the raw JSON format`,
43
- }),
44
- }),
45
- object({
46
- bestEffort: optional(flag("-b", "--best-effort", {
47
- description:
48
- message`Parse the NodeInfo document with best effort. If the NodeInfo document is not well-formed, the option will try to parse it as much as possible.`,
49
- })),
50
- noFavicon: optional(flag("--no-favicon", {
51
- description: message`Disable fetching the favicon of the instance`,
52
- })),
53
- metadata: optional(flag("-m", "--metadata", {
54
- description:
55
- message`Show the extra metadata of the NodeInfo, i.e., the metadata field of the document.`,
56
- })),
57
- }),
58
- ),
59
- );
60
-
61
- const userAgentOption = optional(object({
62
- userAgent: option("-u", "--user-agent", string(), {
63
- description: message`The custom User-Agent header value.`,
64
- }),
65
- }));
66
-
67
- export const nodeInfoCommand = Command(
68
- "nodeinfo",
69
- merge(
70
- object({
71
- command: constant("nodeinfo"),
72
- host: argument(string({ metavar: "HOST" }), {
73
- description: message`Bare hostname or a full URL of the instance`,
74
- }),
75
- }),
76
- debugOption,
77
- nodeInfoOption,
78
- userAgentOption,
79
- ),
80
- {
81
- brief:
82
- message`Get information about a remote node using the NodeInfo protocol`,
83
- description:
84
- message`Get information about a remote node using the NodeInfo protocol.
85
-
86
- The argument is the hostname of the remote node, or the URL of the remote node.`,
87
- },
88
- );
89
-
90
- export async function runNodeInfo(
91
- command: InferValue<typeof nodeInfoCommand>,
92
- ) {
93
- const spinner = ora({
94
- text: "Fetching a NodeInfo document...",
95
- discardStdin: false,
96
- }).start();
97
-
98
- const url = new URL(
99
- URL.canParse(command.host) ? command.host : `https://${command.host}`,
100
- );
101
-
102
- if ("raw" in command && command.raw) {
103
- const nodeInfo = await getNodeInfo(url, {
104
- parse: "none",
105
- userAgent: command.userAgent,
106
- });
107
-
108
- if (nodeInfo === undefined) {
109
- spinner.fail("No NodeInfo document found.");
110
- printError(message`No NodeInfo document found.`);
111
- process.exit(1);
112
- }
113
- spinner.succeed("NodeInfo document fetched.");
114
-
115
- print(message`${text(formatObject(nodeInfo, undefined, true))}`);
116
- return;
117
- }
118
- const nodeInfo = await getNodeInfo(url, {
119
- parse: "bestEffort" in command && command.bestEffort
120
- ? "best-effort"
121
- : "strict",
122
- userAgent: command.userAgent,
123
- });
124
- logger.debug("NodeInfo document: {nodeInfo}", { nodeInfo });
125
- if (nodeInfo == undefined) {
126
- spinner.fail("No NodeInfo document found or it is invalid.");
127
- printError(message`No NodeInfo document found or it is invalid.`);
128
- if (!("bestEffort" in command && command.bestEffort)) {
129
- printError(
130
- message`Use the -b/--best-effort option to try to parse the document anyway.`,
131
- );
132
- }
133
- process.exit(1);
134
- }
135
-
136
- let layout: string[];
137
- let defaultWidth = 0;
138
-
139
- if (!("noFavicon" in command && command.noFavicon)) {
140
- spinner.text = "Fetching the favicon...";
141
- try {
142
- const faviconUrl = await getFaviconUrl(url, command.userAgent);
143
- const response = await fetch(faviconUrl, {
144
- headers: {
145
- "User-Agent": command.userAgent == null
146
- ? getUserAgent()
147
- : command.userAgent,
148
- },
149
- });
150
- if (response.ok) {
151
- const contentType = response.headers.get("Content-Type");
152
- let buffer: ArrayBuffer = await response.arrayBuffer();
153
- if (
154
- contentType === "image/vnd.microsoft.icon" ||
155
- contentType === "image/x-icon" ||
156
- isICO(buffer)
157
- ) {
158
- const images = await parseICO(buffer);
159
- if (images.length < 1) {
160
- throw new Error("No images found in the ICO file.");
161
- }
162
- buffer = images[0].buffer;
163
- }
164
- const image = await Jimp.read(buffer);
165
- const colorSupport = checkTerminalColorSupport();
166
- layout = getAsciiArt(image, DEFAULT_IMAGE_WIDTH, colorSupport, colors)
167
- .split("\n").map((line) => ` ${line} `);
168
- defaultWidth = 41;
169
- } else {
170
- logger.error(
171
- "Failed to fetch the favicon: {status} {statusText}",
172
- { status: response.status, statusText: response.statusText },
173
- );
174
- layout = [""];
175
- }
176
- } catch (error) {
177
- logger.error(
178
- "Failed to fetch or render the favicon: {error}",
179
- { error },
180
- );
181
- layout = [""];
182
- }
183
- } else {
184
- layout = [""];
185
- }
186
- spinner.succeed("NodeInfo document fetched.");
187
- print(message``);
188
-
189
- let i = 0;
190
- const next = () => {
191
- i++;
192
- if (i >= layout.length) layout.push(" ".repeat(defaultWidth));
193
- return i;
194
- };
195
- layout[i] += colors.bold(url.host);
196
- layout[next()] += colors.dim("=".repeat(url.host.length));
197
- layout[next()] += colors.bold(colors.dim("Software:"));
198
- layout[next()] += ` ${nodeInfo.software.name} v${nodeInfo.software.version}`;
199
- if (nodeInfo.software.homepage != null) {
200
- layout[next()] += ` ${nodeInfo.software.homepage.href}`;
201
- }
202
- if (nodeInfo.software.repository != null) {
203
- layout[next()] += " " +
204
- colors.dim(nodeInfo.software.repository.href);
205
- }
206
- if (nodeInfo.protocols.length > 0) {
207
- layout[next()] += colors.bold(colors.dim("Protocols:"));
208
- for (const protocol of nodeInfo.protocols) {
209
- layout[next()] += ` ${protocol}`;
210
- }
211
- }
212
- if (nodeInfo.services?.inbound?.length ?? 0 > 0) {
213
- layout[next()] += colors.bold(colors.dim("Inbound services:"));
214
- for (const service of nodeInfo.services?.inbound ?? []) {
215
- layout[next()] += ` ${service}`;
216
- }
217
- }
218
- if (nodeInfo.services?.outbound?.length ?? 0 > 0) {
219
- layout[next()] += colors.bold(colors.dim("Outbound services:"));
220
- for (const service of nodeInfo.services?.outbound ?? []) {
221
- layout[next()] += ` ${service}`;
222
- }
223
- }
224
- if (
225
- nodeInfo.usage?.users != null && (nodeInfo.usage.users.total != null ||
226
- nodeInfo.usage.users.activeHalfyear != null ||
227
- nodeInfo.usage.users.activeMonth != null)
228
- ) {
229
- layout[next()] += colors.bold(colors.dim("Users:"));
230
- if (nodeInfo.usage.users.total != null) {
231
- layout[next()] +=
232
- ` ${nodeInfo.usage.users.total.toLocaleString("en-US")} ` +
233
- colors.dim("(total)");
234
- }
235
- if (nodeInfo.usage.users.activeHalfyear != null) {
236
- layout[next()] +=
237
- ` ${nodeInfo.usage.users.activeHalfyear.toLocaleString("en-US")} ` +
238
- colors.dim("(active half year)");
239
- }
240
- if (nodeInfo.usage.users.activeMonth != null) {
241
- layout[next()] +=
242
- ` ${nodeInfo.usage.users.activeMonth.toLocaleString("en-US")} ` +
243
- colors.dim("(active month)");
244
- }
245
- }
246
- if (nodeInfo.usage?.localPosts != null) {
247
- layout[next()] += colors.bold(colors.dim("Local posts: "));
248
- layout[next()] += " " +
249
- nodeInfo.usage.localPosts.toLocaleString("en-US");
250
- }
251
- if (nodeInfo.usage?.localComments != null) {
252
- layout[next()] += colors.bold(colors.dim("Local comments:"));
253
- layout[next()] += " " +
254
- nodeInfo.usage.localComments.toLocaleString("en-US");
255
- }
256
- if (nodeInfo.openRegistrations != null) {
257
- layout[next()] += colors.bold(colors.dim("Open registrations:"));
258
- layout[next()] += " " + (nodeInfo.openRegistrations ? "Yes" : "No");
259
- }
260
-
261
- if (
262
- "metadata" in command && command.metadata && nodeInfo.metadata != null &&
263
- Object.keys(nodeInfo.metadata).length > 0
264
- ) {
265
- layout[next()] += colors.bold(colors.dim("Metadata:"));
266
- for (const [key, value] of Object.entries(nodeInfo.metadata)) {
267
- layout[next()] += ` ${colors.dim(key + ":")} ${
268
- indent(
269
- typeof value === "string" ? value : formatObject(value),
270
- defaultWidth + 4 + key.length,
271
- )
272
- }`;
273
- }
274
- }
275
-
276
- console.log(layout.join("\n"));
277
- }
278
-
279
- function indent(text: string, depth: number) {
280
- return text.replace(/\n/g, "\n" + " ".repeat(depth));
281
- }
282
-
283
- const LINK_REGEXP =
284
- /<link((?:\s+(?:[-a-z]+)=(?:"[^"]*"|'[^']*'|[^\s]+))*)\s*\/?>/ig;
285
- const LINK_ATTRS_REGEXP = /(?:\s+([-a-z]+)=("[^"]*"|'[^']*'|[^\s]+))/ig;
286
-
287
- export async function getFaviconUrl(
288
- url: string | URL,
289
- userAgent?: string,
290
- ): Promise<URL> {
291
- const response = await fetch(url, {
292
- headers: {
293
- "User-Agent": userAgent == null ? getUserAgent() : userAgent,
294
- },
295
- });
296
- const text = await response.text();
297
- for (const match of text.matchAll(LINK_REGEXP)) {
298
- const attrs: Record<string, string> = {};
299
- for (const attrMatch of match[1].matchAll(LINK_ATTRS_REGEXP)) {
300
- const [, key, value] = attrMatch;
301
- attrs[key] = value.startsWith('"') || value.startsWith("'")
302
- ? value.slice(1, -1)
303
- : value;
304
- }
305
- const rel = attrs.rel?.toLowerCase()?.trim()?.split(/\s+/) ?? [];
306
- if (!rel.includes("icon") && !rel.includes("apple-touch-icon")) continue;
307
- if ("sizes" in attrs && attrs.sizes.match(/\d+x\d+/)) {
308
- const [w, h] = attrs.sizes.split("x").map((v) => Number.parseInt(v));
309
- if (w < 38 || h < 19) continue;
310
- }
311
- if ("href" in attrs) {
312
- if (attrs.href.endsWith(".svg")) continue;
313
- return new URL(attrs.href, response.url);
314
- }
315
- }
316
- return new URL("/favicon.ico", response.url);
317
- }
318
-
319
- function checkTerminalColorSupport(): "truecolor" | "256color" | "none" {
320
- // Check if colors are explicitly disabled
321
- const noColor = process.env.NO_COLOR;
322
- if (noColor != null && noColor !== "") {
323
- return "none";
324
- }
325
-
326
- // Check for true color (24-bit) support
327
- const colorTerm = process.env.COLORTERM;
328
- if (
329
- colorTerm != null &&
330
- (colorTerm.includes("24bit") || colorTerm.includes("truecolor"))
331
- ) {
332
- return "truecolor";
333
- }
334
-
335
- // Check for xterm 256-color support
336
- const term = process.env.TERM;
337
- if (
338
- term != null &&
339
- (term.includes("256color") ||
340
- term.includes("xterm") ||
341
- term === "screen" ||
342
- term === "tmux")
343
- ) {
344
- return "256color";
345
- }
346
-
347
- // Fallback: assume basic color support if TERM is set
348
- if (term != null && term !== "dumb") {
349
- return "256color";
350
- }
351
-
352
- // Check for Windows Terminal support
353
- // FIXME: WT_SESSION is not a reliable way to check for Windows Terminal support
354
- const isWindows = os.platform() === "win32";
355
- const isWT = process.env.WT_SESSION;
356
- if (isWindows && isWT != null && isWT !== "") {
357
- return "truecolor";
358
- }
359
-
360
- return "none";
361
- }
362
-
363
- const DEFAULT_IMAGE_WIDTH = 38;
364
-
365
- const ASCII_CHARS =
366
- // cSpell: disable
367
- "█▓▒░@#B8&WM%*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. ";
368
- // cSpell: enable
369
-
370
- const CUBE_VALUES = [0, 95, 135, 175, 215, 255];
371
-
372
- const findClosestIndex = (value: number): number => {
373
- let minDiff = Infinity;
374
- let closestIndex = 0;
375
- for (let idx = 0; idx < CUBE_VALUES.length; idx++) {
376
- const diff = Math.abs(value - CUBE_VALUES[idx]);
377
- if (diff < minDiff) {
378
- minDiff = diff;
379
- closestIndex = idx;
380
- }
381
- }
382
- return closestIndex;
383
- };
384
-
385
- export function rgbTo256Color(r: number, g: number, b: number): number {
386
- // Check if it's a grayscale color first (when all RGB values are very close)
387
- const gray = Math.round((r + g + b) / 3);
388
- const isGrayscale = Math.abs(r - gray) <= 5 && Math.abs(g - gray) <= 5 &&
389
- Math.abs(b - gray) <= 5;
390
-
391
- // Handle grayscale colors (colors 232-255) - but exclude exact cube values
392
- if (isGrayscale) {
393
- const isExactCubeValue = CUBE_VALUES.includes(r) && r === g && g === b;
394
-
395
- if (!isExactCubeValue) {
396
- if (gray < 8) return 232; // Darkest grayscale
397
- if (gray > 238) return 255; // Brightest grayscale
398
-
399
- // Map to grayscale range 232-255 (24 levels)
400
- // XTerm grayscale: 8, 18, 28, ..., 238 maps to 232, 233, 234, ..., 255
401
- const grayIndex = Math.round((gray - 8) / 10);
402
- return Math.max(232, Math.min(255, 232 + grayIndex));
403
- }
404
- }
405
-
406
- // Handle RGB colors (colors 16-231)
407
- // XTerm 256 color cube values: [0, 95, 135, 175, 215, 255]
408
-
409
- const r6 = findClosestIndex(r);
410
- const g6 = findClosestIndex(g);
411
- const b6 = findClosestIndex(b);
412
-
413
- return 16 + (36 * r6) + (6 * g6) + b6;
414
- }
415
-
416
- export function getAsciiArt(
417
- image: Awaited<ReturnType<typeof Jimp.read>>,
418
- width = DEFAULT_IMAGE_WIDTH,
419
- colorSupport: "truecolor" | "256color" | "none",
420
- colors: ChalkInstance,
421
- ): string {
422
- const ratio = image.width / image.height;
423
- const height = Math.round(
424
- width / ratio * 0.5, // Multiply by 0.5 because characters are taller than they are wide.
425
- );
426
- image.resize({ w: width, h: height });
427
- let art = "";
428
- for (let y = 0; y < height; y++) {
429
- for (let x = 0; x < width; x++) {
430
- const pixel = image.getPixelColor(x, y);
431
- const color = intToRGBA(pixel);
432
- if (color.a < 1) {
433
- art += " ";
434
- continue;
435
- }
436
- const brightness = (color.r + color.g + color.b) / 3;
437
- const charIndex = Math.round(
438
- (brightness / 255) * (ASCII_CHARS.length - 1),
439
- );
440
- const char = ASCII_CHARS[charIndex];
441
-
442
- if (colorSupport === "truecolor") {
443
- art += colors.rgb(color.r, color.g, color.b)(char);
444
- } else if (colorSupport === "256color") {
445
- const colorIndex = rgbTo256Color(color.r, color.g, color.b);
446
- art += colors.ansi256(colorIndex)(char);
447
- } else {
448
- art += char;
449
- }
450
- }
451
- if (y < height - 1) art += "\n";
452
- }
453
- return art;
454
- }
package/src/table.ts DELETED
@@ -1,17 +0,0 @@
1
- export const tableStyle = {
2
- top: "─",
3
- topMid: "┬",
4
- topLeft: "╭",
5
- topRight: "╮",
6
- bottom: "─",
7
- bottomMid: "┴",
8
- bottomLeft: "╰",
9
- bottomRight: "╯",
10
- left: "│",
11
- leftMid: "├",
12
- mid: "─",
13
- midMid: "┼",
14
- right: "│",
15
- rightMid: "┤",
16
- middle: "│",
17
- };