ai-forge-cli 0.4.2 → 0.4.7
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/dist/add-component-B3O3RZWD.js +120 -0
- package/dist/{add-feature-2AR4DP7P.js → add-feature-TYLPV3DB.js} +38 -9
- package/dist/add-hook-VJC6P6AP.js +103 -0
- package/dist/{add-integration-QXB63S3V.js → add-integration-ESA7JFY7.js} +98 -28
- package/dist/add-layout-IVTJUG6G.js +104 -0
- package/dist/add-page-G75JUU77.js +81 -0
- package/dist/add-util-V5SQRVJC.js +73 -0
- package/dist/{chunk-PIFX2L5H.js → chunk-MBF6K2AC.js} +1 -0
- package/dist/chunk-YMJTSRWM.js +49 -0
- package/dist/index.js +8 -3
- package/dist/{init-OYJP5QCZ.js → init-C4FFZDSP.js} +1 -1
- package/dist/templates/component/Component.tsx.hbs +11 -0
- package/dist/templates/feature/routes/$id.tsx.hbs +3 -13
- package/dist/templates/feature/routes/index.tsx.hbs +19 -9
- package/dist/templates/feature/src/components/Card.tsx.hbs +21 -0
- package/dist/templates/feature/src/components/Detail.tsx.hbs +28 -0
- package/dist/templates/feature/src/components/Form.tsx.hbs +46 -0
- package/dist/templates/feature/src/components/List.tsx.hbs +26 -0
- package/dist/templates/feature/src/components/index.ts.hbs +4 -4
- package/dist/templates/hook/feature-hook.ts.hbs +4 -0
- package/dist/templates/hook/global-hook.ts.hbs +11 -0
- package/dist/templates/init/claude.md.hbs +37 -11
- package/dist/templates/integration/auth/src/components/auth/AuthGuard.tsx.hbs +31 -0
- package/dist/templates/integration/auth/src/components/auth/LoginForm.tsx.hbs +83 -0
- package/dist/templates/integration/auth/src/components/auth/SignupForm.tsx.hbs +102 -0
- package/dist/templates/integration/auth/src/components/auth/UserMenu.tsx.hbs +36 -0
- package/dist/templates/integration/auth/src/components/auth/index.ts.hbs +4 -0
- package/dist/templates/integration/auth/src/routes/login.tsx.hbs +14 -0
- package/dist/templates/integration/auth/src/routes/signup.tsx.hbs +14 -0
- package/dist/templates/integration/storage/src/components/storage/FilePreview.tsx.hbs +18 -0
- package/dist/templates/integration/storage/src/components/storage/FileUpload.tsx.hbs +49 -0
- package/dist/templates/integration/storage/src/components/storage/index.ts.hbs +2 -0
- package/dist/templates/layout/auth/_layout.tsx.hbs +15 -0
- package/dist/templates/layout/auth/index.tsx.hbs +5 -0
- package/dist/templates/layout/base/_layout.tsx.hbs +14 -0
- package/dist/templates/layout/base/index.tsx.hbs +13 -0
- package/dist/templates/layout/dashboard/_layout.tsx.hbs +20 -0
- package/dist/templates/layout/dashboard/components/Header.tsx.hbs +12 -0
- package/dist/templates/layout/dashboard/components/Sidebar.tsx.hbs +39 -0
- package/dist/templates/layout/dashboard/components/index.ts.hbs +2 -0
- package/dist/templates/layout/dashboard/index.tsx.hbs +15 -0
- package/dist/templates/layout/marketing/_layout.tsx.hbs +39 -0
- package/dist/templates/layout/marketing/index.tsx.hbs +16 -0
- package/dist/templates/page/components/Content.tsx.hbs +8 -0
- package/dist/templates/page/components/index.ts.hbs +1 -0
- package/dist/templates/page/route.tsx.hbs +10 -0
- package/dist/templates/util/util.ts.hbs +7 -0
- package/package.json +1 -1
- package/templates/component/Component.tsx.hbs +11 -0
- package/templates/feature/routes/$id.tsx.hbs +3 -13
- package/templates/feature/routes/index.tsx.hbs +19 -9
- package/templates/feature/src/components/Card.tsx.hbs +21 -0
- package/templates/feature/src/components/Detail.tsx.hbs +28 -0
- package/templates/feature/src/components/Form.tsx.hbs +46 -0
- package/templates/feature/src/components/List.tsx.hbs +26 -0
- package/templates/feature/src/components/index.ts.hbs +4 -4
- package/templates/hook/feature-hook.ts.hbs +4 -0
- package/templates/hook/global-hook.ts.hbs +11 -0
- package/templates/init/claude.md.hbs +37 -11
- package/templates/integration/auth/src/components/auth/AuthGuard.tsx.hbs +31 -0
- package/templates/integration/auth/src/components/auth/LoginForm.tsx.hbs +83 -0
- package/templates/integration/auth/src/components/auth/SignupForm.tsx.hbs +102 -0
- package/templates/integration/auth/src/components/auth/UserMenu.tsx.hbs +36 -0
- package/templates/integration/auth/src/components/auth/index.ts.hbs +4 -0
- package/templates/integration/auth/src/routes/login.tsx.hbs +14 -0
- package/templates/integration/auth/src/routes/signup.tsx.hbs +14 -0
- package/templates/integration/storage/src/components/storage/FilePreview.tsx.hbs +18 -0
- package/templates/integration/storage/src/components/storage/FileUpload.tsx.hbs +49 -0
- package/templates/integration/storage/src/components/storage/index.ts.hbs +2 -0
- package/templates/layout/auth/_layout.tsx.hbs +15 -0
- package/templates/layout/auth/index.tsx.hbs +5 -0
- package/templates/layout/base/_layout.tsx.hbs +14 -0
- package/templates/layout/base/index.tsx.hbs +13 -0
- package/templates/layout/dashboard/_layout.tsx.hbs +20 -0
- package/templates/layout/dashboard/components/Header.tsx.hbs +12 -0
- package/templates/layout/dashboard/components/Sidebar.tsx.hbs +39 -0
- package/templates/layout/dashboard/components/index.ts.hbs +2 -0
- package/templates/layout/dashboard/index.tsx.hbs +15 -0
- package/templates/layout/marketing/_layout.tsx.hbs +39 -0
- package/templates/layout/marketing/index.tsx.hbs +16 -0
- package/templates/page/components/Content.tsx.hbs +8 -0
- package/templates/page/components/index.ts.hbs +1 -0
- package/templates/page/route.tsx.hbs +10 -0
- 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 React component. Creates: <target>/<Name>.tsx, updates index.ts. Default: src/components/ui/. Use --feature, --page, or --layout to target specific folders."
|
|
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-
|
|
13
|
+
} from "./chunk-MBF6K2AC.js";
|
|
10
14
|
import {
|
|
11
15
|
fileExists,
|
|
12
16
|
logger,
|
|
@@ -20,7 +24,7 @@ import { join } from "path";
|
|
|
20
24
|
var add_feature_default = defineCommand({
|
|
21
25
|
meta: {
|
|
22
26
|
name: "add:feature",
|
|
23
|
-
description: "Create a
|
|
27
|
+
description: "Create a full-stack feature. Creates: convex/features/<name>/ (schema, queries, mutations), src/features/<name>/ (hooks, components), src/routes/<name>/ (index, $id). Use for data-driven features with backend."
|
|
24
28
|
},
|
|
25
29
|
args: {
|
|
26
30
|
name: {
|
|
@@ -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(
|
|
102
|
-
logger.log(` 3.
|
|
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 React hook. Without --feature: creates src/hooks/<useName>.ts. With --feature: appends to src/features/<name>/hooks.ts. Auto-prefixes 'use' if needed."
|
|
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-
|
|
10
|
+
} from "./chunk-MBF6K2AC.js";
|
|
8
11
|
import {
|
|
9
12
|
fileExists,
|
|
10
13
|
logger,
|
|
@@ -31,7 +34,7 @@ function runCommand(cmd, args, cwd) {
|
|
|
31
34
|
var add_integration_default = defineCommand({
|
|
32
35
|
meta: {
|
|
33
36
|
name: "add:integration",
|
|
34
|
-
description: "Add
|
|
37
|
+
description: "Add infrastructure. auth: creates convex/auth.ts, src/components/auth/ (LoginForm, SignupForm, AuthGuard, UserMenu), routes. storage: creates convex/lib/storage.ts, src/components/storage/ (FileUpload, FilePreview)."
|
|
35
38
|
},
|
|
36
39
|
args: {
|
|
37
40
|
name: {
|
|
@@ -72,38 +75,72 @@ async function setupAuth(cwd) {
|
|
|
72
75
|
step1.fail("Failed to install dependencies");
|
|
73
76
|
process.exit(1);
|
|
74
77
|
}
|
|
75
|
-
|
|
78
|
+
await ensureShadcnComponents(cwd, "auth");
|
|
79
|
+
const step2 = logger.step("Creating backend files...");
|
|
76
80
|
try {
|
|
77
|
-
const
|
|
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
|
|
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
|
-
|
|
124
|
+
step4.succeed("Routes created");
|
|
88
125
|
} catch (err) {
|
|
89
|
-
|
|
126
|
+
step4.fail("Failed to create routes");
|
|
90
127
|
throw err;
|
|
91
128
|
}
|
|
92
|
-
const
|
|
129
|
+
const step5 = logger.step("Updating convex/schema.ts...");
|
|
93
130
|
try {
|
|
94
131
|
await updateSchemaForAuth(cwd);
|
|
95
|
-
|
|
132
|
+
step5.succeed("Schema updated");
|
|
96
133
|
} catch {
|
|
97
|
-
|
|
134
|
+
step5.fail("Failed to update schema");
|
|
98
135
|
}
|
|
99
|
-
const
|
|
136
|
+
const step6 = logger.step("Updating providers...");
|
|
100
137
|
try {
|
|
101
138
|
await updateProvidersForAuth(cwd);
|
|
102
|
-
|
|
139
|
+
step6.succeed("Providers updated");
|
|
103
140
|
} catch {
|
|
104
|
-
|
|
141
|
+
step6.fail("Failed to update providers");
|
|
105
142
|
}
|
|
106
|
-
const
|
|
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
|
-
|
|
157
|
+
step7.succeed(".env.example updated");
|
|
121
158
|
} catch {
|
|
122
|
-
|
|
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.
|
|
129
|
-
logger.log(" 2.
|
|
130
|
-
logger.log(" 3.
|
|
131
|
-
logger.
|
|
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
|
-
|
|
187
|
+
await ensureShadcnComponents(cwd, "storage");
|
|
188
|
+
const step1 = logger.step("Creating backend files...");
|
|
143
189
|
try {
|
|
144
|
-
const
|
|
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
|
|
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("
|
|
198
|
+
step1.succeed("Backend files created");
|
|
153
199
|
} catch (err) {
|
|
154
|
-
step1.fail("Failed to create
|
|
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("
|
|
161
|
-
logger.log("
|
|
162
|
-
logger.log("
|
|
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 wrapper for grouped routes. Creates: src/routes/<name>/_layout.tsx, index.tsx. Dashboard preset adds Sidebar/Header in src/components/layout/. Use for shared navigation."
|
|
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
|
+
};
|