ai-forge-cli 0.2.2 → 0.2.3

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.
@@ -9,7 +9,7 @@ import {
9
9
  logger,
10
10
  readFile,
11
11
  writeFile
12
- } from "./chunk-J4V5PGVT.js";
12
+ } from "./chunk-HL4K5AHI.js";
13
13
 
14
14
  // src/commands/add-feature.ts
15
15
  import { defineCommand } from "citty";
@@ -3,7 +3,7 @@ import {
3
3
  fileExists,
4
4
  logger,
5
5
  readFile
6
- } from "./chunk-J4V5PGVT.js";
6
+ } from "./chunk-HL4K5AHI.js";
7
7
 
8
8
  // src/commands/check.ts
9
9
  import { defineCommand } from "citty";
@@ -3,6 +3,7 @@
3
3
  // src/utils/logger.ts
4
4
  import { consola } from "consola";
5
5
  import pc from "picocolors";
6
+ import ora from "ora";
6
7
  var logger = {
7
8
  success(msg) {
8
9
  consola.log(` ${pc.green("\u2713")} ${msg}`);
@@ -21,6 +22,15 @@ var logger = {
21
22
  },
22
23
  blank() {
23
24
  consola.log("");
25
+ },
26
+ /**
27
+ * Create a spinner for a step. Call .succeed() or .fail() when done.
28
+ */
29
+ step(msg) {
30
+ return ora({
31
+ text: msg,
32
+ prefixText: " "
33
+ }).start();
24
34
  }
25
35
  };
26
36
 
package/dist/index.js CHANGED
@@ -19,9 +19,9 @@ var main = defineCommand({
19
19
  }
20
20
  },
21
21
  subCommands: {
22
- init: () => import("./init-S5H4HBFM.js").then((m) => m.default),
23
- "add:feature": () => import("./add-feature-YXWSRIVE.js").then((m) => m.default),
24
- check: () => import("./check-RCJRXIU5.js").then((m) => m.default),
22
+ init: () => import("./init-UBPMMJ4L.js").then((m) => m.default),
23
+ "add:feature": () => import("./add-feature-JBVJHKHX.js").then((m) => m.default),
24
+ check: () => import("./check-B4VHLFHH.js").then((m) => m.default),
25
25
  version: () => import("./version-VO3LHLDO.js").then((m) => m.default)
26
26
  },
27
27
  run({ args }) {
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ kebabCase,
4
+ renderTemplate
5
+ } from "./chunk-PIFX2L5H.js";
6
+ import {
7
+ ensureDir,
8
+ fileExists,
9
+ logger,
10
+ readFile,
11
+ writeFile
12
+ } from "./chunk-HL4K5AHI.js";
13
+
14
+ // src/commands/init.ts
15
+ import { defineCommand } from "citty";
16
+ import { spawn } from "child_process";
17
+ import { join } from "path";
18
+ import pc from "picocolors";
19
+ function runInteractive(cmd, args, cwd) {
20
+ return new Promise((resolve, reject) => {
21
+ const proc = spawn(cmd, args, {
22
+ stdio: "inherit",
23
+ cwd
24
+ });
25
+ proc.on("close", (code) => {
26
+ if (code === 0) {
27
+ resolve(code);
28
+ } else {
29
+ reject(new Error(`Command failed with code ${code}`));
30
+ }
31
+ });
32
+ proc.on("error", reject);
33
+ });
34
+ }
35
+ function stripJsonComments(str) {
36
+ return str.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
37
+ }
38
+ var init_default = defineCommand({
39
+ meta: {
40
+ name: "init",
41
+ description: "Create a new project with TanStack Start + Convex + Tailwind"
42
+ },
43
+ args: {
44
+ name: {
45
+ type: "positional",
46
+ description: "Name of the project",
47
+ required: true
48
+ }
49
+ },
50
+ async run({ args }) {
51
+ const rawName = args.name;
52
+ const name = kebabCase(rawName);
53
+ const cwd = process.cwd();
54
+ const projectDir = join(cwd, name);
55
+ if (await fileExists(projectDir)) {
56
+ logger.error(`Directory "${name}" already exists`);
57
+ process.exit(1);
58
+ }
59
+ logger.blank();
60
+ logger.log(` ${pc.bold("Forge CLI")} - Creating project "${name}"`);
61
+ logger.blank();
62
+ logger.log(` ${pc.cyan("Step 1/5:")} TanStack Start setup`);
63
+ logger.blank();
64
+ try {
65
+ await runInteractive("pnpm", ["create", "@tanstack/start@latest", name]);
66
+ } catch {
67
+ logger.error("TanStack Start scaffolding failed");
68
+ process.exit(1);
69
+ }
70
+ logger.blank();
71
+ const step2 = logger.step(`${pc.cyan("Step 2/5:")} Adding Forge customizations...`);
72
+ try {
73
+ const pkgPath = join(projectDir, "package.json");
74
+ const pkg = JSON.parse(await readFile(pkgPath));
75
+ pkg.dependencies = {
76
+ ...pkg.dependencies,
77
+ convex: "^1.31.4",
78
+ clsx: "^2.1.1",
79
+ "tailwind-merge": "^3.4.0"
80
+ };
81
+ pkg.devDependencies = {
82
+ ...pkg.devDependencies,
83
+ "@biomejs/biome": "^1.9.4",
84
+ autoprefixer: "^10.4.20",
85
+ postcss: "^8.5.0",
86
+ tailwindcss: "^3.4.17"
87
+ };
88
+ pkg.scripts = {
89
+ ...pkg.scripts,
90
+ lint: "biome check .",
91
+ "lint:fix": "biome check . --write",
92
+ format: "biome format . --write"
93
+ };
94
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2));
95
+ const tsconfigPath = join(projectDir, "tsconfig.json");
96
+ const tsconfigContent = await readFile(tsconfigPath);
97
+ const tsconfig = JSON.parse(stripJsonComments(tsconfigContent));
98
+ tsconfig.compilerOptions = {
99
+ ...tsconfig.compilerOptions,
100
+ paths: {
101
+ ...tsconfig.compilerOptions?.paths,
102
+ "~/*": ["./src/*"],
103
+ "@convex/*": ["./convex/*"]
104
+ }
105
+ };
106
+ await writeFile(tsconfigPath, JSON.stringify(tsconfig, null, 2));
107
+ const templateData = { name };
108
+ const forgeFiles = [
109
+ { templatePath: "init/biome.json.hbs", destPath: join(projectDir, "biome.json") },
110
+ { templatePath: "init/tailwind.config.ts.hbs", destPath: join(projectDir, "tailwind.config.ts") },
111
+ { templatePath: "init/postcss.config.js.hbs", destPath: join(projectDir, "postcss.config.js") },
112
+ { templatePath: "init/convex/schema.ts.hbs", destPath: join(projectDir, "convex/schema.ts") },
113
+ { templatePath: "init/src/lib/cn.ts.hbs", destPath: join(projectDir, "src/lib/cn.ts") },
114
+ { templatePath: "init/src/providers/index.tsx.hbs", destPath: join(projectDir, "src/providers/index.tsx") },
115
+ { templatePath: "init/claude.md.hbs", destPath: join(projectDir, "CLAUDE.md") }
116
+ ];
117
+ for (const file of forgeFiles) {
118
+ const content = renderTemplate(file.templatePath, templateData);
119
+ await writeFile(file.destPath, content);
120
+ }
121
+ const rootPath = join(projectDir, "src/routes/__root.tsx");
122
+ if (await fileExists(rootPath)) {
123
+ let rootContent = await readFile(rootPath);
124
+ if (!rootContent.includes("Providers")) {
125
+ rootContent = `import { Providers } from "../providers";
126
+ ${rootContent}`;
127
+ rootContent = rootContent.replace(
128
+ /(<body[^>]*>)([\s\S]*?)(<\/body>)/,
129
+ "$1<Providers>$2</Providers>$3"
130
+ );
131
+ await writeFile(rootPath, rootContent);
132
+ }
133
+ }
134
+ const emptyDirs = [
135
+ join(projectDir, "src/components/ui"),
136
+ join(projectDir, "src/features"),
137
+ join(projectDir, "src/hooks"),
138
+ join(projectDir, "convex/features")
139
+ ];
140
+ for (const dir of emptyDirs) {
141
+ await ensureDir(dir);
142
+ await writeFile(join(dir, ".gitkeep"), "");
143
+ }
144
+ const stylesPath = join(projectDir, "src/styles.css");
145
+ const existingStyles = await fileExists(stylesPath) ? await readFile(stylesPath) : "";
146
+ if (!existingStyles.includes("@tailwind")) {
147
+ const tailwindDirectives = `@tailwind base;
148
+ @tailwind components;
149
+ @tailwind utilities;
150
+
151
+ `;
152
+ await writeFile(stylesPath, tailwindDirectives + existingStyles);
153
+ }
154
+ const gitignorePath = join(projectDir, ".gitignore");
155
+ let gitignore = await fileExists(gitignorePath) ? await readFile(gitignorePath) : "";
156
+ const additions = [".env", ".env.local", ".output", "convex/_generated"];
157
+ for (const item of additions) {
158
+ if (!gitignore.includes(item)) {
159
+ gitignore += `
160
+ ${item}`;
161
+ }
162
+ }
163
+ await writeFile(gitignorePath, gitignore.trim() + "\n");
164
+ await writeFile(join(projectDir, ".env.example"), "VITE_CONVEX_URL=\n");
165
+ step2.succeed(`${pc.cyan("Step 2/5:")} Forge customizations added`);
166
+ } catch (err) {
167
+ step2.fail(`${pc.cyan("Step 2/5:")} Failed to add customizations`);
168
+ throw err;
169
+ }
170
+ logger.blank();
171
+ logger.log(` ${pc.cyan("Step 3/5:")} Installing dependencies...`);
172
+ logger.blank();
173
+ try {
174
+ await runInteractive("pnpm", ["install"], projectDir);
175
+ } catch {
176
+ logger.error("Failed to install dependencies");
177
+ process.exit(1);
178
+ }
179
+ logger.blank();
180
+ logger.log(` ${pc.cyan("Step 4/5:")} Convex setup`);
181
+ logger.blank();
182
+ try {
183
+ await runInteractive("npx", ["convex", "dev", "--once", "--configure=new"], projectDir);
184
+ } catch {
185
+ logger.warn("Convex setup skipped or failed - you can run it later");
186
+ }
187
+ logger.blank();
188
+ logger.log(` ${pc.cyan("Step 5/5:")} shadcn/ui setup`);
189
+ logger.blank();
190
+ try {
191
+ await runInteractive("pnpm", ["dlx", "shadcn@latest", "init"], projectDir);
192
+ } catch {
193
+ logger.warn("shadcn setup skipped or failed - you can run it later");
194
+ }
195
+ logger.blank();
196
+ logger.log(` ${pc.green("\u2713")} ${pc.bold("Project created successfully!")}`);
197
+ logger.blank();
198
+ logger.log(` ${pc.cyan("cd")} ${name}`);
199
+ logger.log(` ${pc.cyan("pnpm dev")}`);
200
+ logger.blank();
201
+ logger.log(` ${pc.dim("CLAUDE.md is configured. Claude Code will use forge CLI automatically.")}`);
202
+ logger.blank();
203
+ }
204
+ });
205
+ export {
206
+ init_default as default
207
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-forge-cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "TypeScript stack scaffolding & enforcement CLI for TanStack Start + Convex",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -43,13 +43,14 @@
43
43
  "consola": "^3.2.3",
44
44
  "fast-glob": "^3.3.2",
45
45
  "handlebars": "^4.7.8",
46
+ "ora": "^9.0.0",
46
47
  "oxc-parser": "^0.56.0",
47
48
  "picocolors": "^1.1.1"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@types/node": "^25.0.8",
51
- "tsx": "^4.19.2",
52
52
  "tsup": "^8.5.1",
53
+ "tsx": "^4.19.2",
53
54
  "typescript": "^5.9.3",
54
55
  "vitest": "^4.0.17"
55
56
  },
