@anaemia/cli 0.0.1 → 0.1.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.
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) SSR framework
3
+ the CLI for the [anaemia](https://github.com/colourlabs/anaemia) SolidJS SSR framework
package/dist/index.js CHANGED
@@ -41,13 +41,12 @@ const logger = {
41
41
  const cli = cac("anaemia");
42
42
  async function loadUserConfig(appRoot) {
43
43
  const configPath = path.resolve(appRoot, "anaemia.config.ts");
44
- if (!fs.existsSync(configPath)) {
44
+ if (!fs.existsSync(configPath))
45
45
  return {};
46
- }
47
46
  try {
48
47
  const jiti = createJiti(import.meta.url);
49
48
  const module = (await jiti.import(configPath));
50
- return module.default || module;
49
+ return (module.default ?? module);
51
50
  }
52
51
  catch (err) {
53
52
  logger.error("failed parsing your anaemia.config.ts file:", err);
@@ -316,7 +315,7 @@ cli
316
315
  fs.writeFileSync(newPath, compiled.code, "utf8");
317
316
  fs.unlinkSync(fullPath);
318
317
  }
319
- catch (err) {
318
+ catch {
320
319
  logger.warn(`failed to strip types from ${file}, skipping...`);
321
320
  }
322
321
  }
package/dist/scaffold.js CHANGED
@@ -4,6 +4,7 @@ import { toCamelCase, toKebabCase, toPascalCase } from "./utils/casing.js";
4
4
  export function scaffoldFeature(rawName, appRoot) {
5
5
  const folderName = toKebabCase(rawName);
6
6
  const componentName = toPascalCase(rawName);
7
+ const camelName = toCamelCase(rawName);
7
8
  const isTypeScript = fs.existsSync(path.join(appRoot, "tsconfig.json"));
8
9
  const ext = isTypeScript ? "tsx" : "jsx";
9
10
  const scriptExt = isTypeScript ? "ts" : "js";
@@ -11,110 +12,105 @@ export function scaffoldFeature(rawName, appRoot) {
11
12
  const directories = [path.join(featureDir, "components"), path.join(featureDir, "hooks"), path.join(featureDir, "server")];
12
13
  directories.forEach((dir) => fs.mkdirSync(dir, { recursive: true }));
13
14
  const componentContent = isTypeScript
14
- ? `import { children, JSX } from "solid-js";
15
+ ? `import type { JSX } from "solid-js";
15
16
  import styles from "./${componentName}.module.scss";
16
17
 
18
+ // data flows in from the route loader via props.
19
+ // in your route file:
20
+ //
21
+ // import { ${camelName}Action } from "@features/${folderName}/index.js";
22
+ //
23
+ // export const loader = async () => await ${camelName}Action(undefined);
24
+ //
25
+ // export default function Page() {
26
+ // const data = useRouteData();
27
+ // return <${componentName} data={data()} />;
28
+ // }
29
+
17
30
  interface ${componentName}Props {
18
31
  children?: JSX.Element;
32
+ data?: unknown;
19
33
  }
20
34
 
21
35
  export function ${componentName}(props: ${componentName}Props) {
22
- const resolved = children(() => props.children);
23
-
24
36
  return (
25
37
  <div class={styles.wrapper}>
26
- Welcome to ${componentName}
27
- {resolved()}
38
+ {props.children}
28
39
  </div>
29
40
  );
30
41
  }
31
42
  `
32
- : `import { children } from "solid-js";
33
- import styles from "./${componentName}.module.scss";
43
+ : `import styles from "./${componentName}.module.scss";
34
44
 
35
45
  export function ${componentName}(props) {
36
- const resolved = children(() => props.children);
37
-
38
46
  return (
39
47
  <div class={styles.wrapper}>
40
- Welcome to ${componentName}
41
- {resolved()}
48
+ {props.children}
42
49
  </div>
43
50
  );
44
51
  }
45
52
  `;
46
53
  const actionsContent = isTypeScript
47
- ? `import { runOnServer } from "@anaemia/core";
54
+ ? `// raw server-side logic DO NOT import UI code here
55
+ // these functions run exclusively on the server
48
56
 
49
- export const ${toCamelCase(componentName)}Action = runOnServer(async (input: unknown) => {
57
+ export async function ${camelName}Query(input: unknown) {
50
58
  // TODO: implement server-side logic
51
59
  return { ok: true };
52
- });
60
+ }
53
61
  `
54
- : `import { runOnServer } from "@anaemia/core";
62
+ : `// raw server-side logic DO NOT import UI code here
63
+ // these functions run exclusively on the server
55
64
 
56
- export const ${toCamelCase(componentName)}Action = runOnServer(async (input) => {
65
+ export async function ${camelName}Query(input) {
57
66
  // TODO: implement server-side logic
58
67
  return { ok: true };
59
- });
68
+ }
60
69
  `;
61
70
  const hookContent = isTypeScript
62
- ? `import { createSignal } from "solid-js";
63
- import { ${toCamelCase(componentName)}Action } from "../server/actions";
71
+ ? `import { createServerResource } from "@anaemia/core";
72
+ import { ${camelName}Action } from "../index.js";
64
73
 
65
74
  export function use${componentName}() {
66
- const [data, setData] = createSignal<unknown>(null);
67
- const [error, setError] = createSignal<string | null>(null);
68
- const [loading, setLoading] = createSignal(false);
69
-
70
- const execute = async (input: unknown) => {
71
- setLoading(true);
72
- setError(null);
73
- try {
74
- const result = await ${toCamelCase(componentName)}Action(input);
75
- setData(result);
76
- return result;
77
- } catch (err: any) {
78
- setError(err.message ?? "Unknown error");
79
- } finally {
80
- setLoading(false);
81
- }
82
- };
75
+ const [data] = createServerResource(() => undefined, ${camelName}Action);
83
76
 
84
- return { data, error, loading, execute };
77
+ return { data };
85
78
  }
86
79
  `
87
- : `import { createSignal } from "solid-js";
88
- import { ${toCamelCase(componentName)}Action } from "../server/actions";
80
+ : `import { createServerResource } from "@anaemia/core";
81
+ import { ${camelName}Action } from "../index.js";
89
82
 
90
83
  export function use${componentName}() {
91
- const [data, setData] = createSignal(null);
92
- const [error, setError] = createSignal(null);
93
- const [loading, setLoading] = createSignal(false);
94
-
95
- const execute = async (input) => {
96
- setLoading(true);
97
- setError(null);
98
- try {
99
- const result = await ${toCamelCase(componentName)}Action(input);
100
- setData(result);
101
- return result;
102
- } catch (err) {
103
- setError(err.message ?? "Unknown error");
104
- } finally {
105
- setLoading(false);
106
- }
107
- };
84
+ const [data] = createServerResource(() => undefined, ${camelName}Action);
108
85
 
109
- return { data, error, loading, execute };
86
+ return { data };
110
87
  }
111
88
  `;
112
- const indexContent = `export { ${componentName} } from "./components/${componentName}";
113
- export { use${componentName} } from "./hooks/use${componentName}";
89
+ const indexContent = isTypeScript
90
+ ? `import { runOnServer } from "@anaemia/core";
91
+ import { ${camelName}Query } from "./server/actions.server.js";
92
+
93
+ // runOnServer wrappers — import these in your route loaders
94
+ export const ${camelName}Action = runOnServer(async (input: unknown) => {
95
+ return await ${camelName}Query(input);
96
+ });
97
+
98
+ export { ${componentName} } from "./components/${componentName}.js";
99
+ export { use${componentName} } from "./hooks/use${componentName}.js";
100
+ `
101
+ : `import { runOnServer } from "@anaemia/core";
102
+ import { ${camelName}Query } from "./server/actions.server.js";
103
+
104
+ export const ${camelName}Action = runOnServer(async (input) => {
105
+ return await ${camelName}Query(input);
106
+ });
107
+
108
+ export { ${componentName} } from "./components/${componentName}.js";
109
+ export { use${componentName} } from "./hooks/use${componentName}.js";
114
110
  `;
115
111
  fs.writeFileSync(path.join(featureDir, `components/${componentName}.${ext}`), componentContent, "utf8");
116
112
  fs.writeFileSync(path.join(featureDir, `components/${componentName}.module.scss`), `.wrapper {\n display: block;\n}\n`, "utf8");
117
- fs.writeFileSync(path.join(featureDir, `server/actions.${scriptExt}`), actionsContent, "utf8");
113
+ fs.writeFileSync(path.join(featureDir, `server/actions.server.${scriptExt}`), actionsContent, "utf8");
118
114
  fs.writeFileSync(path.join(featureDir, `hooks/use${componentName}.${scriptExt}`), hookContent, "utf8");
119
115
  fs.writeFileSync(path.join(featureDir, `index.${scriptExt}`), indexContent, "utf8");
120
116
  console.log("\n🎯 successfully generated feature domain template structure:");
@@ -123,8 +119,10 @@ export { use${componentName} } from "./hooks/use${componentName}";
123
119
  console.log(` │ ├── ${componentName}.${ext}`);
124
120
  console.log(` │ └── ${componentName}.module.scss`);
125
121
  console.log(` ├── hooks/`);
126
- console.log(` └── server/`);
127
- console.log(` └── actions.${scriptExt}\n`);
122
+ console.log(` └── use${componentName}.${scriptExt}`);
123
+ console.log(` ├── server/`);
124
+ console.log(` │ └── actions.server.${scriptExt}`);
125
+ console.log(` └── index.${scriptExt}\n`);
128
126
  }
129
127
  export function generateSharedComponent(appRoot, componentName, { logger, pc }) {
130
128
  const kebabFolder = toKebabCase(componentName);
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
1
  {
2
2
  "name": "@anaemia/cli",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "anaemia": "./dist/index.js"
7
7
  },
8
8
  "dependencies": {
9
- "@rspack/core": "^2.0.4",
9
+ "@rspack/core": "^2.0.5",
10
+ "@rspack/dev-server": "2.0.1",
10
11
  "cac": "^7.0.0",
11
12
  "cross-spawn": "^7.0.6",
12
13
  "jiti": "^2.7.0",
13
14
  "picocolors": "^1.1.1",
14
15
  "prompts": "^2.4.2",
15
16
  "sucrase": "^3.35.1",
17
+ "fs-extra": "^11.3.5",
16
18
  "ws": "^8.21.0",
17
- "@anaemia/bundler": "0.0.1",
18
- "@anaemia/core": "0.0.1"
19
+ "@anaemia/bundler": "0.1.0",
20
+ "@anaemia/core": "0.1.0"
19
21
  },
20
22
  "devDependencies": {
21
- "@rspack/dev-server": "^2.0.1",
22
23
  "@types/cross-spawn": "^6.0.6",
23
24
  "@types/fs-extra": "^11.0.4",
24
25
  "@types/node": "^25.9.1",
25
26
  "@types/prompts": "^2.4.9",
26
- "@types/ws": "^8.18.1",
27
- "fs-extra": "^11.3.5"
27
+ "@types/ws": "^8.18.1"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "tsc",
package/src/index.ts CHANGED
@@ -17,6 +17,7 @@ import { transform } from "sucrase";
17
17
  import { WebSocketServer } from "ws";
18
18
  import { WebSocket as NodeWS } from "ws";
19
19
  import http from "node:http";
20
+ import { AnaemiaConfig } from "@anaemia/core/config";
20
21
 
21
22
  const __filename = fileURLToPath(import.meta.url);
22
23
  const __dirname = path.dirname(__filename);
@@ -33,7 +34,7 @@ const logger = {
33
34
  warn(msg: string) {
34
35
  console.log(`${this.prefix} ${pc.yellow(msg)}`);
35
36
  },
36
- error(msg: string, detail?: any) {
37
+ error(msg: string, detail?: unknown) {
37
38
  console.error(`${this.prefix} ${pc.red(msg)}`);
38
39
  if (detail) console.error(detail);
39
40
  },
@@ -44,17 +45,19 @@ const logger = {
44
45
 
45
46
  const cli = cac("anaemia");
46
47
 
47
- async function loadUserConfig(appRoot: string) {
48
- const configPath = path.resolve(appRoot, "anaemia.config.ts");
48
+ interface UserConfigModule {
49
+ default?: AnaemiaConfig;
50
+ [key: string]: unknown;
51
+ }
49
52
 
50
- if (!fs.existsSync(configPath)) {
51
- return {};
52
- }
53
+ async function loadUserConfig(appRoot: string): Promise<AnaemiaConfig> {
54
+ const configPath = path.resolve(appRoot, "anaemia.config.ts");
55
+ if (!fs.existsSync(configPath)) return {};
53
56
 
54
57
  try {
55
58
  const jiti = createJiti(import.meta.url);
56
- const module = (await jiti.import(configPath)) as any;
57
- return module.default || module;
59
+ const module = (await jiti.import(configPath)) as UserConfigModule;
60
+ return (module.default ?? module) as AnaemiaConfig;
58
61
  } catch (err) {
59
62
  logger.error("failed parsing your anaemia.config.ts file:", err);
60
63
  return {};
@@ -375,7 +378,7 @@ cli
375
378
 
376
379
  fs.writeFileSync(newPath, compiled.code, "utf8");
377
380
  fs.unlinkSync(fullPath);
378
- } catch (err) {
381
+ } catch {
379
382
  logger.warn(`failed to strip types from ${file}, skipping...`);
380
383
  }
381
384
  }
package/src/scaffold.ts CHANGED
@@ -5,6 +5,7 @@ import { toCamelCase, toKebabCase, toPascalCase } from "./utils/casing.js";
5
5
  export function scaffoldFeature(rawName: string, appRoot: string) {
6
6
  const folderName = toKebabCase(rawName);
7
7
  const componentName = toPascalCase(rawName);
8
+ const camelName = toCamelCase(rawName);
8
9
 
9
10
  const isTypeScript = fs.existsSync(path.join(appRoot, "tsconfig.json"));
10
11
  const ext = isTypeScript ? "tsx" : "jsx";
@@ -17,116 +18,111 @@ export function scaffoldFeature(rawName: string, appRoot: string) {
17
18
  directories.forEach((dir) => fs.mkdirSync(dir, { recursive: true }));
18
19
 
19
20
  const componentContent = isTypeScript
20
- ? `import { children, JSX } from "solid-js";
21
+ ? `import type { JSX } from "solid-js";
21
22
  import styles from "./${componentName}.module.scss";
22
23
 
24
+ // data flows in from the route loader via props.
25
+ // in your route file:
26
+ //
27
+ // import { ${camelName}Action } from "@features/${folderName}/index.js";
28
+ //
29
+ // export const loader = async () => await ${camelName}Action(undefined);
30
+ //
31
+ // export default function Page() {
32
+ // const data = useRouteData();
33
+ // return <${componentName} data={data()} />;
34
+ // }
35
+
23
36
  interface ${componentName}Props {
24
37
  children?: JSX.Element;
38
+ data?: unknown;
25
39
  }
26
40
 
27
41
  export function ${componentName}(props: ${componentName}Props) {
28
- const resolved = children(() => props.children);
29
-
30
42
  return (
31
43
  <div class={styles.wrapper}>
32
- Welcome to ${componentName}
33
- {resolved()}
44
+ {props.children}
34
45
  </div>
35
46
  );
36
47
  }
37
48
  `
38
- : `import { children } from "solid-js";
39
- import styles from "./${componentName}.module.scss";
49
+ : `import styles from "./${componentName}.module.scss";
40
50
 
41
51
  export function ${componentName}(props) {
42
- const resolved = children(() => props.children);
43
-
44
52
  return (
45
53
  <div class={styles.wrapper}>
46
- Welcome to ${componentName}
47
- {resolved()}
54
+ {props.children}
48
55
  </div>
49
56
  );
50
57
  }
51
58
  `;
52
59
 
53
60
  const actionsContent = isTypeScript
54
- ? `import { runOnServer } from "@anaemia/core";
61
+ ? `// raw server-side logic DO NOT import UI code here
62
+ // these functions run exclusively on the server
55
63
 
56
- export const ${toCamelCase(componentName)}Action = runOnServer(async (input: unknown) => {
64
+ export async function ${camelName}Query(input: unknown) {
57
65
  // TODO: implement server-side logic
58
66
  return { ok: true };
59
- });
67
+ }
60
68
  `
61
- : `import { runOnServer } from "@anaemia/core";
69
+ : `// raw server-side logic DO NOT import UI code here
70
+ // these functions run exclusively on the server
62
71
 
63
- export const ${toCamelCase(componentName)}Action = runOnServer(async (input) => {
72
+ export async function ${camelName}Query(input) {
64
73
  // TODO: implement server-side logic
65
74
  return { ok: true };
66
- });
75
+ }
67
76
  `;
68
77
 
69
78
  const hookContent = isTypeScript
70
- ? `import { createSignal } from "solid-js";
71
- import { ${toCamelCase(componentName)}Action } from "../server/actions";
79
+ ? `import { createServerResource } from "@anaemia/core";
80
+ import { ${camelName}Action } from "../index.js";
72
81
 
73
82
  export function use${componentName}() {
74
- const [data, setData] = createSignal<unknown>(null);
75
- const [error, setError] = createSignal<string | null>(null);
76
- const [loading, setLoading] = createSignal(false);
77
-
78
- const execute = async (input: unknown) => {
79
- setLoading(true);
80
- setError(null);
81
- try {
82
- const result = await ${toCamelCase(componentName)}Action(input);
83
- setData(result);
84
- return result;
85
- } catch (err: any) {
86
- setError(err.message ?? "Unknown error");
87
- } finally {
88
- setLoading(false);
89
- }
90
- };
83
+ const [data] = createServerResource(() => undefined, ${camelName}Action);
91
84
 
92
- return { data, error, loading, execute };
85
+ return { data };
93
86
  }
94
87
  `
95
- : `import { createSignal } from "solid-js";
96
- import { ${toCamelCase(componentName)}Action } from "../server/actions";
88
+ : `import { createServerResource } from "@anaemia/core";
89
+ import { ${camelName}Action } from "../index.js";
97
90
 
98
91
  export function use${componentName}() {
99
- const [data, setData] = createSignal(null);
100
- const [error, setError] = createSignal(null);
101
- const [loading, setLoading] = createSignal(false);
102
-
103
- const execute = async (input) => {
104
- setLoading(true);
105
- setError(null);
106
- try {
107
- const result = await ${toCamelCase(componentName)}Action(input);
108
- setData(result);
109
- return result;
110
- } catch (err) {
111
- setError(err.message ?? "Unknown error");
112
- } finally {
113
- setLoading(false);
114
- }
115
- };
92
+ const [data] = createServerResource(() => undefined, ${camelName}Action);
116
93
 
117
- return { data, error, loading, execute };
94
+ return { data };
118
95
  }
119
96
  `;
120
97
 
121
- const indexContent = `export { ${componentName} } from "./components/${componentName}";
122
- export { use${componentName} } from "./hooks/use${componentName}";
98
+ const indexContent = isTypeScript
99
+ ? `import { runOnServer } from "@anaemia/core";
100
+ import { ${camelName}Query } from "./server/actions.server.js";
101
+
102
+ // runOnServer wrappers — import these in your route loaders
103
+ export const ${camelName}Action = runOnServer(async (input: unknown) => {
104
+ return await ${camelName}Query(input);
105
+ });
106
+
107
+ export { ${componentName} } from "./components/${componentName}.js";
108
+ export { use${componentName} } from "./hooks/use${componentName}.js";
109
+ `
110
+ : `import { runOnServer } from "@anaemia/core";
111
+ import { ${camelName}Query } from "./server/actions.server.js";
112
+
113
+ export const ${camelName}Action = runOnServer(async (input) => {
114
+ return await ${camelName}Query(input);
115
+ });
116
+
117
+ export { ${componentName} } from "./components/${componentName}.js";
118
+ export { use${componentName} } from "./hooks/use${componentName}.js";
123
119
  `;
124
120
 
125
121
  fs.writeFileSync(path.join(featureDir, `components/${componentName}.${ext}`), componentContent, "utf8");
126
122
 
127
123
  fs.writeFileSync(path.join(featureDir, `components/${componentName}.module.scss`), `.wrapper {\n display: block;\n}\n`, "utf8");
128
124
 
129
- fs.writeFileSync(path.join(featureDir, `server/actions.${scriptExt}`), actionsContent, "utf8");
125
+ fs.writeFileSync(path.join(featureDir, `server/actions.server.${scriptExt}`), actionsContent, "utf8");
130
126
 
131
127
  fs.writeFileSync(path.join(featureDir, `hooks/use${componentName}.${scriptExt}`), hookContent, "utf8");
132
128
 
@@ -138,8 +134,10 @@ export { use${componentName} } from "./hooks/use${componentName}";
138
134
  console.log(` │ ├── ${componentName}.${ext}`);
139
135
  console.log(` │ └── ${componentName}.module.scss`);
140
136
  console.log(` ├── hooks/`);
141
- console.log(` └── server/`);
142
- console.log(` └── actions.${scriptExt}\n`);
137
+ console.log(` └── use${componentName}.${scriptExt}`);
138
+ console.log(` ├── server/`);
139
+ console.log(` │ └── actions.server.${scriptExt}`);
140
+ console.log(` └── index.${scriptExt}\n`);
143
141
  }
144
142
 
145
143
  interface GeneratorOptions {
@@ -0,0 +1,15 @@
1
+ // fuck you typescript
2
+
3
+ declare module "@rspack/dev-server" {
4
+ import type { Compiler, MultiCompiler, DevServer } from "@rspack/core";
5
+
6
+ export type DevServerConfiguration = DevServer;
7
+
8
+ export class RspackDevServer {
9
+ constructor(config: DevServer, compiler: Compiler | MultiCompiler);
10
+ start(): Promise<void>;
11
+ stop(): Promise<void>;
12
+ }
13
+
14
+ export default RspackDevServer;
15
+ }
package/tsconfig.json CHANGED
@@ -3,7 +3,6 @@
3
3
  "compilerOptions": {
4
4
  "outDir": "./dist",
5
5
  "rootDir": "./src",
6
- "types": ["node"],
7
6
  "noEmit": false,
8
7
  "paths": {}
9
8
  },
@@ -1,6 +0,0 @@
1
- {
2
- "routes": [],
3
- "errors": {},
4
- "chunks": {},
5
- "buildTime": "2026-05-28T16:49:33.968Z"
6
- }