ai-forge-cli 0.4.2 → 0.4.6

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 (84) hide show
  1. package/dist/add-component-AQPCXQ4O.js +120 -0
  2. package/dist/{add-feature-2AR4DP7P.js → add-feature-VEY62Y5M.js} +37 -8
  3. package/dist/add-hook-OE4BKE6B.js +103 -0
  4. package/dist/{add-integration-QXB63S3V.js → add-integration-NJ56UXSY.js} +97 -27
  5. package/dist/add-layout-2KQPPTJX.js +104 -0
  6. package/dist/add-page-GIC2ZXJI.js +81 -0
  7. package/dist/add-util-T5JXAV4G.js +73 -0
  8. package/dist/{chunk-PIFX2L5H.js → chunk-MBF6K2AC.js} +1 -0
  9. package/dist/chunk-YMJTSRWM.js +49 -0
  10. package/dist/index.js +8 -3
  11. package/dist/{init-OYJP5QCZ.js → init-C4FFZDSP.js} +1 -1
  12. package/dist/templates/component/Component.tsx.hbs +11 -0
  13. package/dist/templates/feature/routes/$id.tsx.hbs +3 -13
  14. package/dist/templates/feature/routes/index.tsx.hbs +19 -9
  15. package/dist/templates/feature/src/components/Card.tsx.hbs +21 -0
  16. package/dist/templates/feature/src/components/Detail.tsx.hbs +28 -0
  17. package/dist/templates/feature/src/components/Form.tsx.hbs +46 -0
  18. package/dist/templates/feature/src/components/List.tsx.hbs +26 -0
  19. package/dist/templates/feature/src/components/index.ts.hbs +4 -4
  20. package/dist/templates/hook/feature-hook.ts.hbs +4 -0
  21. package/dist/templates/hook/global-hook.ts.hbs +11 -0
  22. package/dist/templates/init/claude.md.hbs +37 -11
  23. package/dist/templates/integration/auth/src/components/auth/AuthGuard.tsx.hbs +31 -0
  24. package/dist/templates/integration/auth/src/components/auth/LoginForm.tsx.hbs +83 -0
  25. package/dist/templates/integration/auth/src/components/auth/SignupForm.tsx.hbs +102 -0
  26. package/dist/templates/integration/auth/src/components/auth/UserMenu.tsx.hbs +36 -0
  27. package/dist/templates/integration/auth/src/components/auth/index.ts.hbs +4 -0
  28. package/dist/templates/integration/auth/src/routes/login.tsx.hbs +14 -0
  29. package/dist/templates/integration/auth/src/routes/signup.tsx.hbs +14 -0
  30. package/dist/templates/integration/storage/src/components/storage/FilePreview.tsx.hbs +18 -0
  31. package/dist/templates/integration/storage/src/components/storage/FileUpload.tsx.hbs +49 -0
  32. package/dist/templates/integration/storage/src/components/storage/index.ts.hbs +2 -0
  33. package/dist/templates/layout/auth/_layout.tsx.hbs +15 -0
  34. package/dist/templates/layout/auth/index.tsx.hbs +5 -0
  35. package/dist/templates/layout/base/_layout.tsx.hbs +14 -0
  36. package/dist/templates/layout/base/index.tsx.hbs +13 -0
  37. package/dist/templates/layout/dashboard/_layout.tsx.hbs +20 -0
  38. package/dist/templates/layout/dashboard/components/Header.tsx.hbs +12 -0
  39. package/dist/templates/layout/dashboard/components/Sidebar.tsx.hbs +39 -0
  40. package/dist/templates/layout/dashboard/components/index.ts.hbs +2 -0
  41. package/dist/templates/layout/dashboard/index.tsx.hbs +15 -0
  42. package/dist/templates/layout/marketing/_layout.tsx.hbs +39 -0
  43. package/dist/templates/layout/marketing/index.tsx.hbs +16 -0
  44. package/dist/templates/page/components/Content.tsx.hbs +8 -0
  45. package/dist/templates/page/components/index.ts.hbs +1 -0
  46. package/dist/templates/page/route.tsx.hbs +10 -0
  47. package/dist/templates/util/util.ts.hbs +7 -0
  48. package/package.json +1 -1
  49. package/templates/component/Component.tsx.hbs +11 -0
  50. package/templates/feature/routes/$id.tsx.hbs +3 -13
  51. package/templates/feature/routes/index.tsx.hbs +19 -9
  52. package/templates/feature/src/components/Card.tsx.hbs +21 -0
  53. package/templates/feature/src/components/Detail.tsx.hbs +28 -0
  54. package/templates/feature/src/components/Form.tsx.hbs +46 -0
  55. package/templates/feature/src/components/List.tsx.hbs +26 -0
  56. package/templates/feature/src/components/index.ts.hbs +4 -4
  57. package/templates/hook/feature-hook.ts.hbs +4 -0
  58. package/templates/hook/global-hook.ts.hbs +11 -0
  59. package/templates/init/claude.md.hbs +37 -11
  60. package/templates/integration/auth/src/components/auth/AuthGuard.tsx.hbs +31 -0
  61. package/templates/integration/auth/src/components/auth/LoginForm.tsx.hbs +83 -0
  62. package/templates/integration/auth/src/components/auth/SignupForm.tsx.hbs +102 -0
  63. package/templates/integration/auth/src/components/auth/UserMenu.tsx.hbs +36 -0
  64. package/templates/integration/auth/src/components/auth/index.ts.hbs +4 -0
  65. package/templates/integration/auth/src/routes/login.tsx.hbs +14 -0
  66. package/templates/integration/auth/src/routes/signup.tsx.hbs +14 -0
  67. package/templates/integration/storage/src/components/storage/FilePreview.tsx.hbs +18 -0
  68. package/templates/integration/storage/src/components/storage/FileUpload.tsx.hbs +49 -0
  69. package/templates/integration/storage/src/components/storage/index.ts.hbs +2 -0
  70. package/templates/layout/auth/_layout.tsx.hbs +15 -0
  71. package/templates/layout/auth/index.tsx.hbs +5 -0
  72. package/templates/layout/base/_layout.tsx.hbs +14 -0
  73. package/templates/layout/base/index.tsx.hbs +13 -0
  74. package/templates/layout/dashboard/_layout.tsx.hbs +20 -0
  75. package/templates/layout/dashboard/components/Header.tsx.hbs +12 -0
  76. package/templates/layout/dashboard/components/Sidebar.tsx.hbs +39 -0
  77. package/templates/layout/dashboard/components/index.ts.hbs +2 -0
  78. package/templates/layout/dashboard/index.tsx.hbs +15 -0
  79. package/templates/layout/marketing/_layout.tsx.hbs +39 -0
  80. package/templates/layout/marketing/index.tsx.hbs +16 -0
  81. package/templates/page/components/Content.tsx.hbs +8 -0
  82. package/templates/page/components/index.ts.hbs +1 -0
  83. package/templates/page/route.tsx.hbs +10 -0
  84. package/templates/util/util.ts.hbs +7 -0
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ kebabCase,
4
+ pascalCase,
5
+ renderTemplate
6
+ } from "./chunk-MBF6K2AC.js";
7
+ import {
8
+ fileExists,
9
+ logger,
10
+ readFile,
11
+ writeFile
12
+ } from "./chunk-HL4K5AHI.js";
13
+
14
+ // src/commands/add-component.ts
15
+ import { defineCommand } from "citty";
16
+ import { join } from "path";
17
+ var add_component_default = defineCommand({
18
+ meta: {
19
+ name: "add:component",
20
+ description: "Create a new React component"
21
+ },
22
+ args: {
23
+ name: {
24
+ type: "positional",
25
+ description: "Component name (will be PascalCased)",
26
+ required: true
27
+ },
28
+ feature: {
29
+ type: "string",
30
+ description: "Add to a feature's components folder"
31
+ },
32
+ page: {
33
+ type: "string",
34
+ description: "Add to a page's components folder"
35
+ },
36
+ layout: {
37
+ type: "string",
38
+ description: "Add to a layout's components folder"
39
+ }
40
+ },
41
+ async run({ args }) {
42
+ const cwd = process.cwd();
43
+ const rawName = args.name;
44
+ const componentName = pascalCase(rawName);
45
+ const feature = args.feature;
46
+ const page = args.page;
47
+ const layout = args.layout;
48
+ let targetDir;
49
+ let indexPath;
50
+ let locationLabel;
51
+ if (feature) {
52
+ const featureName = kebabCase(feature);
53
+ targetDir = join(cwd, "src/features", featureName, "components");
54
+ indexPath = join(targetDir, "index.ts");
55
+ locationLabel = `src/features/${featureName}/components`;
56
+ if (!await fileExists(join(cwd, "src/features", featureName))) {
57
+ logger.error(`Feature "${featureName}" does not exist`);
58
+ logger.log(` Run: forge add:feature ${featureName}`);
59
+ process.exit(1);
60
+ }
61
+ } else if (page) {
62
+ const pageName = kebabCase(page);
63
+ targetDir = join(cwd, "src/components", pageName);
64
+ indexPath = join(targetDir, "index.ts");
65
+ locationLabel = `src/components/${pageName}`;
66
+ } else if (layout) {
67
+ const layoutName = kebabCase(layout);
68
+ targetDir = join(cwd, "src/components", layoutName);
69
+ indexPath = join(targetDir, "index.ts");
70
+ locationLabel = `src/components/${layoutName}`;
71
+ } else {
72
+ targetDir = join(cwd, "src/components/ui");
73
+ indexPath = join(targetDir, "index.ts");
74
+ locationLabel = "src/components/ui";
75
+ }
76
+ const componentPath = join(targetDir, `${componentName}.tsx`);
77
+ if (await fileExists(componentPath)) {
78
+ logger.error(`Component "${componentName}" already exists at ${locationLabel}`);
79
+ process.exit(1);
80
+ }
81
+ logger.blank();
82
+ const templateData = {
83
+ componentName,
84
+ name: kebabCase(rawName)
85
+ };
86
+ const content = renderTemplate("component/Component.tsx.hbs", templateData);
87
+ await writeFile(componentPath, content);
88
+ logger.success(`Created ${locationLabel}/${componentName}.tsx`);
89
+ const exportLine = `export { ${componentName} } from "./${componentName}";`;
90
+ if (await fileExists(indexPath)) {
91
+ const existing = await readFile(indexPath);
92
+ if (!existing.includes(exportLine)) {
93
+ await writeFile(indexPath, existing.trim() + "\n" + exportLine + "\n");
94
+ logger.success(`Updated ${locationLabel}/index.ts`);
95
+ }
96
+ } else {
97
+ await writeFile(indexPath, exportLine + "\n");
98
+ logger.success(`Created ${locationLabel}/index.ts`);
99
+ }
100
+ logger.blank();
101
+ logger.log(` Component "${componentName}" created!`);
102
+ logger.blank();
103
+ logger.log(" Location:");
104
+ logger.log(` ${locationLabel}/${componentName}.tsx`);
105
+ logger.blank();
106
+ logger.log(" Import:");
107
+ if (feature) {
108
+ logger.log(` import { ${componentName} } from "~/features/${kebabCase(feature)}/components";`);
109
+ } else if (page || layout) {
110
+ const folder = page ? kebabCase(page) : kebabCase(layout);
111
+ logger.log(` import { ${componentName} } from "~/components/${folder}";`);
112
+ } else {
113
+ logger.log(` import { ${componentName} } from "~/components/ui";`);
114
+ }
115
+ logger.blank();
116
+ }
117
+ });
118
+ export {
119
+ add_component_default as default
120
+ };
@@ -2,11 +2,15 @@
2
2
  import {
3
3
  insertAtMarker
4
4
  } from "./chunk-M7T2JD5B.js";
