@anaemia/cli 0.3.7 → 0.5.0

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.
package/LICENSE CHANGED
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright [yyyy] [name of copyright owner]
189
+ Copyright [2026] [colourlabs]
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -1,3 +1,3 @@
1
1
  # @anaemia/cli
2
2
 
3
- the CLI for the [anaemia](https://github.com/colourlabs/anaemia) SolidJS SSR framework
3
+ the CLI for the [anaemia](https://github.com/colourlabs/anaemia) SolidJS SSR framework
@@ -61,14 +61,12 @@ export function register(cli) {
61
61
  serverProcess = null;
62
62
  }
63
63
  bridgeServer.close();
64
- serverCompiler?.close(() => { });
65
- if (devServer) {
66
- await devServer.stop();
67
- }
64
+ serverCompiler.close(() => { });
65
+ await devServer.stop();
68
66
  process.exit(0);
69
67
  };
70
- process.on("SIGINT", cleanup);
71
- process.on("SIGTERM", cleanup);
68
+ process.on("SIGINT", () => void cleanup());
69
+ process.on("SIGTERM", () => void cleanup());
72
70
  logger.compiler("warming up and analyzing assets...");
73
71
  try {
74
72
  await devServer.start();
@@ -6,11 +6,15 @@ export function register(cli) {
6
6
  const appRoot = process.cwd();
7
7
  const routes = await scanRoutes(appRoot);
8
8
  logger.info("scanned route architecture:\n");
9
- routes.forEach((r, i) => {
9
+ for (const [i, r] of routes.entries()) {
10
10
  const isLast = i === routes.length - 1;
11
11
  const branch = isLast ? "└─" : "├─";
12
12
  const indent = isLast ? " " : "│ ";
13
- const typeLabel = r.type === "catch-all" ? pc.red(`[${r.type}]`) : r.type === "layout" ? pc.yellow(`[${r.type}]`) : pc.green(`[${r.type}]`);
13
+ const typeLabel = r.type === "catch-all"
14
+ ? pc.red(`[${r.type}]`)
15
+ : r.type === "layout"
16
+ ? pc.yellow(`[${r.type}]`)
17
+ : pc.green(`[${r.type}]`);
14
18
  console.log(`${branch} ${pc.cyan(r.urlPattern)} ${typeLabel}`);
15
19
  const lines = [];
16
20
  if (r.params.length)
@@ -19,12 +23,12 @@ export function register(cli) {
19
23
  lines.push(`chunk: ${pc.dim(r.chunkName)}`);
20
24
  if (r.layouts.length)
21
25
  lines.push(`layouts: ${pc.dim(String(r.layouts.length))}`);
22
- lines.forEach((line, li) => {
26
+ for (const [li, line] of lines.entries()) {
23
27
  const isLastLine = li === lines.length - 1;
24
28
  console.log(`${indent}${isLastLine ? "└─" : "├─"} ${line}`);
25
- });
29
+ }
26
30
  if (!isLast)
27
31
  console.log("│");
28
- });
32
+ }
29
33
  });
30
34
  }
@@ -1,6 +1,6 @@
1
1
  import { loadUserConfig } from "../utils/config.js";
2
2
  import spawn from "cross-spawn";
3
- import path from "path";
3
+ import path from "node:path";
4
4
  export function register(cli) {
5
5
  cli.command("start", "serve the production build").action(async () => {
6
6
  const appRoot = process.cwd();
package/dist/scaffold.js CHANGED
@@ -9,8 +9,13 @@ export function scaffoldFeature(rawName, appRoot) {
9
9
  const ext = isTypeScript ? "tsx" : "jsx";
10
10
  const scriptExt = isTypeScript ? "ts" : "js";
11
11
  const featureDir = path.resolve(appRoot, `./src/features/${folderName}`);
12
- const directories = [path.join(featureDir, "components"), path.join(featureDir, "hooks"), path.join(featureDir, "server")];
13
- directories.forEach((dir) => fs.mkdirSync(dir, { recursive: true }));
12
+ const directories = [
13
+ path.join(featureDir, "components"),
14
+ path.join(featureDir, "hooks"),
15
+ path.join(featureDir, "server"),
16
+ ];
17
+ for (const dir of directories)
18
+ fs.mkdirSync(dir, { recursive: true });
14
19
  const componentContent = isTypeScript
15
20
  ? `import type { JSX } from "solid-js";
16
21
  import styles from "./${componentName}.module.scss";
@@ -201,8 +206,7 @@ export function scaffoldPage(rawName, appRoot) {
201
206
  const componentName = toPascalCase(fileName
202
207
  .replace(/^\[\.\.\./, "") // strip [...
203
208
  .replace(/^\[/, "") // strip [
204
- .replace(/\]$/, "") // strip ]
205
- ) + "Page";
209
+ .replace(/\]$/, "")) + "Page";
206
210
  // derive the URL pattern for the comment header
207
211
  const urlPattern = "/" +
208
212
  segments
@@ -288,7 +292,9 @@ export function scaffoldHook(rawName, appRoot) {
288
292
  const featureName = isFeatureHook ? segments[0] : null;
289
293
  // ensure it starts with "use"
290
294
  const hookName = rawHookName.startsWith("use") ? rawHookName : `use${toPascalCase(rawHookName)}`;
291
- const hookDir = isFeatureHook ? path.resolve(appRoot, `./src/features/${toKebabCase(featureName)}/hooks`) : path.resolve(appRoot, `./src/shared/hooks`);
295
+ const hookDir = isFeatureHook
296
+ ? path.resolve(appRoot, `./src/features/${toKebabCase(featureName)}/hooks`)
297
+ : path.resolve(appRoot, `./src/shared/hooks`);
292
298
  const hookPath = path.join(hookDir, `${hookName}.${ext}`);
293
299
  if (fs.existsSync(hookPath)) {
294
300
  console.error(`[anaemia] generation halted: hook "${hookName}" already exists at ${hookPath}`);
@@ -361,7 +367,9 @@ export function ${hookName}(options) {
361
367
  }
362
368
  }
363
369
  }
364
- const location = isFeatureHook ? `src/features/${toKebabCase(featureName)}/hooks/${hookName}.${ext}` : `src/shared/hooks/${hookName}.${ext}`;
370
+ const location = isFeatureHook
371
+ ? `src/features/${toKebabCase(featureName)}/hooks/${hookName}.${ext}`
372
+ : `src/shared/hooks/${hookName}.${ext}`;
365
373
  console.log("\n🪝 successfully generated hook:");
366
374
  console.log(` └─ ${location}\n`);
367
375
  }
@@ -9,7 +9,7 @@ export function toKebabCase(str) {
9
9
  export function toPascalCase(str) {
10
10
  return toKebabCase(str)
11
11
  .split("-")
12
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
12
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
13
13
  .join("");
14
14
  }
15
15
  export function toCamelCase(str) {
@@ -1,5 +1,5 @@
1
- import path from "path";
2
- import fs from "fs";
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
3
  import { createJiti } from "jiti";
4
4
  import logger from "./logger.js";
5
5
  export async function loadUserConfig(appRoot) {
@@ -16,6 +16,7 @@ export async function fetchTemplate(targetPath) {
16
16
  extract.on("error", reject);
17
17
  const reader = res.body.getReader();
18
18
  const pump = async () => {
19
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
19
20
  while (true) {
20
21
  const { done, value } = await reader.read();
21
22
  if (done) {
@@ -21,9 +21,7 @@ export function convertTypeScriptToJs(dir) {
21
21
  jsxRuntime: "preserve",
22
22
  production: true,
23
23
  });
24
- const cleaned = compiled.code
25
- .replace(/\n{3,}/g, "\n\n")
26
- .trimStart();
24
+ const cleaned = compiled.code.replace(/\n{3,}/g, "\n\n").trimStart();
27
25
  const newPath = fullPath.replace(/\.tsx?$/, isTsx ? ".jsx" : ".js");
28
26
  fs.writeFileSync(newPath, cleaned, "utf8");
29
27
  fs.unlinkSync(fullPath);
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@anaemia/cli",
3
- "version": "0.3.7",
4
- "type": "module",
3
+ "version": "0.5.0",
5
4
  "bin": {
6
5
  "anaemia": "./dist/index.js"
7
6
  },
7
+ "type": "module",
8
8
  "dependencies": {
9
- "@anaemia/bundler": "^0.3.7",
10
- "@anaemia/core": "^0.3.7",
9
+ "@anaemia/bundler": "^0.5.0",
10
+ "@anaemia/core": "^0.5.0",
11
11
  "@rspack/core": "^2.0.5",
12
12
  "@rspack/dev-server": "2.0.1",
13
13
  "cac": "^7.0.0",
@@ -54,7 +54,9 @@ export function register(cli: CAC) {
54
54
  return;
55
55
  }
56
56
 
57
- logger.error(`unknown layout generator type "${type}". Supported variants: "feature:", "component:", "page:", "hook:"`);
57
+ logger.error(
58
+ `unknown layout generator type "${type}". Supported variants: "feature:", "component:", "page:", "hook:"`,
59
+ );
58
60
  process.exit(1);
59
61
  }
60
62
 
@@ -9,7 +9,7 @@ import { WebSocketServer } from "ws";
9
9
  import { WebSocket as NodeWS } from "ws";
10
10
  import { loadUserConfig } from "../utils/config.js";
11
11
  import logger from "../utils/logger.js";
12
- import { ChildProcess } from "node:child_process";
12
+ import type { ChildProcess } from "node:child_process";
13
13
  import { flattenWsMessage } from "../utils/flatten-ws-message.js";
14
14
 
15
15
  export function register(cli: CAC) {
@@ -75,15 +75,13 @@ export function register(cli: CAC) {
75
75
  serverProcess = null;
76
76
  }
77
77
  bridgeServer.close();
78
- serverCompiler?.close(() => {});
79
- if (devServer) {
80
- await devServer.stop();
81
- }
78
+ serverCompiler.close(() => {});
79
+ await devServer.stop();
82
80
  process.exit(0);
83
81
  };
84
82
 
85
- process.on("SIGINT", cleanup);
86
- process.on("SIGTERM", cleanup);
83
+ process.on("SIGINT", () => void cleanup());
84
+ process.on("SIGTERM", () => void cleanup());
87
85
 
88
86
  logger.compiler("warming up and analyzing assets...");
89
87
 
@@ -10,12 +10,17 @@ export function register(cli: CAC) {
10
10
 
11
11
  logger.info("scanned route architecture:\n");
12
12
 
13
- routes.forEach((r, i) => {
13
+ for (const [i, r] of routes.entries()) {
14
14
  const isLast = i === routes.length - 1;
15
15
  const branch = isLast ? "└─" : "├─";
16
16
  const indent = isLast ? " " : "│ ";
17
17
 
18
- const typeLabel = r.type === "catch-all" ? pc.red(`[${r.type}]`) : r.type === "layout" ? pc.yellow(`[${r.type}]`) : pc.green(`[${r.type}]`);
18
+ const typeLabel =
19
+ r.type === "catch-all"
20
+ ? pc.red(`[${r.type}]`)
21
+ : r.type === "layout"
22
+ ? pc.yellow(`[${r.type}]`)
23
+ : pc.green(`[${r.type}]`);
19
24
  console.log(`${branch} ${pc.cyan(r.urlPattern)} ${typeLabel}`);
20
25
 
21
26
  const lines: string[] = [];
@@ -23,12 +28,12 @@ export function register(cli: CAC) {
23
28
  if (r.chunkName) lines.push(`chunk: ${pc.dim(r.chunkName)}`);
24
29
  if (r.layouts.length) lines.push(`layouts: ${pc.dim(String(r.layouts.length))}`);
25
30
 
26
- lines.forEach((line, li) => {
31
+ for (const [li, line] of lines.entries()) {
27
32
  const isLastLine = li === lines.length - 1;
28
33
  console.log(`${indent}${isLastLine ? "└─" : "├─"} ${line}`);
29
- });
34
+ }
30
35
 
31
36
  if (!isLast) console.log("│");
32
- });
37
+ }
33
38
  });
34
39
  }
@@ -1,7 +1,7 @@
1
1
  import type { CAC } from "cac";
2
2
  import { loadUserConfig } from "../utils/config.js";
3
3
  import spawn from "cross-spawn";
4
- import path from "path";
4
+ import path from "node:path";
5
5
 
6
6
  export function register(cli: CAC) {
7
7
  cli.command("start", "serve the production build").action(async () => {
package/src/scaffold.ts CHANGED
@@ -13,9 +13,13 @@ export function scaffoldFeature(rawName: string, appRoot: string) {
13
13
 
14
14
  const featureDir = path.resolve(appRoot, `./src/features/${folderName}`);
15
15
 
16
- const directories = [path.join(featureDir, "components"), path.join(featureDir, "hooks"), path.join(featureDir, "server")];
16
+ const directories = [
17
+ path.join(featureDir, "components"),
18
+ path.join(featureDir, "hooks"),
19
+ path.join(featureDir, "server"),
20
+ ];
17
21
 
18
- directories.forEach((dir) => fs.mkdirSync(dir, { recursive: true }));
22
+ for (const dir of directories) fs.mkdirSync(dir, { recursive: true });
19
23
 
20
24
  const componentContent = isTypeScript
21
25
  ? `import type { JSX } from "solid-js";
@@ -120,7 +124,11 @@ export { use${componentName} } from "./hooks/use${componentName}.js";
120
124
 
121
125
  fs.writeFileSync(path.join(featureDir, `components/${componentName}.${ext}`), componentContent, "utf8");
122
126
 
123
- fs.writeFileSync(path.join(featureDir, `components/${componentName}.module.scss`), `.wrapper {\n display: block;\n}\n`, "utf8");
127
+ fs.writeFileSync(
128
+ path.join(featureDir, `components/${componentName}.module.scss`),
129
+ `.wrapper {\n display: block;\n}\n`,
130
+ "utf8",
131
+ );
124
132
 
125
133
  fs.writeFileSync(path.join(featureDir, `server/actions.server.${scriptExt}`), actionsContent, "utf8");
126
134
 
@@ -240,7 +248,7 @@ export function scaffoldPage(rawName: string, appRoot: string) {
240
248
  fileName
241
249
  .replace(/^\[\.\.\./, "") // strip [...
242
250
  .replace(/^\[/, "") // strip [
243
- .replace(/\]$/, "") // strip ]
251
+ .replace(/\]$/, ""), // strip ]
244
252
  ) + "Page";
245
253
 
246
254
  // derive the URL pattern for the comment header
@@ -338,7 +346,9 @@ export function scaffoldHook(rawName: string, appRoot: string) {
338
346
  // ensure it starts with "use"
339
347
  const hookName = rawHookName.startsWith("use") ? rawHookName : `use${toPascalCase(rawHookName)}`;
340
348
 
341
- const hookDir = isFeatureHook ? path.resolve(appRoot, `./src/features/${toKebabCase(featureName!)}/hooks`) : path.resolve(appRoot, `./src/shared/hooks`);
349
+ const hookDir = isFeatureHook
350
+ ? path.resolve(appRoot, `./src/features/${toKebabCase(featureName!)}/hooks`)
351
+ : path.resolve(appRoot, `./src/shared/hooks`);
342
352
 
343
353
  const hookPath = path.join(hookDir, `${hookName}.${ext}`);
344
354
 
@@ -421,7 +431,9 @@ export function ${hookName}(options) {
421
431
  }
422
432
  }
423
433
 
424
- const location = isFeatureHook ? `src/features/${toKebabCase(featureName!)}/hooks/${hookName}.${ext}` : `src/shared/hooks/${hookName}.${ext}`;
434
+ const location = isFeatureHook
435
+ ? `src/features/${toKebabCase(featureName!)}/hooks/${hookName}.${ext}`
436
+ : `src/shared/hooks/${hookName}.${ext}`;
425
437
 
426
438
  console.log("\n🪝 successfully generated hook:");
427
439
  console.log(` └─ ${location}\n`);
@@ -10,11 +10,11 @@ export function toKebabCase(str: string): string {
10
10
  export function toPascalCase(str: string): string {
11
11
  return toKebabCase(str)
12
12
  .split("-")
13
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
13
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
14
14
  .join("");
15
15
  }
16
16
 
17
17
  export function toCamelCase(str: string): string {
18
18
  const pascal = toPascalCase(str);
19
19
  return pascal.charAt(0).toLowerCase() + pascal.slice(1);
20
- }
20
+ }
@@ -1,6 +1,6 @@
1
- import { AnaemiaConfig } from "@anaemia/core/config";
2
- import path from "path";
3
- import fs from "fs";
1
+ import type { AnaemiaConfig } from "@anaemia/core/config";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
4
  import { createJiti } from "jiti";
5
5
  import logger from "./logger.js";
6
6
 
@@ -22,6 +22,7 @@ export async function fetchTemplate(targetPath: string): Promise<void> {
22
22
  const reader = res.body!.getReader();
23
23
 
24
24
  const pump = async () => {
25
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
25
26
  while (true) {
26
27
  const { done, value } = await reader.read();
27
28
  if (done) {
@@ -3,4 +3,4 @@ export function flattenWsMessage(data: Buffer | Buffer[] | ArrayBuffer | string)
3
3
  if (Buffer.isBuffer(data)) return data.toString("utf-8");
4
4
  if (data instanceof ArrayBuffer) return Buffer.from(data).toString("utf-8");
5
5
  return String(data);
6
- }
6
+ }
@@ -27,9 +27,7 @@ export function convertTypeScriptToJs(dir: string): void {
27
27
  production: true,
28
28
  });
29
29
 
30
- const cleaned = compiled.code
31
- .replace(/\n{3,}/g, "\n\n")
32
- .trimStart();
30
+ const cleaned = compiled.code.replace(/\n{3,}/g, "\n\n").trimStart();
33
31
 
34
32
  const newPath = fullPath.replace(/\.tsx?$/, isTsx ? ".jsx" : ".js");
35
33
  fs.writeFileSync(newPath, cleaned, "utf8");
@@ -43,4 +41,4 @@ export function convertTypeScriptToJs(dir: string): void {
43
41
  if (fs.existsSync(tsconfigPath)) {
44
42
  fs.unlinkSync(tsconfigPath);
45
43
  }
46
- }
44
+ }
@@ -28,7 +28,10 @@ test("convertTypeScriptToJs strips types from .ts files", async () => {
28
28
  test("convertTypeScriptToJs strips types from .tsx files", async () => {
29
29
  const dir = createTmpDir();
30
30
  try {
31
- fs.writeFileSync(path.join(dir, "Component.tsx"), `export default function Comp(props: { name: string }) {\n return <div>{props.name}</div>;\n}\n`);
31
+ fs.writeFileSync(
32
+ path.join(dir, "Component.tsx"),
33
+ `export default function Comp(props: { name: string }) {\n return <div>{props.name}</div>;\n}\n`,
34
+ );
32
35
 
33
36
  const { convertTypeScriptToJs } = await import("../../dist/utils/ts-to-js.js");
34
37
  convertTypeScriptToJs(dir);
@@ -45,7 +48,10 @@ test("convertTypeScriptToJs strips types from .tsx files", async () => {
45
48
  test("convertTypeScriptToJs cleans up leading blank lines", async () => {
46
49
  const dir = createTmpDir();
47
50
  try {
48
- fs.writeFileSync(path.join(dir, "root.tsx"), `import { JSX } from "solid-js";\n\nexport default function Root(props: { children: JSX.Element }) {\n return <>{props.children}</>;\n}\n`);
51
+ fs.writeFileSync(
52
+ path.join(dir, "root.tsx"),
53
+ `import { JSX } from "solid-js";\n\nexport default function Root(props: { children: JSX.Element }) {\n return <>{props.children}</>;\n}\n`,
54
+ );
49
55
 
50
56
  const { convertTypeScriptToJs } = await import("../../dist/utils/ts-to-js.js");
51
57
  convertTypeScriptToJs(dir);
@@ -70,4 +76,4 @@ test("convertTypeScriptToJs removes tsconfig.json", async () => {
70
76
  } finally {
71
77
  fs.rmSync(dir, { recursive: true, force: true });
72
78
  }
73
- });
79
+ });
@@ -14,4 +14,4 @@ test("flattenWsMessage handles a single Buffer", () => {
14
14
  test("flattenWsMessage concatenates a Buffer array", () => {
15
15
  const bufs = [Buffer.from("hel"), Buffer.from("lo")];
16
16
  assert.equal(flattenWsMessage(bufs), "hello");
17
- });
17
+ });
@@ -20,4 +20,4 @@ test("fetchTemplate extracts template contents directly into target directory",
20
20
  } finally {
21
21
  fs.rmSync(targetDir, { recursive: true, force: true });
22
22
  }
23
- });
23
+ });
package/tsconfig.json CHANGED
@@ -7,4 +7,4 @@
7
7
  "paths": {}
8
8
  },
9
9
  "include": ["src/**/*"]
10
- }
10
+ }