create-nene 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +87 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/default/.cursor/rules/backend-patterns.mdc +68 -0
- package/templates/default/.cursor/rules/frontend-patterns.mdc +53 -0
- package/templates/default/.cursor/rules/nene-architecture.mdc +65 -0
- package/templates/default/.cursor/rules/shared-package.mdc +68 -0
- package/templates/default/.cursor/rules/task-management.mdc +60 -0
- package/templates/default/README.md +42 -25
- package/templates/default/_gitignore +10 -17
- package/templates/default/apps/api/nest-cli.json +8 -0
- package/templates/default/apps/api/package.json +36 -0
- package/templates/default/apps/api/src/app.controller.ts +12 -0
- package/templates/default/apps/api/src/app.module.ts +19 -0
- package/templates/default/apps/api/src/app.service.ts +8 -0
- package/templates/default/apps/api/src/config/configuration.ts +6 -0
- package/templates/default/apps/api/src/health/health.controller.ts +12 -0
- package/templates/default/apps/api/src/health/health.module.ts +9 -0
- package/templates/default/apps/api/src/health/health.service.ts +12 -0
- package/templates/default/apps/api/src/main.ts +32 -0
- package/templates/default/apps/api/tsconfig.json +26 -0
- package/templates/default/apps/web/next.config.ts +7 -0
- package/templates/default/apps/web/package.json +28 -0
- package/templates/default/apps/web/postcss.config.mjs +9 -0
- package/templates/default/apps/web/src/app/globals.css +20 -0
- package/templates/default/apps/web/src/app/layout.tsx +19 -0
- package/templates/default/apps/web/src/app/page.tsx +75 -0
- package/templates/default/apps/web/tailwind.config.ts +15 -0
- package/templates/default/{tsconfig.json → apps/web/tsconfig.json} +11 -8
- package/templates/default/docs/API.md +55 -0
- package/templates/default/docs/ARCHITECTURE.md +56 -0
- package/templates/default/docs/PROGRESS.md +40 -0
- package/templates/default/package.json +12 -17
- package/templates/default/packages/shared/package.json +29 -0
- package/templates/default/packages/shared/src/constants/index.ts +13 -0
- package/templates/default/packages/shared/src/index.ts +3 -0
- package/templates/default/packages/shared/src/types/index.ts +21 -0
- package/templates/default/packages/shared/tsconfig.json +17 -0
- package/templates/default/packages/shared/tsup.config.ts +10 -0
- package/templates/default/pnpm-workspace.yaml +3 -0
- package/templates/default/turbo.json +14 -0
- package/templates/default/eslint.config.mjs +0 -10
- package/templates/default/src/app/layout.tsx +0 -14
- package/templates/default/src/app/page.tsx +0 -29
- package/templates/default/src/server/api/hello.ts +0 -14
- package/templates/default/src/server/index.ts +0 -3
- package/templates/minimal/README.md +0 -11
- package/templates/minimal/_gitignore +0 -6
- package/templates/minimal/package.json +0 -22
- package/templates/minimal/src/app/layout.tsx +0 -9
- package/templates/minimal/src/app/page.tsx +0 -7
- package/templates/minimal/tsconfig.json +0 -19
package/dist/index.js
CHANGED
|
@@ -12,14 +12,6 @@ function validateProjectName(name) {
|
|
|
12
12
|
const validNameRegex = /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
13
13
|
return validNameRegex.test(name);
|
|
14
14
|
}
|
|
15
|
-
function detectPackageManager() {
|
|
16
|
-
const userAgent = process.env.npm_config_user_agent;
|
|
17
|
-
if (userAgent) {
|
|
18
|
-
if (userAgent.startsWith("yarn")) return "yarn";
|
|
19
|
-
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
20
|
-
}
|
|
21
|
-
return "npm";
|
|
22
|
-
}
|
|
23
15
|
function copyDir(src, dest) {
|
|
24
16
|
fs.mkdirSync(dest, { recursive: true });
|
|
25
17
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
@@ -33,12 +25,12 @@ function copyDir(src, dest) {
|
|
|
33
25
|
}
|
|
34
26
|
}
|
|
35
27
|
}
|
|
36
|
-
function getTemplateDir(
|
|
37
|
-
const devPath = path.resolve(__dirname, "..", "templates",
|
|
38
|
-
const prodPath = path.resolve(__dirname, "..", "..", "templates",
|
|
28
|
+
function getTemplateDir() {
|
|
29
|
+
const devPath = path.resolve(__dirname, "..", "templates", "default");
|
|
30
|
+
const prodPath = path.resolve(__dirname, "..", "..", "templates", "default");
|
|
39
31
|
if (fs.existsSync(devPath)) return devPath;
|
|
40
32
|
if (fs.existsSync(prodPath)) return prodPath;
|
|
41
|
-
throw new Error(
|
|
33
|
+
throw new Error("Template not found");
|
|
42
34
|
}
|
|
43
35
|
function updatePackageJson(projectPath, projectName) {
|
|
44
36
|
const pkgPath = path.join(projectPath, "package.json");
|
|
@@ -46,6 +38,57 @@ function updatePackageJson(projectPath, projectName) {
|
|
|
46
38
|
pkg.name = projectName;
|
|
47
39
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
48
40
|
}
|
|
41
|
+
function updateSubPackageNames(projectPath, projectName) {
|
|
42
|
+
const webPkgPath = path.join(projectPath, "apps", "web", "package.json");
|
|
43
|
+
if (fs.existsSync(webPkgPath)) {
|
|
44
|
+
const pkg = JSON.parse(fs.readFileSync(webPkgPath, "utf-8"));
|
|
45
|
+
pkg.name = `@${projectName}/web`;
|
|
46
|
+
if (pkg.dependencies?.["@app/shared"]) {
|
|
47
|
+
pkg.dependencies[`@${projectName}/shared`] = pkg.dependencies["@app/shared"];
|
|
48
|
+
delete pkg.dependencies["@app/shared"];
|
|
49
|
+
}
|
|
50
|
+
fs.writeFileSync(webPkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
51
|
+
}
|
|
52
|
+
const apiPkgPath = path.join(projectPath, "apps", "api", "package.json");
|
|
53
|
+
if (fs.existsSync(apiPkgPath)) {
|
|
54
|
+
const pkg = JSON.parse(fs.readFileSync(apiPkgPath, "utf-8"));
|
|
55
|
+
pkg.name = `@${projectName}/api`;
|
|
56
|
+
if (pkg.dependencies?.["@app/shared"]) {
|
|
57
|
+
pkg.dependencies[`@${projectName}/shared`] = pkg.dependencies["@app/shared"];
|
|
58
|
+
delete pkg.dependencies["@app/shared"];
|
|
59
|
+
}
|
|
60
|
+
fs.writeFileSync(apiPkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
61
|
+
}
|
|
62
|
+
const sharedPkgPath = path.join(projectPath, "packages", "shared", "package.json");
|
|
63
|
+
if (fs.existsSync(sharedPkgPath)) {
|
|
64
|
+
const pkg = JSON.parse(fs.readFileSync(sharedPkgPath, "utf-8"));
|
|
65
|
+
pkg.name = `@${projectName}/shared`;
|
|
66
|
+
fs.writeFileSync(sharedPkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
67
|
+
}
|
|
68
|
+
const pagePath = path.join(projectPath, "apps", "web", "src", "app", "page.tsx");
|
|
69
|
+
if (fs.existsSync(pagePath)) {
|
|
70
|
+
let content = fs.readFileSync(pagePath, "utf-8");
|
|
71
|
+
content = content.replace(/@app\/shared/g, `@${projectName}/shared`);
|
|
72
|
+
fs.writeFileSync(pagePath, content);
|
|
73
|
+
}
|
|
74
|
+
const cursorRulesDir = path.join(projectPath, ".cursor", "rules");
|
|
75
|
+
if (fs.existsSync(cursorRulesDir)) {
|
|
76
|
+
const ruleFiles = fs.readdirSync(cursorRulesDir);
|
|
77
|
+
for (const file of ruleFiles) {
|
|
78
|
+
const filePath = path.join(cursorRulesDir, file);
|
|
79
|
+
let content = fs.readFileSync(filePath, "utf-8");
|
|
80
|
+
content = content.replace(/@app\/shared/g, `@${projectName}/shared`);
|
|
81
|
+
fs.writeFileSync(filePath, content);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const rootPkgPath = path.join(projectPath, "package.json");
|
|
85
|
+
if (fs.existsSync(rootPkgPath)) {
|
|
86
|
+
let content = fs.readFileSync(rootPkgPath, "utf-8");
|
|
87
|
+
content = content.replace(/@app\/web/g, `@${projectName}/web`);
|
|
88
|
+
content = content.replace(/@app\/api/g, `@${projectName}/api`);
|
|
89
|
+
fs.writeFileSync(rootPkgPath, content);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
49
92
|
function renameGitignore(projectPath) {
|
|
50
93
|
const gitignorePath = path.join(projectPath, "_gitignore");
|
|
51
94
|
const targetPath = path.join(projectPath, ".gitignore");
|
|
@@ -70,46 +113,16 @@ async function promptForOptions(projectName) {
|
|
|
70
113
|
return true;
|
|
71
114
|
}
|
|
72
115
|
},
|
|
73
|
-
{
|
|
74
|
-
type: "select",
|
|
75
|
-
name: "template",
|
|
76
|
-
message: "Select a template:",
|
|
77
|
-
choices: [
|
|
78
|
-
{
|
|
79
|
-
title: "Default",
|
|
80
|
-
description: "Full-stack with unified frontend and backend",
|
|
81
|
-
value: "default"
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
title: "Minimal",
|
|
85
|
-
description: "Minimal setup for quick prototyping",
|
|
86
|
-
value: "minimal"
|
|
87
|
-
}
|
|
88
|
-
],
|
|
89
|
-
initial: 0
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
type: "confirm",
|
|
93
|
-
name: "typescript",
|
|
94
|
-
message: "Use TypeScript?",
|
|
95
|
-
initial: true
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
type: "confirm",
|
|
99
|
-
name: "eslint",
|
|
100
|
-
message: "Use ESLint?",
|
|
101
|
-
initial: true
|
|
102
|
-
},
|
|
103
116
|
{
|
|
104
117
|
type: "select",
|
|
105
118
|
name: "packageManager",
|
|
106
119
|
message: "Select a package manager:",
|
|
107
120
|
choices: [
|
|
121
|
+
{ title: "pnpm (recommended)", value: "pnpm" },
|
|
108
122
|
{ title: "npm", value: "npm" },
|
|
109
|
-
{ title: "yarn", value: "yarn" }
|
|
110
|
-
{ title: "pnpm", value: "pnpm" }
|
|
123
|
+
{ title: "yarn", value: "yarn" }
|
|
111
124
|
],
|
|
112
|
-
initial:
|
|
125
|
+
initial: 0
|
|
113
126
|
}
|
|
114
127
|
],
|
|
115
128
|
{
|
|
@@ -121,14 +134,11 @@ async function promptForOptions(projectName) {
|
|
|
121
134
|
);
|
|
122
135
|
return {
|
|
123
136
|
projectName: projectName || response.projectName,
|
|
124
|
-
template: response.template,
|
|
125
|
-
typescript: response.typescript,
|
|
126
|
-
eslint: response.eslint,
|
|
127
137
|
packageManager: response.packageManager
|
|
128
138
|
};
|
|
129
139
|
}
|
|
130
140
|
async function createProject(options) {
|
|
131
|
-
const { projectName,
|
|
141
|
+
const { projectName, packageManager } = options;
|
|
132
142
|
const projectPath = path.resolve(process.cwd(), projectName);
|
|
133
143
|
if (fs.existsSync(projectPath)) {
|
|
134
144
|
const { overwrite } = await prompts({
|
|
@@ -145,13 +155,14 @@ async function createProject(options) {
|
|
|
145
155
|
}
|
|
146
156
|
console.log();
|
|
147
157
|
console.log(
|
|
148
|
-
pc.cyan(`Creating a new nene.js
|
|
158
|
+
pc.cyan(`Creating a new nene.js monorepo in ${pc.bold(projectPath)}`)
|
|
149
159
|
);
|
|
150
160
|
console.log();
|
|
151
|
-
const templateDir = getTemplateDir(
|
|
161
|
+
const templateDir = getTemplateDir();
|
|
152
162
|
copyDir(templateDir, projectPath);
|
|
153
163
|
renameGitignore(projectPath);
|
|
154
164
|
updatePackageJson(projectPath, projectName);
|
|
165
|
+
updateSubPackageNames(projectPath, projectName);
|
|
155
166
|
console.log(pc.cyan("Installing dependencies..."));
|
|
156
167
|
console.log();
|
|
157
168
|
const installCmd = {
|
|
@@ -172,10 +183,31 @@ async function createProject(options) {
|
|
|
172
183
|
);
|
|
173
184
|
}
|
|
174
185
|
console.log();
|
|
186
|
+
console.log(pc.cyan("Building shared package..."));
|
|
187
|
+
try {
|
|
188
|
+
const buildCmd = packageManager === "npm" ? "npm run build" : `${packageManager} build`;
|
|
189
|
+
execSync(buildCmd, {
|
|
190
|
+
cwd: path.join(projectPath, "packages", "shared"),
|
|
191
|
+
stdio: "inherit"
|
|
192
|
+
});
|
|
193
|
+
} catch {
|
|
194
|
+
console.log(
|
|
195
|
+
pc.yellow("\nFailed to build shared package. Run 'pnpm build' in packages/shared manually.")
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
console.log();
|
|
175
199
|
console.log(
|
|
176
200
|
pc.green("Success!") + ` Created ${pc.bold(projectName)} at ${projectPath}`
|
|
177
201
|
);
|
|
178
202
|
console.log();
|
|
203
|
+
console.log("Project structure:");
|
|
204
|
+
console.log();
|
|
205
|
+
console.log(` ${pc.cyan("apps/web")} - Next.js frontend (port 3000)`);
|
|
206
|
+
console.log(` ${pc.cyan("apps/api")} - NestJS backend (port 4000)`);
|
|
207
|
+
console.log(` ${pc.cyan("packages/shared")} - Shared types and constants`);
|
|
208
|
+
console.log(` ${pc.cyan("docs/")} - Project documentation`);
|
|
209
|
+
console.log(` ${pc.cyan(".cursor/rules/")} - Cursor AI agent rules`);
|
|
210
|
+
console.log();
|
|
179
211
|
console.log("Next steps:");
|
|
180
212
|
console.log();
|
|
181
213
|
console.log(` ${pc.cyan("cd")} ${projectName}`);
|
|
@@ -183,15 +215,18 @@ async function createProject(options) {
|
|
|
183
215
|
` ${pc.cyan(packageManager === "npm" ? "npm run" : packageManager)} dev`
|
|
184
216
|
);
|
|
185
217
|
console.log();
|
|
218
|
+
console.log("This will start both the frontend (port 3000) and backend (port 4000).");
|
|
219
|
+
console.log();
|
|
186
220
|
console.log("Happy coding!");
|
|
187
221
|
console.log();
|
|
188
222
|
}
|
|
189
223
|
async function main() {
|
|
190
|
-
program.name("create-nene").description("Create a new nene.js
|
|
224
|
+
program.name("create-nene").description("Create a new nene.js monorepo with Next.js and NestJS").version("0.2.0").argument("[project-name]", "Name of the project").action(async (projectName) => {
|
|
191
225
|
console.log();
|
|
192
226
|
console.log(
|
|
193
227
|
pc.bold(pc.cyan(" nene.js ") + "- The AI-native full-stack framework")
|
|
194
228
|
);
|
|
229
|
+
console.log(pc.dim(" Next.js + NestJS monorepo for AI-assisted development"));
|
|
195
230
|
console.log();
|
|
196
231
|
const options = await promptForOptions(projectName);
|
|
197
232
|
await createProject(options);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { program } from \"commander\";\nimport prompts from \"prompts\";\nimport pc from \"picocolors\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { execSync } from \"node:child_process\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ninterface ProjectOptions {\n projectName: string;\n template: \"default\" | \"minimal\";\n typescript: boolean;\n eslint: boolean;\n packageManager: \"npm\" | \"yarn\" | \"pnpm\";\n}\n\nfunction validateProjectName(name: string): boolean {\n const validNameRegex =\n /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/;\n return validNameRegex.test(name);\n}\n\nfunction detectPackageManager(): \"npm\" | \"yarn\" | \"pnpm\" {\n const userAgent = process.env.npm_config_user_agent;\n if (userAgent) {\n if (userAgent.startsWith(\"yarn\")) return \"yarn\";\n if (userAgent.startsWith(\"pnpm\")) return \"pnpm\";\n }\n return \"npm\";\n}\n\nfunction copyDir(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n const entries = fs.readdirSync(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry.name);\n const destPath = path.join(dest, entry.name);\n\n if (entry.isDirectory()) {\n copyDir(srcPath, destPath);\n } else {\n fs.copyFileSync(srcPath, destPath);\n }\n }\n}\n\nfunction getTemplateDir(template: string): string {\n // In development, templates are relative to src\n // In production (dist), templates are at the package root\n const devPath = path.resolve(__dirname, \"..\", \"templates\", template);\n const prodPath = path.resolve(__dirname, \"..\", \"..\", \"templates\", template);\n\n if (fs.existsSync(devPath)) return devPath;\n if (fs.existsSync(prodPath)) return prodPath;\n\n throw new Error(`Template \"${template}\" not found`);\n}\n\nfunction updatePackageJson(projectPath: string, projectName: string): void {\n const pkgPath = path.join(projectPath, \"package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\"));\n pkg.name = projectName;\n fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + \"\\n\");\n}\n\nfunction renameGitignore(projectPath: string): void {\n const gitignorePath = path.join(projectPath, \"_gitignore\");\n const targetPath = path.join(projectPath, \".gitignore\");\n if (fs.existsSync(gitignorePath)) {\n fs.renameSync(gitignorePath, targetPath);\n }\n}\n\nasync function promptForOptions(projectName?: string): Promise<ProjectOptions> {\n const defaultProjectName = projectName || \"my-nene-app\";\n\n const response = await prompts(\n [\n {\n type: projectName ? null : \"text\",\n name: \"projectName\",\n message: \"Project name:\",\n initial: defaultProjectName,\n validate: (value: string) => {\n if (!value) return \"Project name is required\";\n if (!validateProjectName(value)) {\n return \"Invalid project name. Use lowercase letters, numbers, and hyphens only.\";\n }\n return true;\n },\n },\n {\n type: \"select\",\n name: \"template\",\n message: \"Select a template:\",\n choices: [\n {\n title: \"Default\",\n description: \"Full-stack with unified frontend and backend\",\n value: \"default\",\n },\n {\n title: \"Minimal\",\n description: \"Minimal setup for quick prototyping\",\n value: \"minimal\",\n },\n ],\n initial: 0,\n },\n {\n type: \"confirm\",\n name: \"typescript\",\n message: \"Use TypeScript?\",\n initial: true,\n },\n {\n type: \"confirm\",\n name: \"eslint\",\n message: \"Use ESLint?\",\n initial: true,\n },\n {\n type: \"select\",\n name: \"packageManager\",\n message: \"Select a package manager:\",\n choices: [\n { title: \"npm\", value: \"npm\" },\n { title: \"yarn\", value: \"yarn\" },\n { title: \"pnpm\", value: \"pnpm\" },\n ],\n initial: [\"npm\", \"yarn\", \"pnpm\"].indexOf(detectPackageManager()),\n },\n ],\n {\n onCancel: () => {\n console.log(pc.red(\"\\nOperation cancelled.\"));\n process.exit(0);\n },\n }\n );\n\n return {\n projectName: projectName || response.projectName,\n template: response.template,\n typescript: response.typescript,\n eslint: response.eslint,\n packageManager: response.packageManager,\n };\n}\n\nasync function createProject(options: ProjectOptions): Promise<void> {\n const { projectName, template, packageManager } = options;\n const projectPath = path.resolve(process.cwd(), projectName);\n\n // Check if directory exists\n if (fs.existsSync(projectPath)) {\n const { overwrite } = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Directory \"${projectName}\" already exists. Overwrite?`,\n initial: false,\n });\n\n if (!overwrite) {\n console.log(pc.red(\"Operation cancelled.\"));\n process.exit(0);\n }\n\n fs.rmSync(projectPath, { recursive: true, force: true });\n }\n\n console.log();\n console.log(\n pc.cyan(`Creating a new nene.js project in ${pc.bold(projectPath)}`)\n );\n console.log();\n\n // Copy template\n const templateDir = getTemplateDir(template);\n copyDir(templateDir, projectPath);\n\n // Rename _gitignore to .gitignore\n renameGitignore(projectPath);\n\n // Update package.json with project name\n updatePackageJson(projectPath, projectName);\n\n // Install dependencies\n console.log(pc.cyan(\"Installing dependencies...\"));\n console.log();\n\n const installCmd = {\n npm: \"npm install\",\n yarn: \"yarn\",\n pnpm: \"pnpm install\",\n }[packageManager];\n\n try {\n execSync(installCmd, {\n cwd: projectPath,\n stdio: \"inherit\",\n });\n } catch {\n console.log(\n pc.yellow(\n \"\\nFailed to install dependencies. You can install them manually.\"\n )\n );\n }\n\n // Success message\n console.log();\n console.log(\n pc.green(\"Success!\") + ` Created ${pc.bold(projectName)} at ${projectPath}`\n );\n console.log();\n console.log(\"Next steps:\");\n console.log();\n console.log(` ${pc.cyan(\"cd\")} ${projectName}`);\n console.log(\n ` ${pc.cyan(packageManager === \"npm\" ? \"npm run\" : packageManager)} dev`\n );\n console.log();\n console.log(\"Happy coding!\");\n console.log();\n}\n\nexport async function main(): Promise<void> {\n program\n .name(\"create-nene\")\n .description(\"Create a new nene.js project\")\n .version(\"0.1.0\")\n .argument(\"[project-name]\", \"Name of the project\")\n .option(\"-t, --template <template>\", \"Template to use (default, minimal)\")\n .option(\"--typescript\", \"Use TypeScript (default: true)\")\n .option(\"--no-typescript\", \"Do not use TypeScript\")\n .option(\"--eslint\", \"Use ESLint (default: true)\")\n .option(\"--no-eslint\", \"Do not use ESLint\")\n .action(async (projectName: string | undefined) => {\n console.log();\n console.log(\n pc.bold(pc.cyan(\" nene.js \") + \"- The AI-native full-stack framework\")\n );\n console.log();\n\n const options = await promptForOptions(projectName);\n await createProject(options);\n });\n\n await program.parseAsync(process.argv);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { program } from \"commander\";\nimport prompts from \"prompts\";\nimport pc from \"picocolors\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { execSync } from \"node:child_process\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ninterface ProjectOptions {\n projectName: string;\n packageManager: \"npm\" | \"yarn\" | \"pnpm\";\n}\n\nfunction validateProjectName(name: string): boolean {\n const validNameRegex =\n /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/;\n return validNameRegex.test(name);\n}\n\nfunction detectPackageManager(): \"npm\" | \"yarn\" | \"pnpm\" {\n const userAgent = process.env.npm_config_user_agent;\n if (userAgent) {\n if (userAgent.startsWith(\"yarn\")) return \"yarn\";\n if (userAgent.startsWith(\"pnpm\")) return \"pnpm\";\n }\n return \"pnpm\"; // Default to pnpm for monorepo\n}\n\nfunction copyDir(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n const entries = fs.readdirSync(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry.name);\n const destPath = path.join(dest, entry.name);\n\n if (entry.isDirectory()) {\n copyDir(srcPath, destPath);\n } else {\n fs.copyFileSync(srcPath, destPath);\n }\n }\n}\n\nfunction getTemplateDir(): string {\n // In development, templates are relative to src\n // In production (dist), templates are at the package root\n const devPath = path.resolve(__dirname, \"..\", \"templates\", \"default\");\n const prodPath = path.resolve(__dirname, \"..\", \"..\", \"templates\", \"default\");\n\n if (fs.existsSync(devPath)) return devPath;\n if (fs.existsSync(prodPath)) return prodPath;\n\n throw new Error(\"Template not found\");\n}\n\nfunction updatePackageJson(projectPath: string, projectName: string): void {\n const pkgPath = path.join(projectPath, \"package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\"));\n pkg.name = projectName;\n fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + \"\\n\");\n}\n\nfunction updateSubPackageNames(projectPath: string, projectName: string): void {\n // Update apps/web package.json\n const webPkgPath = path.join(projectPath, \"apps\", \"web\", \"package.json\");\n if (fs.existsSync(webPkgPath)) {\n const pkg = JSON.parse(fs.readFileSync(webPkgPath, \"utf-8\"));\n pkg.name = `@${projectName}/web`;\n // Update shared package reference\n if (pkg.dependencies?.[\"@app/shared\"]) {\n pkg.dependencies[`@${projectName}/shared`] = pkg.dependencies[\"@app/shared\"];\n delete pkg.dependencies[\"@app/shared\"];\n }\n fs.writeFileSync(webPkgPath, JSON.stringify(pkg, null, 2) + \"\\n\");\n }\n\n // Update apps/api package.json\n const apiPkgPath = path.join(projectPath, \"apps\", \"api\", \"package.json\");\n if (fs.existsSync(apiPkgPath)) {\n const pkg = JSON.parse(fs.readFileSync(apiPkgPath, \"utf-8\"));\n pkg.name = `@${projectName}/api`;\n // Update shared package reference\n if (pkg.dependencies?.[\"@app/shared\"]) {\n pkg.dependencies[`@${projectName}/shared`] = pkg.dependencies[\"@app/shared\"];\n delete pkg.dependencies[\"@app/shared\"];\n }\n fs.writeFileSync(apiPkgPath, JSON.stringify(pkg, null, 2) + \"\\n\");\n }\n\n // Update packages/shared package.json\n const sharedPkgPath = path.join(projectPath, \"packages\", \"shared\", \"package.json\");\n if (fs.existsSync(sharedPkgPath)) {\n const pkg = JSON.parse(fs.readFileSync(sharedPkgPath, \"utf-8\"));\n pkg.name = `@${projectName}/shared`;\n fs.writeFileSync(sharedPkgPath, JSON.stringify(pkg, null, 2) + \"\\n\");\n }\n\n // Update import statements in apps/web/src/app/page.tsx\n const pagePath = path.join(projectPath, \"apps\", \"web\", \"src\", \"app\", \"page.tsx\");\n if (fs.existsSync(pagePath)) {\n let content = fs.readFileSync(pagePath, \"utf-8\");\n content = content.replace(/@app\\/shared/g, `@${projectName}/shared`);\n fs.writeFileSync(pagePath, content);\n }\n\n // Update cursor rules to use correct package names\n const cursorRulesDir = path.join(projectPath, \".cursor\", \"rules\");\n if (fs.existsSync(cursorRulesDir)) {\n const ruleFiles = fs.readdirSync(cursorRulesDir);\n for (const file of ruleFiles) {\n const filePath = path.join(cursorRulesDir, file);\n let content = fs.readFileSync(filePath, \"utf-8\");\n content = content.replace(/@app\\/shared/g, `@${projectName}/shared`);\n fs.writeFileSync(filePath, content);\n }\n }\n\n // Update turbo.json filter names\n const rootPkgPath = path.join(projectPath, \"package.json\");\n if (fs.existsSync(rootPkgPath)) {\n let content = fs.readFileSync(rootPkgPath, \"utf-8\");\n content = content.replace(/@app\\/web/g, `@${projectName}/web`);\n content = content.replace(/@app\\/api/g, `@${projectName}/api`);\n fs.writeFileSync(rootPkgPath, content);\n }\n}\n\nfunction renameGitignore(projectPath: string): void {\n const gitignorePath = path.join(projectPath, \"_gitignore\");\n const targetPath = path.join(projectPath, \".gitignore\");\n if (fs.existsSync(gitignorePath)) {\n fs.renameSync(gitignorePath, targetPath);\n }\n}\n\nasync function promptForOptions(projectName?: string): Promise<ProjectOptions> {\n const defaultProjectName = projectName || \"my-nene-app\";\n\n const response = await prompts(\n [\n {\n type: projectName ? null : \"text\",\n name: \"projectName\",\n message: \"Project name:\",\n initial: defaultProjectName,\n validate: (value: string) => {\n if (!value) return \"Project name is required\";\n if (!validateProjectName(value)) {\n return \"Invalid project name. Use lowercase letters, numbers, and hyphens only.\";\n }\n return true;\n },\n },\n {\n type: \"select\",\n name: \"packageManager\",\n message: \"Select a package manager:\",\n choices: [\n { title: \"pnpm (recommended)\", value: \"pnpm\" },\n { title: \"npm\", value: \"npm\" },\n { title: \"yarn\", value: \"yarn\" },\n ],\n initial: 0,\n },\n ],\n {\n onCancel: () => {\n console.log(pc.red(\"\\nOperation cancelled.\"));\n process.exit(0);\n },\n }\n );\n\n return {\n projectName: projectName || response.projectName,\n packageManager: response.packageManager,\n };\n}\n\nasync function createProject(options: ProjectOptions): Promise<void> {\n const { projectName, packageManager } = options;\n const projectPath = path.resolve(process.cwd(), projectName);\n\n // Check if directory exists\n if (fs.existsSync(projectPath)) {\n const { overwrite } = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Directory \"${projectName}\" already exists. Overwrite?`,\n initial: false,\n });\n\n if (!overwrite) {\n console.log(pc.red(\"Operation cancelled.\"));\n process.exit(0);\n }\n\n fs.rmSync(projectPath, { recursive: true, force: true });\n }\n\n console.log();\n console.log(\n pc.cyan(`Creating a new nene.js monorepo in ${pc.bold(projectPath)}`)\n );\n console.log();\n\n // Copy template\n const templateDir = getTemplateDir();\n copyDir(templateDir, projectPath);\n\n // Rename _gitignore to .gitignore\n renameGitignore(projectPath);\n\n // Update package.json with project name\n updatePackageJson(projectPath, projectName);\n\n // Update sub-package names\n updateSubPackageNames(projectPath, projectName);\n\n // Install dependencies\n console.log(pc.cyan(\"Installing dependencies...\"));\n console.log();\n\n const installCmd = {\n npm: \"npm install\",\n yarn: \"yarn\",\n pnpm: \"pnpm install\",\n }[packageManager];\n\n try {\n execSync(installCmd, {\n cwd: projectPath,\n stdio: \"inherit\",\n });\n } catch {\n console.log(\n pc.yellow(\n \"\\nFailed to install dependencies. You can install them manually.\"\n )\n );\n }\n\n // Build shared package first\n console.log();\n console.log(pc.cyan(\"Building shared package...\"));\n \n try {\n const buildCmd = packageManager === \"npm\" ? \"npm run build\" : `${packageManager} build`;\n execSync(buildCmd, {\n cwd: path.join(projectPath, \"packages\", \"shared\"),\n stdio: \"inherit\",\n });\n } catch {\n console.log(\n pc.yellow(\"\\nFailed to build shared package. Run 'pnpm build' in packages/shared manually.\")\n );\n }\n\n // Success message\n console.log();\n console.log(\n pc.green(\"Success!\") + ` Created ${pc.bold(projectName)} at ${projectPath}`\n );\n console.log();\n console.log(\"Project structure:\");\n console.log();\n console.log(` ${pc.cyan(\"apps/web\")} - Next.js frontend (port 3000)`);\n console.log(` ${pc.cyan(\"apps/api\")} - NestJS backend (port 4000)`);\n console.log(` ${pc.cyan(\"packages/shared\")} - Shared types and constants`);\n console.log(` ${pc.cyan(\"docs/\")} - Project documentation`);\n console.log(` ${pc.cyan(\".cursor/rules/\")} - Cursor AI agent rules`);\n console.log();\n console.log(\"Next steps:\");\n console.log();\n console.log(` ${pc.cyan(\"cd\")} ${projectName}`);\n console.log(\n ` ${pc.cyan(packageManager === \"npm\" ? \"npm run\" : packageManager)} dev`\n );\n console.log();\n console.log(\"This will start both the frontend (port 3000) and backend (port 4000).\");\n console.log();\n console.log(\"Happy coding!\");\n console.log();\n}\n\nexport async function main(): Promise<void> {\n program\n .name(\"create-nene\")\n .description(\"Create a new nene.js monorepo with Next.js and NestJS\")\n .version(\"0.2.0\")\n .argument(\"[project-name]\", \"Name of the project\")\n .action(async (projectName: string | undefined) => {\n console.log();\n console.log(\n pc.bold(pc.cyan(\" nene.js \") + \"- The AI-native full-stack framework\")\n );\n console.log(pc.dim(\" Next.js + NestJS monorepo for AI-assisted development\"));\n console.log();\n\n const options = await promptForOptions(projectName);\n await createProject(options);\n });\n\n await program.parseAsync(process.argv);\n}\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,OAAO,aAAa;AACpB,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AAEzB,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,KAAK,QAAQ,UAAU;AAOzC,SAAS,oBAAoB,MAAuB;AAClD,QAAM,iBACJ;AACF,SAAO,eAAe,KAAK,IAAI;AACjC;AAWA,SAAS,QAAQ,KAAa,MAAoB;AAChD,KAAG,UAAU,MAAM,EAAE,WAAW,KAAK,CAAC;AACtC,QAAM,UAAU,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAE3D,aAAW,SAAS,SAAS;AAC3B,UAAM,UAAU,KAAK,KAAK,KAAK,MAAM,IAAI;AACzC,UAAM,WAAW,KAAK,KAAK,MAAM,MAAM,IAAI;AAE3C,QAAI,MAAM,YAAY,GAAG;AACvB,cAAQ,SAAS,QAAQ;AAAA,IAC3B,OAAO;AACL,SAAG,aAAa,SAAS,QAAQ;AAAA,IACnC;AAAA,EACF;AACF;AAEA,SAAS,iBAAyB;AAGhC,QAAM,UAAU,KAAK,QAAQ,WAAW,MAAM,aAAa,SAAS;AACpE,QAAM,WAAW,KAAK,QAAQ,WAAW,MAAM,MAAM,aAAa,SAAS;AAE3E,MAAI,GAAG,WAAW,OAAO,EAAG,QAAO;AACnC,MAAI,GAAG,WAAW,QAAQ,EAAG,QAAO;AAEpC,QAAM,IAAI,MAAM,oBAAoB;AACtC;AAEA,SAAS,kBAAkB,aAAqB,aAA2B;AACzE,QAAM,UAAU,KAAK,KAAK,aAAa,cAAc;AACrD,QAAM,MAAM,KAAK,MAAM,GAAG,aAAa,SAAS,OAAO,CAAC;AACxD,MAAI,OAAO;AACX,KAAG,cAAc,SAAS,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,IAAI;AAC/D;AAEA,SAAS,sBAAsB,aAAqB,aAA2B;AAE7E,QAAM,aAAa,KAAK,KAAK,aAAa,QAAQ,OAAO,cAAc;AACvE,MAAI,GAAG,WAAW,UAAU,GAAG;AAC7B,UAAM,MAAM,KAAK,MAAM,GAAG,aAAa,YAAY,OAAO,CAAC;AAC3D,QAAI,OAAO,IAAI,WAAW;AAE1B,QAAI,IAAI,eAAe,aAAa,GAAG;AACrC,UAAI,aAAa,IAAI,WAAW,SAAS,IAAI,IAAI,aAAa,aAAa;AAC3E,aAAO,IAAI,aAAa,aAAa;AAAA,IACvC;AACA,OAAG,cAAc,YAAY,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,IAAI;AAAA,EAClE;AAGA,QAAM,aAAa,KAAK,KAAK,aAAa,QAAQ,OAAO,cAAc;AACvE,MAAI,GAAG,WAAW,UAAU,GAAG;AAC7B,UAAM,MAAM,KAAK,MAAM,GAAG,aAAa,YAAY,OAAO,CAAC;AAC3D,QAAI,OAAO,IAAI,WAAW;AAE1B,QAAI,IAAI,eAAe,aAAa,GAAG;AACrC,UAAI,aAAa,IAAI,WAAW,SAAS,IAAI,IAAI,aAAa,aAAa;AAC3E,aAAO,IAAI,aAAa,aAAa;AAAA,IACvC;AACA,OAAG,cAAc,YAAY,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,IAAI;AAAA,EAClE;AAGA,QAAM,gBAAgB,KAAK,KAAK,aAAa,YAAY,UAAU,cAAc;AACjF,MAAI,GAAG,WAAW,aAAa,GAAG;AAChC,UAAM,MAAM,KAAK,MAAM,GAAG,aAAa,eAAe,OAAO,CAAC;AAC9D,QAAI,OAAO,IAAI,WAAW;AAC1B,OAAG,cAAc,eAAe,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,IAAI;AAAA,EACrE;AAGA,QAAM,WAAW,KAAK,KAAK,aAAa,QAAQ,OAAO,OAAO,OAAO,UAAU;AAC/E,MAAI,GAAG,WAAW,QAAQ,GAAG;AAC3B,QAAI,UAAU,GAAG,aAAa,UAAU,OAAO;AAC/C,cAAU,QAAQ,QAAQ,iBAAiB,IAAI,WAAW,SAAS;AACnE,OAAG,cAAc,UAAU,OAAO;AAAA,EACpC;AAGA,QAAM,iBAAiB,KAAK,KAAK,aAAa,WAAW,OAAO;AAChE,MAAI,GAAG,WAAW,cAAc,GAAG;AACjC,UAAM,YAAY,GAAG,YAAY,cAAc;AAC/C,eAAW,QAAQ,WAAW;AAC5B,YAAM,WAAW,KAAK,KAAK,gBAAgB,IAAI;AAC/C,UAAI,UAAU,GAAG,aAAa,UAAU,OAAO;AAC/C,gBAAU,QAAQ,QAAQ,iBAAiB,IAAI,WAAW,SAAS;AACnE,SAAG,cAAc,UAAU,OAAO;AAAA,IACpC;AAAA,EACF;AAGA,QAAM,cAAc,KAAK,KAAK,aAAa,cAAc;AACzD,MAAI,GAAG,WAAW,WAAW,GAAG;AAC9B,QAAI,UAAU,GAAG,aAAa,aAAa,OAAO;AAClD,cAAU,QAAQ,QAAQ,cAAc,IAAI,WAAW,MAAM;AAC7D,cAAU,QAAQ,QAAQ,cAAc,IAAI,WAAW,MAAM;AAC7D,OAAG,cAAc,aAAa,OAAO;AAAA,EACvC;AACF;AAEA,SAAS,gBAAgB,aAA2B;AAClD,QAAM,gBAAgB,KAAK,KAAK,aAAa,YAAY;AACzD,QAAM,aAAa,KAAK,KAAK,aAAa,YAAY;AACtD,MAAI,GAAG,WAAW,aAAa,GAAG;AAChC,OAAG,WAAW,eAAe,UAAU;AAAA,EACzC;AACF;AAEA,eAAe,iBAAiB,aAA+C;AAC7E,QAAM,qBAAqB,eAAe;AAE1C,QAAM,WAAW,MAAM;AAAA,IACrB;AAAA,MACE;AAAA,QACE,MAAM,cAAc,OAAO;AAAA,QAC3B,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,QACT,UAAU,CAAC,UAAkB;AAC3B,cAAI,CAAC,MAAO,QAAO;AACnB,cAAI,CAAC,oBAAoB,KAAK,GAAG;AAC/B,mBAAO;AAAA,UACT;AACA,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,UACP,EAAE,OAAO,sBAAsB,OAAO,OAAO;AAAA,UAC7C,EAAE,OAAO,OAAO,OAAO,MAAM;AAAA,UAC7B,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,QACjC;AAAA,QACA,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA;AAAA,MACE,UAAU,MAAM;AACd,gBAAQ,IAAI,GAAG,IAAI,wBAAwB,CAAC;AAC5C,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,aAAa,eAAe,SAAS;AAAA,IACrC,gBAAgB,SAAS;AAAA,EAC3B;AACF;AAEA,eAAe,cAAc,SAAwC;AACnE,QAAM,EAAE,aAAa,eAAe,IAAI;AACxC,QAAM,cAAc,KAAK,QAAQ,QAAQ,IAAI,GAAG,WAAW;AAG3D,MAAI,GAAG,WAAW,WAAW,GAAG;AAC9B,UAAM,EAAE,UAAU,IAAI,MAAM,QAAQ;AAAA,MAClC,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS,cAAc,WAAW;AAAA,MAClC,SAAS;AAAA,IACX,CAAC;AAED,QAAI,CAAC,WAAW;AACd,cAAQ,IAAI,GAAG,IAAI,sBAAsB,CAAC;AAC1C,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,OAAG,OAAO,aAAa,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACzD;AAEA,UAAQ,IAAI;AACZ,UAAQ;AAAA,IACN,GAAG,KAAK,sCAAsC,GAAG,KAAK,WAAW,CAAC,EAAE;AAAA,EACtE;AACA,UAAQ,IAAI;AAGZ,QAAM,cAAc,eAAe;AACnC,UAAQ,aAAa,WAAW;AAGhC,kBAAgB,WAAW;AAG3B,oBAAkB,aAAa,WAAW;AAG1C,wBAAsB,aAAa,WAAW;AAG9C,UAAQ,IAAI,GAAG,KAAK,4BAA4B,CAAC;AACjD,UAAQ,IAAI;AAEZ,QAAM,aAAa;AAAA,IACjB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,EACR,EAAE,cAAc;AAEhB,MAAI;AACF,aAAS,YAAY;AAAA,MACnB,KAAK;AAAA,MACL,OAAO;AAAA,IACT,CAAC;AAAA,EACH,QAAQ;AACN,YAAQ;AAAA,MACN,GAAG;AAAA,QACD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,UAAQ,IAAI;AACZ,UAAQ,IAAI,GAAG,KAAK,4BAA4B,CAAC;AAEjD,MAAI;AACF,UAAM,WAAW,mBAAmB,QAAQ,kBAAkB,GAAG,cAAc;AAC/E,aAAS,UAAU;AAAA,MACjB,KAAK,KAAK,KAAK,aAAa,YAAY,QAAQ;AAAA,MAChD,OAAO;AAAA,IACT,CAAC;AAAA,EACH,QAAQ;AACN,YAAQ;AAAA,MACN,GAAG,OAAO,iFAAiF;AAAA,IAC7F;AAAA,EACF;AAGA,UAAQ,IAAI;AACZ,UAAQ;AAAA,IACN,GAAG,MAAM,UAAU,IAAI,YAAY,GAAG,KAAK,WAAW,CAAC,OAAO,WAAW;AAAA,EAC3E;AACA,UAAQ,IAAI;AACZ,UAAQ,IAAI,oBAAoB;AAChC,UAAQ,IAAI;AACZ,UAAQ,IAAI,KAAK,GAAG,KAAK,UAAU,CAAC,sCAAsC;AAC1E,UAAQ,IAAI,KAAK,GAAG,KAAK,UAAU,CAAC,oCAAoC;AACxE,UAAQ,IAAI,KAAK,GAAG,KAAK,iBAAiB,CAAC,+BAA+B;AAC1E,UAAQ,IAAI,KAAK,GAAG,KAAK,OAAO,CAAC,kCAAkC;AACnE,UAAQ,IAAI,KAAK,GAAG,KAAK,gBAAgB,CAAC,0BAA0B;AACpE,UAAQ,IAAI;AACZ,UAAQ,IAAI,aAAa;AACzB,UAAQ,IAAI;AACZ,UAAQ,IAAI,KAAK,GAAG,KAAK,IAAI,CAAC,IAAI,WAAW,EAAE;AAC/C,UAAQ;AAAA,IACN,KAAK,GAAG,KAAK,mBAAmB,QAAQ,YAAY,cAAc,CAAC;AAAA,EACrE;AACA,UAAQ,IAAI;AACZ,UAAQ,IAAI,wEAAwE;AACpF,UAAQ,IAAI;AACZ,UAAQ,IAAI,eAAe;AAC3B,UAAQ,IAAI;AACd;AAEA,eAAsB,OAAsB;AAC1C,UACG,KAAK,aAAa,EAClB,YAAY,uDAAuD,EACnE,QAAQ,OAAO,EACf,SAAS,kBAAkB,qBAAqB,EAChD,OAAO,OAAO,gBAAoC;AACjD,YAAQ,IAAI;AACZ,YAAQ;AAAA,MACN,GAAG,KAAK,GAAG,KAAK,YAAY,IAAI,sCAAsC;AAAA,IACxE;AACA,YAAQ,IAAI,GAAG,IAAI,yDAAyD,CAAC;AAC7E,YAAQ,IAAI;AAEZ,UAAM,UAAU,MAAM,iBAAiB,WAAW;AAClD,UAAM,cAAc,OAAO;AAAA,EAC7B,CAAC;AAEH,QAAM,QAAQ,WAAW,QAAQ,IAAI;AACvC;","names":[]}
|
package/package.json
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: NestJS backend development patterns
|
|
3
|
+
globs: apps/api/**/*.ts
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Backend Patterns
|
|
8
|
+
|
|
9
|
+
## Module Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
apps/api/src/
|
|
13
|
+
├── main.ts # Application entry
|
|
14
|
+
├── app.module.ts # Root module
|
|
15
|
+
├── config/ # Configuration
|
|
16
|
+
└── {feature}/ # Feature modules
|
|
17
|
+
├── {feature}.module.ts
|
|
18
|
+
├── {feature}.controller.ts
|
|
19
|
+
├── {feature}.service.ts
|
|
20
|
+
└── dto/ # Feature-specific DTOs (if not shared)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Controller Pattern
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { Controller, Get, Post, Body } from '@nestjs/common';
|
|
27
|
+
import { CreateUserDto, User } from '@app/shared';
|
|
28
|
+
|
|
29
|
+
@Controller('users')
|
|
30
|
+
export class UsersController {
|
|
31
|
+
constructor(private readonly usersService: UsersService) {}
|
|
32
|
+
|
|
33
|
+
@Get()
|
|
34
|
+
findAll(): Promise<User[]> {
|
|
35
|
+
return this.usersService.findAll();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Post()
|
|
39
|
+
create(@Body() dto: CreateUserDto): Promise<User> {
|
|
40
|
+
return this.usersService.create(dto);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Validation
|
|
46
|
+
|
|
47
|
+
Use class-validator with shared DTOs:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { CreateUserDto } from '@app/shared';
|
|
51
|
+
|
|
52
|
+
@Post()
|
|
53
|
+
create(@Body() dto: CreateUserDto) {
|
|
54
|
+
// Automatically validated by ValidationPipe
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Configuration
|
|
59
|
+
|
|
60
|
+
Use @nestjs/config for environment variables:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { ConfigService } from '@nestjs/config';
|
|
64
|
+
|
|
65
|
+
constructor(private config: ConfigService) {
|
|
66
|
+
const port = this.config.get<number>('PORT');
|
|
67
|
+
}
|
|
68
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Next.js frontend development patterns
|
|
3
|
+
globs: apps/web/**/*.{ts,tsx}
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Frontend Patterns
|
|
8
|
+
|
|
9
|
+
## Component Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
apps/web/src/
|
|
13
|
+
├── app/ # App Router pages
|
|
14
|
+
├── components/ # React components
|
|
15
|
+
│ ├── ui/ # Reusable UI components
|
|
16
|
+
│ └── features/ # Feature-specific components
|
|
17
|
+
├── hooks/ # Custom React hooks
|
|
18
|
+
└── lib/ # Utilities and helpers
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Server vs Client Components
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// Default: Server Component (no directive needed)
|
|
25
|
+
export default function Page() {
|
|
26
|
+
return <div>Server rendered</div>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Client Component: add 'use client' at top
|
|
30
|
+
'use client';
|
|
31
|
+
export function InteractiveComponent() {
|
|
32
|
+
const [state, setState] = useState();
|
|
33
|
+
return <button onClick={() => setState(...)}>Click</button>;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API Calls to Backend
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// Use environment variable for API URL
|
|
41
|
+
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
42
|
+
|
|
43
|
+
// Import routes from shared
|
|
44
|
+
import { API_ROUTES } from '@app/shared';
|
|
45
|
+
|
|
46
|
+
const response = await fetch(`${API_URL}${API_ROUTES.USERS}`);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Styling
|
|
50
|
+
|
|
51
|
+
- Use Tailwind CSS for styling
|
|
52
|
+
- Follow mobile-first responsive design
|
|
53
|
+
- Use CSS variables for theming
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Nene monorepo architecture and key patterns
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Nene Architecture Rules
|
|
7
|
+
|
|
8
|
+
## Project Structure
|
|
9
|
+
|
|
10
|
+
This is a full-stack monorepo:
|
|
11
|
+
- `apps/web/` - Next.js 15 frontend (port 3000)
|
|
12
|
+
- `apps/api/` - NestJS 11 backend (port 4000)
|
|
13
|
+
- `packages/shared/` - Shared types, DTOs, constants
|
|
14
|
+
|
|
15
|
+
## Key Patterns
|
|
16
|
+
|
|
17
|
+
### Type Sharing (Critical)
|
|
18
|
+
|
|
19
|
+
**NEVER duplicate types.** Always use `@app/shared`:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// ✅ GOOD - import from shared
|
|
23
|
+
import { User, CreateUserDto } from '@app/shared';
|
|
24
|
+
|
|
25
|
+
// ❌ BAD - duplicating types
|
|
26
|
+
interface User { id: string; ... }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### API Routes
|
|
30
|
+
|
|
31
|
+
Use shared constants for API routes:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// packages/shared/src/constants/index.ts
|
|
35
|
+
export const API_ROUTES = {
|
|
36
|
+
USERS: '/api/users',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Use in both apps
|
|
40
|
+
import { API_ROUTES } from '@app/shared';
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Validation
|
|
44
|
+
|
|
45
|
+
DTOs with class-validator are shared:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// packages/shared/src/types/index.ts
|
|
49
|
+
export class CreateUserDto {
|
|
50
|
+
@IsEmail()
|
|
51
|
+
email: string;
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Before Making Changes
|
|
56
|
+
|
|
57
|
+
1. Check `docs/ARCHITECTURE.md` for current patterns
|
|
58
|
+
2. Check `docs/API.md` for existing endpoints
|
|
59
|
+
3. Check `docs/PROGRESS.md` for project status
|
|
60
|
+
|
|
61
|
+
## After Making Changes
|
|
62
|
+
|
|
63
|
+
1. Update `docs/API.md` when adding/modifying endpoints
|
|
64
|
+
2. Update `docs/PROGRESS.md` to reflect progress
|
|
65
|
+
3. Run `pnpm build` to verify shared package changes work
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Shared package development rules
|
|
3
|
+
globs: packages/shared/**/*.ts
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Shared Package Rules
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
The `@app/shared` package is the single source of truth for:
|
|
12
|
+
- TypeScript types and interfaces
|
|
13
|
+
- DTOs with validation decorators
|
|
14
|
+
- API route constants
|
|
15
|
+
- Shared utilities
|
|
16
|
+
|
|
17
|
+
## File Structure
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
packages/shared/src/
|
|
21
|
+
├── index.ts # Main exports
|
|
22
|
+
├── types/
|
|
23
|
+
│ └── index.ts # Interfaces and types
|
|
24
|
+
├── constants/
|
|
25
|
+
│ └── index.ts # API routes, constants
|
|
26
|
+
└── utils/ # Shared utilities (optional)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Adding New Types
|
|
30
|
+
|
|
31
|
+
1. Add to `packages/shared/src/types/index.ts`
|
|
32
|
+
2. Export from `packages/shared/src/index.ts`
|
|
33
|
+
3. Run `pnpm build` in shared package
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// types/index.ts
|
|
37
|
+
export interface NewType {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// index.ts
|
|
43
|
+
export * from './types';
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Adding DTOs
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { IsString, IsEmail } from 'class-validator';
|
|
50
|
+
|
|
51
|
+
export class CreateUserDto {
|
|
52
|
+
@IsEmail()
|
|
53
|
+
email: string;
|
|
54
|
+
|
|
55
|
+
@IsString()
|
|
56
|
+
name: string;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## After Changes
|
|
61
|
+
|
|
62
|
+
Always rebuild shared package after changes:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pnpm --filter @app/shared build
|
|
66
|
+
# or from root
|
|
67
|
+
pnpm build
|
|
68
|
+
```
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Task management workflow for docs/plan kanban board
|
|
3
|
+
globs: docs/plan/**/*.md
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Task Management Rules
|
|
8
|
+
|
|
9
|
+
Rules for the project's kanban board (`docs/plan/`) task management.
|
|
10
|
+
|
|
11
|
+
## When Starting a Task
|
|
12
|
+
|
|
13
|
+
1. Check the Priority Order in `docs/plan/README.md`
|
|
14
|
+
2. Select the highest-priority TODO file
|
|
15
|
+
3. Review the file contents before starting work
|
|
16
|
+
|
|
17
|
+
## When Completing a Task (Required)
|
|
18
|
+
|
|
19
|
+
When completing a task, you **must** do the following:
|
|
20
|
+
|
|
21
|
+
### 1. Move the File
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
mv docs/plan/TODO/{task-file}.md docs/plan/DONE/
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Update File Contents
|
|
28
|
+
|
|
29
|
+
```markdown
|
|
30
|
+
# Status change
|
|
31
|
+
|
|
32
|
+
- **Status**: 📋 Todo → - **Status**: ✅ Done
|
|
33
|
+
|
|
34
|
+
# Complete checklist items
|
|
35
|
+
|
|
36
|
+
- [ ] item → - [x] item
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 3. Update README.md Counts
|
|
40
|
+
|
|
41
|
+
```markdown
|
|
42
|
+
| ✅ DONE | N | # +1 increase
|
|
43
|
+
| 📋 TODO | M | # -1 decrease
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Task Status
|
|
47
|
+
|
|
48
|
+
| Status | Emoji | Folder |
|
|
49
|
+
| ------ | ----- | -------- |
|
|
50
|
+
| Todo | 📋 | `TODO/` |
|
|
51
|
+
| Doing | 🔄 | `DOING/` |
|
|
52
|
+
| Done | ✅ | `DONE/` |
|
|
53
|
+
|
|
54
|
+
## Priority
|
|
55
|
+
|
|
56
|
+
| Priority | Emoji |
|
|
57
|
+
| -------- | ----- |
|
|
58
|
+
| High | 🔥 |
|
|
59
|
+
| Medium | 🟠 |
|
|
60
|
+
| Low | 🔵 |
|