5
+ import {
6
+ ensureShadcnComponents
7
+ } from "./chunk-YMJTSRWM.js";
5
8
  import {
6
9
  camelCase,
7
10
  kebabCase,
11
+ pascalCase,
8
12
  renderTemplate
9
- } from "./chunk-PIFX2L5H.js";
13
+ } from "./chunk-MBF6K2AC.js";
10
14
  import {
11
15
  fileExists,
12
16
  logger,
@@ -44,6 +48,8 @@ var add_feature_default = defineCommand({
44
48
  process.exit(1);
45
49
  }
46
50
  logger.blank();
51
+ await ensureShadcnComponents(cwd, "feature");
52
+ const pascal = pascalCase(name);
47
53
  const templateData = { name };
48
54
  const files = [
49
55
  // Convex files
@@ -63,11 +69,7 @@ var add_feature_default = defineCommand({
63
69
  templatePath: "feature/convex/index.ts.hbs",
64
70
  destPath: join(cwd, "convex/features", name, "index.ts")
65
71
  },
66
- // Src files
67
- {
68
- templatePath: "feature/src/components/index.ts.hbs",
69
- destPath: join(cwd, "src/features", name, "components/index.ts")
70
- },
72
+ // Src files - hooks and index
71
73
  {
72
74
  templatePath: "feature/src/hooks.ts.hbs",
73
75
  destPath: join(cwd, "src/features", name, "hooks.ts")
@@ -76,6 +78,27 @@ var add_feature_default = defineCommand({
76
78
  templatePath: "feature/src/index.ts.hbs",
77
79
  destPath: join(cwd, "src/features", name, "index.ts")
78
80
  },
81
+ // Components
82
+ {
83
+ templatePath: "feature/src/components/List.tsx.hbs",
84
+ destPath: join(cwd, "src/features", name, "components", `${pascal}List.tsx`)
85
+ },
86
+ {
87
+ templatePath: "feature/src/components/Card.tsx.hbs",
88
+ destPath: join(cwd, "src/features", name, "components", `${pascal}Card.tsx`)
89
+ },
90
+ {
91
+ templatePath: "feature/src/components/Form.tsx.hbs",
92
+ destPath: join(cwd, "src/features", name, "components", `${pascal}Form.tsx`)
93
+ },
94
+ {
95
+ templatePath: "feature/src/components/Detail.tsx.hbs",
96
+ destPath: join(cwd, "src/features", name, "components", `${pascal}Detail.tsx`)
97
+ },
98
+ {
99
+ templatePath: "feature/src/components/index.ts.hbs",
100
+ destPath: join(cwd, "src/features", name, "components/index.ts")
101
+ },
79
102
  // Route files
80
103
  {
81
104
  templatePath: "feature/routes/index.tsx.hbs",
@@ -96,10 +119,16 @@ var add_feature_default = defineCommand({
96
119
  logger.blank();
97
120
  logger.log(` Feature "${name}" created successfully.`);
98
121
  logger.blank();
122
+ logger.log(" Created:");
123
+ logger.log(` Backend: convex/features/${name}/`);
124
+ logger.log(` Components: ${pascal}List, ${pascal}Card, ${pascal}Form, ${pascal}Detail`);
125
+ logger.log(` Routes: /src/routes/${name}/`);
126
+ logger.blank();
99
127
  logger.log(" Next steps:");
100
128
  logger.log(` 1. Define your schema in convex/features/${name}/schema.ts`);
101
- logger.log(" 2. Run `npx convex dev` to sync");
102
- logger.log(` 3. Build components in src/features/${name}/components/`);
129
+ logger.log(` 2. Update form fields in ${pascal}Form.tsx`);
130
+ logger.log(` 3. Customize ${pascal}Card.tsx display`);
131
+ logger.log(" 4. Run: npx convex dev");
103
132
  logger.blank();
104
133
  }
105
134
  });
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ camelCase,
4
+ kebabCase,
5
+ renderTemplate
6
+ } from "./chunk-MBF6K2AC.js";
7
+ import {
8
+ fileExists,
9
+ logger,
10
+ readFile,
11
+ writeFile
12
+ } from "./chunk-HL4K5AHI.js";
13
+
14
+ // src/commands/add-hook.ts
15
+ import { defineCommand } from "citty";
16
+ import { join } from "path";
17
+ var add_hook_default = defineCommand({
18
+ meta: {
19
+ name: "add:hook",
20
+ description: "Create a new React hook"
21
+ },
22
+ args: {
23
+ name: {
24
+ type: "positional",
25
+ description: "Hook name (will be prefixed with 'use' if needed)",
26
+ required: true
27
+ },
28
+ feature: {
29
+ type: "string",
30
+ description: "Append to a feature's hooks.ts file"
31
+ }
32
+ },
33
+ async run({ args }) {
34
+ const cwd = process.cwd();
35
+ const rawName = args.name;
36
+ const feature = args.feature;
37
+ let hookName = camelCase(rawName);
38
+ if (!hookName.startsWith("use")) {
39
+ hookName = `use${hookName.charAt(0).toUpperCase()}${hookName.slice(1)}`;
40
+ }
41
+ const templateData = {
42
+ hookName,
43
+ name: rawName
44
+ };
45
+ logger.blank();
46
+ if (feature) {
47
+ const featureName = kebabCase(feature);
48
+ const hooksPath = join(cwd, "src/features", featureName, "hooks.ts");
49
+ if (!await fileExists(hooksPath)) {
50
+ logger.error(`Feature "${featureName}" does not exist or has no hooks.ts`);
51
+ logger.log(` Run: forge add:feature ${featureName}`);
52
+ process.exit(1);
53
+ }
54
+ const existing = await readFile(hooksPath);
55
+ if (existing.includes(`export function ${hookName}`)) {
56
+ logger.error(`Hook "${hookName}" already exists in ${featureName}/hooks.ts`);
57
+ process.exit(1);
58
+ }
59
+ const hookCode = renderTemplate("hook/feature-hook.ts.hbs", templateData);
60
+ await writeFile(hooksPath, existing.trim() + "\n\n" + hookCode);
61
+ logger.success(`Added ${hookName} to src/features/${featureName}/hooks.ts`);
62
+ logger.blank();
63
+ logger.log(` Hook "${hookName}" added to feature "${featureName}"!`);
64
+ logger.blank();
65
+ logger.log(" Import:");
66
+ logger.log(` import { ${hookName} } from "~/features/${featureName}";`);
67
+ } else {
68
+ const hooksDir = join(cwd, "src/hooks");
69
+ const hookFile = join(hooksDir, `${hookName}.ts`);
70
+ const indexPath = join(hooksDir, "index.ts");
71
+ if (await fileExists(hookFile)) {
72
+ logger.error(`Hook "${hookName}" already exists at src/hooks/${hookName}.ts`);
73
+ process.exit(1);
74
+ }
75
+ const content = renderTemplate("hook/global-hook.ts.hbs", templateData);
76
+ await writeFile(hookFile, content);
77
+ logger.success(`Created src/hooks/${hookName}.ts`);
78
+ const exportLine = `export { ${hookName} } from "./${hookName}";`;
79
+ if (await fileExists(indexPath)) {
80
+ const existing = await readFile(indexPath);
81
+ if (!existing.includes(exportLine)) {
82
+ await writeFile(indexPath, existing.trim() + "\n" + exportLine + "\n");
83
+ logger.success("Updated src/hooks/index.ts");
84
+ }
85
+ } else {
86
+ await writeFile(indexPath, exportLine + "\n");
87
+ logger.success("Created src/hooks/index.ts");
88
+ }
89
+ logger.blank();
90
+ logger.log(` Hook "${hookName}" created!`);
91
+ logger.blank();
92
+ logger.log(" Location:");
93
+ logger.log(` src/hooks/${hookName}.ts`);
94
+ logger.blank();
95
+ logger.log(" Import:");
96
+ logger.log(` import { ${hookName} } from "~/hooks";`);
97
+ }
98
+ logger.blank();
99
+ }
100
+ });
101
+ export {
102
+ add_hook_default as default
103
+ };
@@ -2,9 +2,12 @@
2
2
  import {
3
3
  insertAtMarker
4
4
  } from "./chunk-M7T2JD5B.js";
5
+ import {
6
+ ensureShadcnComponents
7
+ } from "./chunk-YMJTSRWM.js";
5
8
  import {
6
9
  renderTemplate
7
- } from "./chunk-PIFX2L5H.js";
10
+ } from "./chunk-MBF6K2AC.js";
8
11
  import {
9
12
  fileExists,
10
13
  logger,
@@ -72,38 +75,72 @@ async function setupAuth(cwd) {
72
75
  step1.fail("Failed to install dependencies");
73
76
  process.exit(1);
74
77
  }
75
- const step2 = logger.step("Creating auth files...");
78
+ await ensureShadcnComponents(cwd, "auth");
79
+ const step2 = logger.step("Creating backend files...");
76
80
  try {
77
- const files = [
81
+ const backendFiles = [
78
82
  { template: "integration/auth/convex/auth.ts.hbs", dest: "convex/auth.ts" },
79
83
  { template: "integration/auth/convex/auth.config.ts.hbs", dest: "convex/auth.config.ts" },
80
84
  { template: "integration/auth/convex/http.ts.hbs", dest: "convex/http.ts" },
81
85
  { template: "integration/auth/src/lib/auth.ts.hbs", dest: "src/lib/auth.ts" }
82
86
  ];
83
- for (const file of files) {
87
+ for (const file of backendFiles) {
88
+ const content = renderTemplate(file.template, {});
89
+ await writeFile(join(cwd, file.dest), content);
90
+ }
91
+ step2.succeed("Backend files created");
92
+ } catch (err) {
93
+ step2.fail("Failed to create backend files");
94
+ throw err;
95
+ }
96
+ const step3 = logger.step("Creating UI components...");
97
+ try {
98
+ const uiFiles = [
99
+ { template: "integration/auth/src/components/auth/LoginForm.tsx.hbs", dest: "src/components/auth/LoginForm.tsx" },
100
+ { template: "integration/auth/src/components/auth/SignupForm.tsx.hbs", dest: "src/components/auth/SignupForm.tsx" },
101
+ { template: "integration/auth/src/components/auth/AuthGuard.tsx.hbs", dest: "src/components/auth/AuthGuard.tsx" },
102
+ { template: "integration/auth/src/components/auth/UserMenu.tsx.hbs", dest: "src/components/auth/UserMenu.tsx" },
103
+ { template: "integration/auth/src/components/auth/index.ts.hbs", dest: "src/components/auth/index.ts" }
104
+ ];
105
+ for (const file of uiFiles) {
106
+ const content = renderTemplate(file.template, {});
107
+ await writeFile(join(cwd, file.dest), content);
108
+ }
109
+ step3.succeed("UI components created");
110
+ } catch (err) {
111
+ step3.fail("Failed to create UI components");
112
+ throw err;
113
+ }
114
+ const step4 = logger.step("Creating routes...");
115
+ try {
116
+ const routeFiles = [
117
+ { template: "integration/auth/src/routes/login.tsx.hbs", dest: "src/routes/login.tsx" },
118
+ { template: "integration/auth/src/routes/signup.tsx.hbs", dest: "src/routes/signup.tsx" }
119
+ ];
120
+ for (const file of routeFiles) {
84
121
  const content = renderTemplate(file.template, {});
85
122
  await writeFile(join(cwd, file.dest), content);
86
123
  }
87
- step2.succeed("Auth files created");
124
+ step4.succeed("Routes created");
88
125
  } catch (err) {
89
- step2.fail("Failed to create auth files");
126
+ step4.fail("Failed to create routes");
90
127
  throw err;
91
128
  }
92
- const step3 = logger.step("Updating convex/schema.ts...");
129
+ const step5 = logger.step("Updating convex/schema.ts...");
93
130
  try {
94
131
  await updateSchemaForAuth(cwd);
95
- step3.succeed("Schema updated");
132
+ step5.succeed("Schema updated");
96
133
  } catch {
97
- step3.fail("Failed to update schema");
134
+ step5.fail("Failed to update schema");
98
135
  }
99
- const step4 = logger.step("Updating providers...");
136
+ const step6 = logger.step("Updating providers...");
100
137
  try {
101
138
  await updateProvidersForAuth(cwd);
102
- step4.succeed("Providers updated");
139
+ step6.succeed("Providers updated");
103
140
  } catch {
104
- step4.fail("Failed to update providers");
141
+ step6.fail("Failed to update providers");
105
142
  }
106
- const step5 = logger.step("Updating .env.example...");
143
+ const step7 = logger.step("Updating .env.example...");
107
144
  try {
108
145
  const envPath = join(cwd, ".env.example");
109
146
  let env = await fileExists(envPath) ? await readFile(envPath) : "";
@@ -117,18 +154,26 @@ AUTH_GOOGLE_SECRET=
117
154
  if (!env.includes("AUTH_GITHUB_ID")) {
118
155
  await writeFile(envPath, env.trim() + "\n" + authVars);
119
156
  }
120
- step5.succeed(".env.example updated");
157
+ step7.succeed(".env.example updated");
121
158
  } catch {
122
- step5.fail("Failed to update .env.example");
159
+ step7.fail("Failed to update .env.example");
123
160
  }
124
161
  logger.blank();
125
162
  logger.log(` ${pc.green("\u2713")} ${pc.bold("Convex Auth configured!")}`);
126
163
  logger.blank();
164
+ logger.log(" Created:");
165
+ logger.log(" Backend: convex/auth.ts, convex/auth.config.ts");
166
+ logger.log(" Components: LoginForm, SignupForm, AuthGuard, UserMenu");
167
+ logger.log(" Routes: /login, /signup");
168
+ logger.blank();
127
169
  logger.log(" Next steps:");
128
- logger.log(" 1. Create OAuth apps on GitHub/Google");
129
- logger.log(" 2. Add secrets: npx convex env set AUTH_GITHUB_ID=xxx");
130
- logger.log(" 3. Generate JWT keys: npx @convex-dev/auth");
131
- logger.log(" 4. Run: npx convex dev");
170
+ logger.log(" 1. Set up auth secrets: npx @convex-dev/auth");
171
+ logger.log(" 2. Run: npx convex dev");
172
+ logger.log(" 3. Visit: http://localhost:3000/login");
173
+ logger.blank();
174
+ logger.log(" Usage:");
175
+ logger.log(" - Protect routes: <AuthGuard><YourComponent /></AuthGuard>");
176
+ logger.log(" - Add to header: <UserMenu />");
132
177
  logger.blank();
133
178
  }
134
179
  async function setupStorage(cwd) {
@@ -139,27 +184,52 @@ async function setupStorage(cwd) {
139
184
  }
140
185
  logger.log(` ${pc.bold("Setting up Convex Storage...")}`);
141
186
  logger.blank();
142
- const step1 = logger.step("Creating storage files...");
187
+ await ensureShadcnComponents(cwd, "storage");
188
+ const step1 = logger.step("Creating backend files...");
143
189
  try {
144
- const files = [
190
+ const backendFiles = [
145
191
  { template: "integration/storage/convex/lib/storage.ts.hbs", dest: "convex/lib/storage.ts" },
146
192
  { template: "integration/storage/src/lib/storage.ts.hbs", dest: "src/lib/storage.ts" }
147
193
  ];
148
- for (const file of files) {
194
+ for (const file of backendFiles) {
149
195
  const content = renderTemplate(file.template, {});
150
196
  await writeFile(join(cwd, file.dest), content);
151
197
  }
152
- step1.succeed("Storage files created");
198
+ step1.succeed("Backend files created");
153
199
  } catch (err) {
154
- step1.fail("Failed to create storage files");
200
+ step1.fail("Failed to create backend files");
201
+ throw err;
202
+ }
203
+ const step2 = logger.step("Creating UI components...");
204
+ try {
205
+ const uiFiles = [
206
+ { template: "integration/storage/src/components/storage/FileUpload.tsx.hbs", dest: "src/components/storage/FileUpload.tsx" },
207
+ { template: "integration/storage/src/components/storage/FilePreview.tsx.hbs", dest: "src/components/storage/FilePreview.tsx" },
208
+ { template: "integration/storage/src/components/storage/index.ts.hbs", dest: "src/components/storage/index.ts" }
209
+ ];
210
+ for (const file of uiFiles) {
211
+ const content = renderTemplate(file.template, {});
212
+ await writeFile(join(cwd, file.dest), content);
213
+ }
214
+ step2.succeed("UI components created");
215
+ } catch (err) {
216
+ step2.fail("Failed to create UI components");
155
217
  throw err;
156
218
  }
157
219
  logger.blank();
158
220
  logger.log(` ${pc.green("\u2713")} ${pc.bold("Convex Storage configured!")}`);
159
221
  logger.blank();
160
- logger.log(" Next steps:");
161
- logger.log(" 1. Use generateUploadUrl() in your mutations");
162
- logger.log(" 2. Use useUpload() hook in your components");
222
+ logger.log(" Created:");
223
+ logger.log(" Backend: convex/lib/storage.ts");
224
+ logger.log(" Lib: src/lib/storage.ts");
225
+ logger.log(" Components: FileUpload, FilePreview");
226
+ logger.blank();
227
+ logger.log(" Usage:");
228
+ logger.log(' import { FileUpload, FilePreview } from "~/components/storage";');
229
+ logger.log(' import { useUpload, useFileUrl } from "~/lib/storage";');
230
+ logger.blank();
231
+ logger.log(" <FileUpload onUpload={(id) => console.log(id)} />");
232
+ logger.log(" <FilePreview storageId={someId} />");
163
233
  logger.blank();
164
234
  }
165
235
  async function updateSchemaForAuth(cwd) {
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ensureShadcnComponents
4
+ } from "./chunk-YMJTSRWM.js";
5
+ import {
6
+ kebabCase,
7
+ pascalCase,
8
+ renderTemplate
9
+ } from "./chunk-MBF6K2AC.js";
10
+ import {
11
+ fileExists,
12
+ logger,
13
+ writeFile
14
+ } from "./chunk-HL4K5AHI.js";
15
+
16
+ // src/commands/add-layout.ts
17
+ import { defineCommand } from "citty";
18
+ import { join } from "path";
19
+ var LAYOUT_PRESETS = ["dashboard", "auth", "marketing"];
20
+ var add_layout_default = defineCommand({
21
+ meta: {
22
+ name: "add:layout",
23
+ description: "Create a layout (dashboard, auth, marketing, or custom)"
24
+ },
25
+ args: {
26
+ name: {
27
+ type: "positional",
28
+ description: `Layout name: ${LAYOUT_PRESETS.join(", ")} or custom name`,
29
+ required: true
30
+ }
31
+ },
32
+ async run({ args }) {
33
+ const cwd = process.cwd();
34
+ const name = kebabCase(args.name);
35
+ const isPreset = LAYOUT_PRESETS.includes(name);
36
+ const pascalName = pascalCase(name);
37
+ const routeFolder = name === "auth" ? `(${name})` : name;
38
+ const layoutFile = join(cwd, "src/routes", routeFolder, "_layout.tsx");
39
+ if (await fileExists(layoutFile)) {
40
+ logger.error(`Layout "${name}" already exists`);
41
+ process.exit(1);
42
+ }
43
+ logger.blank();
44
+ if (isPreset && (name === "dashboard" || name === "marketing")) {
45
+ await ensureShadcnComponents(cwd, name);
46
+ }
47
+ const templateData = {
48
+ name,
49
+ pascalName
50
+ };
51
+ const templateFolder = isPreset ? name : "base";
52
+ const layoutContent = renderTemplate(`layout/${templateFolder}/_layout.tsx.hbs`, templateData);
53
+ await writeFile(layoutFile, layoutContent);
54
+ logger.success(`Created src/routes/${routeFolder}/_layout.tsx`);
55
+ if (name === "dashboard") {
56
+ const dashboardComponents = [
57
+ {
58
+ template: "layout/dashboard/components/Sidebar.tsx.hbs",
59
+ dest: "src/components/layout/Sidebar.tsx"
60
+ },
61
+ {
62
+ template: "layout/dashboard/components/Header.tsx.hbs",
63
+ dest: "src/components/layout/Header.tsx"
64
+ },
65
+ {
66
+ template: "layout/dashboard/components/index.ts.hbs",
67
+ dest: "src/components/layout/index.ts"
68
+ }
69
+ ];
70
+ for (const { template, dest } of dashboardComponents) {
71
+ const content = renderTemplate(template, templateData);
72
+ await writeFile(join(cwd, dest), content);
73
+ logger.success(`Created ${dest}`);
74
+ }
75
+ }
76
+ const exampleRoute = join(cwd, "src/routes", routeFolder, "index.tsx");
77
+ if (!await fileExists(exampleRoute)) {
78
+ const exampleContent = renderTemplate(`layout/${templateFolder}/index.tsx.hbs`, templateData);
79
+ await writeFile(exampleRoute, exampleContent);
80
+ logger.success(`Created src/routes/${routeFolder}/index.tsx`);
81
+ }
82
+ logger.blank();
83
+ logger.log(` Layout "${name}" created!`);
84
+ logger.blank();
85
+ logger.log(" Files:");
86
+ logger.log(` - src/routes/${routeFolder}/_layout.tsx`);
87
+ if (name === "dashboard") {
88
+ logger.log(" - src/components/layout/Sidebar.tsx");
89
+ logger.log(" - src/components/layout/Header.tsx");
90
+ }
91
+ logger.blank();
92
+ logger.log(` Routes inside src/routes/${routeFolder}/ will use this layout.`);
93
+ logger.blank();
94
+ if (name === "dashboard") {
95
+ logger.log(" Example: http://localhost:3000/dashboard");
96
+ } else if (name === "auth") {
97
+ logger.log(" Example: Add login.tsx to the route group for /login");
98
+ }
99
+ logger.blank();
100
+ }
101
+ });
102
+ export {
103
+ add_layout_default as default
104
+ };