@@ -1,210 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- kebabCase,
4
- renderTemplate
5
- } from "./chunk-PIFX2L5H.js";
6
- import {
7
- ensureDir,
8
- fileExists,
9
- logger,
10
- readFile,
11
- writeFile
12
- } from "./chunk-J4V5PGVT.js";
13
-
14
- // src/commands/init.ts
15
- import { defineCommand } from "citty";
16
- import { spawn } from "child_process";
17
- import { join } from "path";
18
- import pc from "picocolors";
19
- function runInteractive(cmd, args, cwd) {
20
- return new Promise((resolve, reject) => {
21
- const proc = spawn(cmd, args, {
22
- stdio: "inherit",
23
- cwd
24
- });
25
- proc.on("close", (code) => {
26
- if (code === 0) {
27
- resolve(code);
28
- } else {
29
- reject(new Error(`Command failed with code ${code}`));
30
- }
31
- });
32
- proc.on("error", reject);
33
- });
34
- }
35
- function stripJsonComments(str) {
36
- return str.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
37
- }
38
- var init_default = defineCommand({
39
- meta: {
40
- name: "init",
41
- description: "Create a new project with TanStack Start + Convex + Tailwind"
42
- },
43
- args: {
44
- name: {
45
- type: "positional",
46
- description: "Name of the project",
47
- required: true
48
- }
49
- },
50
- async run({ args }) {
51
- const rawName = args.name;
52
- const name = kebabCase(rawName);
53
- const cwd = process.cwd();
54
- const projectDir = join(cwd, name);
55
- if (await fileExists(projectDir)) {
56
- logger.error(`Directory "${name}" already exists`);
57
- process.exit(1);
58
- }
59
- logger.blank();
60
- logger.log(` ${pc.bold("Forge CLI")} - Creating project "${name}"`);
61
- logger.blank();
62
- logger.log(` ${pc.cyan("Step 1/5:")} TanStack Start setup`);
63
- logger.blank();
64
- try {
65
- await runInteractive("pnpm", ["create", "@tanstack/start@latest", name]);
66
- } catch {
67
- logger.error("TanStack Start scaffolding failed");
68
- process.exit(1);
69
- }
70
- logger.blank();
71
- logger.log(` ${pc.cyan("Step 2/5:")} Adding Forge customizations...`);
72
- const pkgPath = join(projectDir, "package.json");
73
- const pkg = JSON.parse(await readFile(pkgPath));
74
- pkg.dependencies = {
75
- ...pkg.dependencies,
76
- convex: "^1.31.4",
77
- clsx: "^2.1.1",
78
- "tailwind-merge": "^3.4.0"
79
- };
80
- pkg.devDependencies = {
81
- ...pkg.devDependencies,
82
- "@biomejs/biome": "^1.9.4",
83
- autoprefixer: "^10.4.20",
84
- postcss: "^8.5.0",
85
- tailwindcss: "^3.4.17"
86
- };
87
- pkg.scripts = {
88
- ...pkg.scripts,
89
- lint: "biome check .",
90
- "lint:fix": "biome check . --write",
91
- format: "biome format . --write"
92
- };
93
- await writeFile(pkgPath, JSON.stringify(pkg, null, 2));
94
- logger.success("Updated package.json with Convex & Tailwind deps");
95
- const tsconfigPath = join(projectDir, "tsconfig.json");
96
- const tsconfigContent = await readFile(tsconfigPath);
97
- const tsconfig = JSON.parse(stripJsonComments(tsconfigContent));
98
- tsconfig.compilerOptions = {
99
- ...tsconfig.compilerOptions,
100
- paths: {
101
- ...tsconfig.compilerOptions?.paths,
102
- "~/*": ["./src/*"],
103
- "@convex/*": ["./convex/*"]
104
- }
105
- };
106
- await writeFile(tsconfigPath, JSON.stringify(tsconfig, null, 2));
107
- logger.success("Added path aliases to tsconfig.json");
108
- const templateData = { name };
109
- const forgeFiles = [
110
- { templatePath: "init/biome.json.hbs", destPath: join(projectDir, "biome.json") },
111
- { templatePath: "init/tailwind.config.ts.hbs", destPath: join(projectDir, "tailwind.config.ts") },
112
- { templatePath: "init/postcss.config.js.hbs", destPath: join(projectDir, "postcss.config.js") },
113
- { templatePath: "init/convex/schema.ts.hbs", destPath: join(projectDir, "convex/schema.ts") },
114
- { templatePath: "init/src/lib/cn.ts.hbs", destPath: join(projectDir, "src/lib/cn.ts") },
115
- { templatePath: "init/src/providers/index.tsx.hbs", destPath: join(projectDir, "src/providers/index.tsx") },
116
- { templatePath: "init/claude.md.hbs", destPath: join(projectDir, "CLAUDE.md") }
117
- ];
118
- for (const file of forgeFiles) {
119
- const content = renderTemplate(file.templatePath, templateData);
120
- await writeFile(file.destPath, content);
121
- const relativePath = file.destPath.replace(projectDir + "/", "");
122
- logger.success(`Created ${relativePath}`);
123
- }
124
- const rootPath = join(projectDir, "src/routes/__root.tsx");
125
- if (await fileExists(rootPath)) {
126
- let rootContent = await readFile(rootPath);
127
- if (!rootContent.includes("Providers")) {
128
- rootContent = `import { Providers } from "../providers";
129
- ${rootContent}`;
130
- rootContent = rootContent.replace(
131
- /(<body[^>]*>)([\s\S]*?)(<\/body>)/,
132
- "$1<Providers>$2</Providers>$3"
133
- );
134
- await writeFile(rootPath, rootContent);
135
- logger.success("Modified __root.tsx to use Providers");
136
- }
137
- }
138
- const emptyDirs = [
139
- join(projectDir, "src/components/ui"),
140
- join(projectDir, "src/features"),
141
- join(projectDir, "src/hooks"),
142
- join(projectDir, "convex/features")
143
- ];
144
- for (const dir of emptyDirs) {
145
- await ensureDir(dir);
146
- await writeFile(join(dir, ".gitkeep"), "");
147
- }
148
- logger.success("Created directory structure");
149
- const stylesPath = join(projectDir, "src/styles.css");
150
- const existingStyles = await fileExists(stylesPath) ? await readFile(stylesPath) : "";
151
- if (!existingStyles.includes("@tailwind")) {
152
- const tailwindDirectives = `@tailwind base;
153
- @tailwind components;
154
- @tailwind utilities;
155
-
156
- `;
157
- await writeFile(stylesPath, tailwindDirectives + existingStyles);
158
- logger.success("Added Tailwind directives to styles.css");
159
- }
160
- const gitignorePath = join(projectDir, ".gitignore");
161
- let gitignore = await fileExists(gitignorePath) ? await readFile(gitignorePath) : "";
162
- const additions = [".env", ".env.local", ".output", "convex/_generated"];
163
- for (const item of additions) {
164
- if (!gitignore.includes(item)) {
165
- gitignore += `
166
- ${item}`;
167
- }
168
- }
169
- await writeFile(gitignorePath, gitignore.trim() + "\n");
170
- logger.success("Updated .gitignore");
171
- await writeFile(join(projectDir, ".env.example"), "VITE_CONVEX_URL=\n");
172
- logger.success("Created .env.example");
173
- logger.blank();
174
- logger.log(` ${pc.cyan("Step 3/5:")} Installing dependencies...`);
175
- logger.blank();
176
- try {
177
- await runInteractive("pnpm", ["install"], projectDir);
178
- } catch {
179
- logger.error("Failed to install dependencies");
180
- process.exit(1);
181
- }
182
- logger.blank();
183
- logger.log(` ${pc.cyan("Step 4/5:")} Convex setup`);
184
- logger.blank();
185
- try {
186
- await runInteractive("npx", ["convex", "dev", "--once", "--configure=new"], projectDir);
187
- } catch {
188
- logger.warn("Convex setup skipped or failed - you can run it later");
189
- }
190
- logger.blank();
191
- logger.log(` ${pc.cyan("Step 5/5:")} shadcn/ui setup`);
192
- logger.blank();
193
- try {
194
- await runInteractive("pnpm", ["dlx", "shadcn@latest", "init"], projectDir);
195
- } catch {
196
- logger.warn("shadcn setup skipped or failed - you can run it later");
197
- }
198
- logger.blank();
199
- logger.log(` ${pc.green("\u2713")} ${pc.bold("Project created successfully!")}`);
200
- logger.blank();
201
- logger.log(` ${pc.cyan("cd")} ${name}`);
202
- logger.log(` ${pc.cyan("pnpm dev")}`);
203
- logger.blank();
204
- logger.log(` ${pc.dim("CLAUDE.md is configured. Claude Code will use forge CLI automatically.")}`);
205
- logger.blank();
206
- }
207
- });
208
- export {
209
- init_default as default
210
- };