projects-init-cli 1.1.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/README.md +127 -0
- package/dist/generator.js +59 -0
- package/dist/index.js +32 -0
- package/dist/prompts.js +156 -0
- package/dist/templates/backend/cdk.js +211 -0
- package/dist/templates/backend/express.js +427 -0
- package/dist/templates/backend/fastapi.js +259 -0
- package/dist/templates/backend/nestjs.js +478 -0
- package/dist/templates/backend/sam.js +258 -0
- package/dist/templates/frontend/html.js +141 -0
- package/dist/templates/frontend/index.js +9 -0
- package/dist/templates/frontend/nextjs.js +240 -0
- package/dist/templates/frontend/react.js +249 -0
- package/dist/templates/index.js +341 -0
- package/dist/types.js +2 -0
- package/package.json +52 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateSAM = generateSAM;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function generateSAM(backendPath, config) {
|
|
10
|
+
// Create root package.json with SAM scripts
|
|
11
|
+
const rootPackageJson = {
|
|
12
|
+
name: `${config.projectName}-backend`,
|
|
13
|
+
version: '1.0.0',
|
|
14
|
+
private: true,
|
|
15
|
+
scripts: {
|
|
16
|
+
'build': 'sam build',
|
|
17
|
+
'deploy': 'sam deploy',
|
|
18
|
+
'deploy:guided': 'sam deploy --guided',
|
|
19
|
+
'local': 'sam local start-api',
|
|
20
|
+
'validate': 'sam validate',
|
|
21
|
+
'synth': 'sam build && sam deploy --no-execute'
|
|
22
|
+
},
|
|
23
|
+
devDependencies: {}
|
|
24
|
+
};
|
|
25
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(backendPath, 'package.json'), rootPackageJson, { spaces: 2 });
|
|
26
|
+
// Create template.yaml
|
|
27
|
+
let templateContent = `AWSTemplateFormatVersion: '2010-09-09'
|
|
28
|
+
Transform: AWS::Serverless-2016-10-31
|
|
29
|
+
Description: >
|
|
30
|
+
${config.projectName}
|
|
31
|
+
SAM Template for ${config.projectName}
|
|
32
|
+
|
|
33
|
+
Globals:
|
|
34
|
+
Function:
|
|
35
|
+
Timeout: 3
|
|
36
|
+
MemorySize: 128
|
|
37
|
+
|
|
38
|
+
Resources:
|
|
39
|
+
HelloWorldFunction:
|
|
40
|
+
Type: AWS::Serverless::Function
|
|
41
|
+
Properties:
|
|
42
|
+
CodeUri: hello-world/
|
|
43
|
+
Handler: app.lambdaHandler
|
|
44
|
+
Runtime: nodejs18.x
|
|
45
|
+
Architectures:
|
|
46
|
+
- x86_64
|
|
47
|
+
Events:
|
|
48
|
+
HelloWorld:
|
|
49
|
+
Type: Api
|
|
50
|
+
Properties:
|
|
51
|
+
Path: /hello
|
|
52
|
+
Method: get
|
|
53
|
+
HelloWorldPost:
|
|
54
|
+
Type: Api
|
|
55
|
+
Properties:
|
|
56
|
+
Path: /hello
|
|
57
|
+
Method: post
|
|
58
|
+
|
|
59
|
+
`;
|
|
60
|
+
if (config.databaseType === 'nosql') {
|
|
61
|
+
templateContent += ` DynamoDBTable:
|
|
62
|
+
Type: AWS::DynamoDB::Table
|
|
63
|
+
Properties:
|
|
64
|
+
TableName: ${config.projectName}Table
|
|
65
|
+
BillingMode: PAY_PER_REQUEST
|
|
66
|
+
AttributeDefinitions:
|
|
67
|
+
- AttributeName: id
|
|
68
|
+
AttributeType: S
|
|
69
|
+
KeySchema:
|
|
70
|
+
- AttributeName: id
|
|
71
|
+
KeyType: HASH
|
|
72
|
+
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
else if (config.databaseType === 'sql') {
|
|
76
|
+
templateContent += ` # Note: RDS requires VPC configuration
|
|
77
|
+
# You'll need to add VPC, Subnets, and Security Groups manually
|
|
78
|
+
# or use AWS RDS Proxy for serverless access
|
|
79
|
+
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
templateContent += `Outputs:
|
|
83
|
+
HelloWorldApi:
|
|
84
|
+
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
|
|
85
|
+
Value: !Sub "https://\${ServerlessRestApi}.execute-api.\${AWS::Region}.amazonaws.com/Prod/hello/"
|
|
86
|
+
HelloWorldFunction:
|
|
87
|
+
Description: "Hello World Lambda Function ARN"
|
|
88
|
+
Value: !GetAtt HelloWorldFunction.Arn
|
|
89
|
+
HelloWorldFunctionIamRole:
|
|
90
|
+
Description: "Implicit IAM Role created for Hello World function"
|
|
91
|
+
Value: !GetAtt HelloWorldFunctionRole.Arn
|
|
92
|
+
`;
|
|
93
|
+
await fs_extra_1.default.writeFile(path_1.default.join(backendPath, 'template.yaml'), templateContent);
|
|
94
|
+
// Create hello-world function
|
|
95
|
+
const helloWorldPath = path_1.default.join(backendPath, 'hello-world');
|
|
96
|
+
await fs_extra_1.default.ensureDir(helloWorldPath);
|
|
97
|
+
const packageJson = {
|
|
98
|
+
name: 'hello-world',
|
|
99
|
+
version: '1.0.0',
|
|
100
|
+
description: 'Hello World SAM Lambda function',
|
|
101
|
+
main: 'app.js',
|
|
102
|
+
scripts: {
|
|
103
|
+
test: 'node tests/unit/test-handler.js'
|
|
104
|
+
},
|
|
105
|
+
dependencies: {}
|
|
106
|
+
};
|
|
107
|
+
if (config.databaseType === 'nosql') {
|
|
108
|
+
packageJson.dependencies = {
|
|
109
|
+
'@aws-sdk/client-dynamodb': '^3.699.0',
|
|
110
|
+
'@aws-sdk/lib-dynamodb': '^3.699.0'
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(helloWorldPath, 'package.json'), packageJson, { spaces: 2 });
|
|
114
|
+
let appJs = `exports.lambdaHandler = async (event) => {
|
|
115
|
+
try {
|
|
116
|
+
const response = {
|
|
117
|
+
statusCode: 200,
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
message: 'Hello from ${config.projectName}!',
|
|
120
|
+
input: event,
|
|
121
|
+
}),
|
|
122
|
+
};
|
|
123
|
+
return response;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.log(err);
|
|
126
|
+
return err;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
`;
|
|
130
|
+
if (config.databaseType === 'nosql') {
|
|
131
|
+
appJs = `const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
|
|
132
|
+
const { DynamoDBDocumentClient, PutCommand, GetCommand } = require('@aws-sdk/lib-dynamodb');
|
|
133
|
+
|
|
134
|
+
const client = new DynamoDBClient({});
|
|
135
|
+
const docClient = DynamoDBDocumentClient.from(client);
|
|
136
|
+
|
|
137
|
+
const TABLE_NAME = process.env.TABLE_NAME || '${config.projectName}Table';
|
|
138
|
+
|
|
139
|
+
exports.lambdaHandler = async (event) => {
|
|
140
|
+
try {
|
|
141
|
+
const response = {
|
|
142
|
+
statusCode: 200,
|
|
143
|
+
body: JSON.stringify({
|
|
144
|
+
message: 'Hello from ${config.projectName}!',
|
|
145
|
+
input: event,
|
|
146
|
+
}),
|
|
147
|
+
};
|
|
148
|
+
return response;
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.log(err);
|
|
151
|
+
return {
|
|
152
|
+
statusCode: 500,
|
|
153
|
+
body: JSON.stringify({ error: err.message }),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
await fs_extra_1.default.writeFile(path_1.default.join(helloWorldPath, 'app.js'), appJs);
|
|
160
|
+
// Create test directory and test file
|
|
161
|
+
const testsPath = path_1.default.join(helloWorldPath, 'tests', 'unit');
|
|
162
|
+
await fs_extra_1.default.ensureDir(testsPath);
|
|
163
|
+
const testFile = `import { describe, it, expect } from 'vitest';
|
|
164
|
+
const app = require('../../app');
|
|
165
|
+
|
|
166
|
+
describe('Tests index', () => {
|
|
167
|
+
it('verifies successful response', async () => {
|
|
168
|
+
const event = {
|
|
169
|
+
httpMethod: 'GET',
|
|
170
|
+
path: '/hello'
|
|
171
|
+
};
|
|
172
|
+
const context = {};
|
|
173
|
+
|
|
174
|
+
const result = await app.lambdaHandler(event, context);
|
|
175
|
+
|
|
176
|
+
expect(result).toBeDefined();
|
|
177
|
+
expect(result.statusCode).toBe(200);
|
|
178
|
+
|
|
179
|
+
const response = JSON.parse(result.body);
|
|
180
|
+
expect(response.message).toBeDefined();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
`;
|
|
184
|
+
await fs_extra_1.default.writeFile(path_1.default.join(testsPath, 'test-handler.js'), testFile);
|
|
185
|
+
// Create package.json for tests
|
|
186
|
+
const testPackageJson = {
|
|
187
|
+
name: 'hello-world-tests',
|
|
188
|
+
version: '1.0.0',
|
|
189
|
+
scripts: {
|
|
190
|
+
test: 'vitest',
|
|
191
|
+
'test:ui': 'vitest --ui',
|
|
192
|
+
'test:coverage': 'vitest --coverage'
|
|
193
|
+
},
|
|
194
|
+
dependencies: {
|
|
195
|
+
'aws-sdk': '^2.814.0'
|
|
196
|
+
},
|
|
197
|
+
devDependencies: {
|
|
198
|
+
'vitest': '^2.1.3',
|
|
199
|
+
'@vitest/ui': '^2.1.3',
|
|
200
|
+
'@vitest/coverage-v8': '^2.1.3'
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(testsPath, 'package.json'), testPackageJson, { spaces: 2 });
|
|
204
|
+
// Create samconfig.toml
|
|
205
|
+
const samConfig = `version = 0.1
|
|
206
|
+
|
|
207
|
+
[default]
|
|
208
|
+
[default.global.parameters]
|
|
209
|
+
stack_name = "${config.projectName}"
|
|
210
|
+
|
|
211
|
+
[default.build.parameters]
|
|
212
|
+
cached = true
|
|
213
|
+
parallel = true
|
|
214
|
+
|
|
215
|
+
[default.validate.parameters]
|
|
216
|
+
lint = true
|
|
217
|
+
|
|
218
|
+
[default.deploy.parameters]
|
|
219
|
+
capabilities = "CAPABILITY_IAM"
|
|
220
|
+
confirm_changeset = true
|
|
221
|
+
resolve_s3 = true
|
|
222
|
+
region = "us-east-1"
|
|
223
|
+
`;
|
|
224
|
+
await fs_extra_1.default.writeFile(path_1.default.join(backendPath, 'samconfig.toml'), samConfig);
|
|
225
|
+
// Create README
|
|
226
|
+
const readme = `# AWS SAM Backend
|
|
227
|
+
|
|
228
|
+
This is an AWS SAM project for serverless deployment.
|
|
229
|
+
|
|
230
|
+
## Prerequisites
|
|
231
|
+
|
|
232
|
+
- AWS CLI configured
|
|
233
|
+
- SAM CLI installed (\`pip install aws-sam-cli\`)
|
|
234
|
+
|
|
235
|
+
## Build and Deploy
|
|
236
|
+
|
|
237
|
+
\`\`\`bash
|
|
238
|
+
npm run build
|
|
239
|
+
npm run deploy:guided
|
|
240
|
+
\`\`\`
|
|
241
|
+
|
|
242
|
+
## Local Development
|
|
243
|
+
|
|
244
|
+
\`\`\`bash
|
|
245
|
+
npm run local
|
|
246
|
+
\`\`\`
|
|
247
|
+
|
|
248
|
+
## Useful Commands
|
|
249
|
+
|
|
250
|
+
- \`npm run build\` - Build your application
|
|
251
|
+
- \`npm run local\` - Start local API Gateway
|
|
252
|
+
- \`npm run deploy:guided\` - Deploy your application with guided prompts
|
|
253
|
+
- \`npm run deploy\` - Deploy your application
|
|
254
|
+
- \`npm run validate\` - Validate SAM template
|
|
255
|
+
- \`sam logs -n HelloWorldFunction --stack-name ${config.projectName} --tail\` - View logs
|
|
256
|
+
`;
|
|
257
|
+
await fs_extra_1.default.writeFile(path_1.default.join(backendPath, 'README.md'), readme);
|
|
258
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateHTML = generateHTML;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function generateHTML(frontendPath, config) {
|
|
10
|
+
const packageJson = {
|
|
11
|
+
name: `${config.projectName}-frontend`,
|
|
12
|
+
version: '0.1.0',
|
|
13
|
+
private: true,
|
|
14
|
+
scripts: {
|
|
15
|
+
dev: 'vite',
|
|
16
|
+
build: 'vite build',
|
|
17
|
+
preview: 'vite preview'
|
|
18
|
+
},
|
|
19
|
+
devDependencies: {
|
|
20
|
+
vite: '^6.0.1',
|
|
21
|
+
'tailwindcss': '^3.4.14',
|
|
22
|
+
'postcss': '^8.4.47',
|
|
23
|
+
'autoprefixer': '^10.4.20'
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(frontendPath, 'package.json'), packageJson, { spaces: 2 });
|
|
27
|
+
// Create Vite config
|
|
28
|
+
const viteConfig = `import { defineConfig } from 'vite'
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
// Vite config for HTML project
|
|
32
|
+
})
|
|
33
|
+
`;
|
|
34
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'vite.config.js'), viteConfig);
|
|
35
|
+
// Create Tailwind config
|
|
36
|
+
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
37
|
+
module.exports = {
|
|
38
|
+
content: [
|
|
39
|
+
"./index.html",
|
|
40
|
+
"./src/**/*.{html,js}",
|
|
41
|
+
],
|
|
42
|
+
theme: {
|
|
43
|
+
extend: {},
|
|
44
|
+
},
|
|
45
|
+
plugins: [],
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'tailwind.config.js'), tailwindConfig);
|
|
49
|
+
// Create PostCSS config
|
|
50
|
+
const postcssConfig = `module.exports = {
|
|
51
|
+
plugins: {
|
|
52
|
+
tailwindcss: {},
|
|
53
|
+
autoprefixer: {},
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'postcss.config.js'), postcssConfig);
|
|
58
|
+
// Create index.html
|
|
59
|
+
const indexHtml = `<!DOCTYPE html>
|
|
60
|
+
<html lang="en">
|
|
61
|
+
<head>
|
|
62
|
+
<meta charset="UTF-8">
|
|
63
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
64
|
+
<title>${config.projectName}</title>
|
|
65
|
+
<link rel="stylesheet" href="/src/styles.css">
|
|
66
|
+
</head>
|
|
67
|
+
<body>
|
|
68
|
+
<main class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
|
|
69
|
+
<div class="bg-white rounded-lg shadow-xl p-8 max-w-md w-full">
|
|
70
|
+
<h1 class="text-4xl font-bold text-gray-800 mb-4">
|
|
71
|
+
Welcome to ${config.projectName}
|
|
72
|
+
</h1>
|
|
73
|
+
<p class="text-gray-600 mb-6">
|
|
74
|
+
Your HTML application with Tailwind CSS is ready!
|
|
75
|
+
</p>
|
|
76
|
+
<div class="space-y-2">
|
|
77
|
+
<div class="flex items-center text-sm text-gray-500">
|
|
78
|
+
<span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
|
|
79
|
+
HTML5
|
|
80
|
+
</div>
|
|
81
|
+
<div class="flex items-center text-sm text-gray-500">
|
|
82
|
+
<span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
|
|
83
|
+
Tailwind CSS
|
|
84
|
+
</div>
|
|
85
|
+
<div class="flex items-center text-sm text-gray-500">
|
|
86
|
+
<span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
|
|
87
|
+
Vite
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</main>
|
|
92
|
+
<script type="module" src="/src/main.js"></script>
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
|
95
|
+
`;
|
|
96
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'index.html'), indexHtml);
|
|
97
|
+
// Create src directory
|
|
98
|
+
const srcPath = path_1.default.join(frontendPath, 'src');
|
|
99
|
+
await fs_extra_1.default.ensureDir(srcPath);
|
|
100
|
+
// Create styles.css
|
|
101
|
+
const stylesCss = `@tailwind base;
|
|
102
|
+
@tailwind components;
|
|
103
|
+
@tailwind utilities;
|
|
104
|
+
`;
|
|
105
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'styles.css'), stylesCss);
|
|
106
|
+
// Create main.js
|
|
107
|
+
const mainJs = `// Your JavaScript code here
|
|
108
|
+
console.log('${config.projectName} is ready!');
|
|
109
|
+
`;
|
|
110
|
+
await fs_extra_1.default.writeFile(path_1.default.join(srcPath, 'main.js'), mainJs);
|
|
111
|
+
// Create deployment files
|
|
112
|
+
await generateFrontendDeployment(frontendPath, config);
|
|
113
|
+
}
|
|
114
|
+
async function generateFrontendDeployment(frontendPath, config) {
|
|
115
|
+
// Netlify configuration
|
|
116
|
+
const netlifyToml = `[build]
|
|
117
|
+
command = "npm run build"
|
|
118
|
+
publish = "dist"
|
|
119
|
+
|
|
120
|
+
[[redirects]]
|
|
121
|
+
from = "/*"
|
|
122
|
+
to = "/index.html"
|
|
123
|
+
status = 200
|
|
124
|
+
|
|
125
|
+
[build.environment]
|
|
126
|
+
NODE_VERSION = "20"
|
|
127
|
+
`;
|
|
128
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'netlify.toml'), netlifyToml);
|
|
129
|
+
// Render configuration
|
|
130
|
+
const renderYaml = `services:
|
|
131
|
+
- type: web
|
|
132
|
+
name: ${config.projectName}-frontend
|
|
133
|
+
env: node
|
|
134
|
+
buildCommand: npm install && npm run build
|
|
135
|
+
staticPublishPath: ./dist
|
|
136
|
+
envVars:
|
|
137
|
+
- key: NODE_ENV
|
|
138
|
+
value: production
|
|
139
|
+
`;
|
|
140
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'render.yaml'), renderYaml);
|
|
141
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateHTML = exports.generateReact = exports.generateNextJS = void 0;
|
|
4
|
+
var nextjs_1 = require("./nextjs");
|
|
5
|
+
Object.defineProperty(exports, "generateNextJS", { enumerable: true, get: function () { return nextjs_1.generateNextJS; } });
|
|
6
|
+
var react_1 = require("./react");
|
|
7
|
+
Object.defineProperty(exports, "generateReact", { enumerable: true, get: function () { return react_1.generateReact; } });
|
|
8
|
+
var html_1 = require("./html");
|
|
9
|
+
Object.defineProperty(exports, "generateHTML", { enumerable: true, get: function () { return html_1.generateHTML; } });
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateNextJS = generateNextJS;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function generateNextJS(frontendPath, config) {
|
|
10
|
+
const packageJson = {
|
|
11
|
+
name: `${config.projectName}-frontend`,
|
|
12
|
+
version: '0.1.0',
|
|
13
|
+
private: true,
|
|
14
|
+
scripts: {
|
|
15
|
+
dev: 'next dev',
|
|
16
|
+
build: 'next build',
|
|
17
|
+
start: 'next start',
|
|
18
|
+
lint: 'next lint',
|
|
19
|
+
test: 'vitest',
|
|
20
|
+
'test:ui': 'vitest --ui',
|
|
21
|
+
'test:coverage': 'vitest --coverage'
|
|
22
|
+
},
|
|
23
|
+
dependencies: {
|
|
24
|
+
react: '^18.3.1',
|
|
25
|
+
'react-dom': '^18.3.1',
|
|
26
|
+
next: '^15.0.3'
|
|
27
|
+
},
|
|
28
|
+
devDependencies: {
|
|
29
|
+
'@types/node': '^22.7.5',
|
|
30
|
+
'@types/react': '^18.3.12',
|
|
31
|
+
'@types/react-dom': '^18.3.1',
|
|
32
|
+
typescript: '^5.6.3',
|
|
33
|
+
'tailwindcss': '^3.4.14',
|
|
34
|
+
'postcss': '^8.4.47',
|
|
35
|
+
'autoprefixer': '^10.4.20',
|
|
36
|
+
'eslint': '^9.15.0',
|
|
37
|
+
'eslint-config-next': '^15.0.3',
|
|
38
|
+
'vitest': '^2.1.3',
|
|
39
|
+
'@vitest/ui': '^2.1.3',
|
|
40
|
+
'@testing-library/react': '^16.0.1',
|
|
41
|
+
'@testing-library/jest-dom': '^6.6.3',
|
|
42
|
+
'@vitejs/plugin-react': '^4.3.2',
|
|
43
|
+
'jsdom': '^25.0.1',
|
|
44
|
+
'@vitest/coverage-v8': '^2.1.3'
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(frontendPath, 'package.json'), packageJson, { spaces: 2 });
|
|
48
|
+
// Create Next.js config
|
|
49
|
+
const nextConfig = `/** @type {import('next').NextConfig} */
|
|
50
|
+
const nextConfig = {
|
|
51
|
+
reactStrictMode: true,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = nextConfig
|
|
55
|
+
`;
|
|
56
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'next.config.js'), nextConfig);
|
|
57
|
+
// Create Tailwind config
|
|
58
|
+
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
59
|
+
module.exports = {
|
|
60
|
+
content: [
|
|
61
|
+
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
|
62
|
+
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
63
|
+
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
64
|
+
],
|
|
65
|
+
theme: {
|
|
66
|
+
extend: {},
|
|
67
|
+
},
|
|
68
|
+
plugins: [],
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'tailwind.config.js'), tailwindConfig);
|
|
72
|
+
// Create PostCSS config
|
|
73
|
+
const postcssConfig = `module.exports = {
|
|
74
|
+
plugins: {
|
|
75
|
+
tailwindcss: {},
|
|
76
|
+
autoprefixer: {},
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'postcss.config.js'), postcssConfig);
|
|
81
|
+
// Create Vitest config
|
|
82
|
+
const vitestConfig = `import { defineConfig } from 'vitest/config'
|
|
83
|
+
import react from '@vitejs/plugin-react'
|
|
84
|
+
import path from 'path'
|
|
85
|
+
|
|
86
|
+
export default defineConfig({
|
|
87
|
+
plugins: [react()],
|
|
88
|
+
test: {
|
|
89
|
+
globals: true,
|
|
90
|
+
environment: 'jsdom',
|
|
91
|
+
setupFiles: './src/test/setup.ts',
|
|
92
|
+
coverage: {
|
|
93
|
+
provider: 'v8',
|
|
94
|
+
reporter: ['text', 'json', 'html'],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
resolve: {
|
|
98
|
+
alias: {
|
|
99
|
+
'@': path.resolve(__dirname, './'),
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
`;
|
|
104
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'vitest.config.ts'), vitestConfig);
|
|
105
|
+
// Create TypeScript config
|
|
106
|
+
const tsconfig = {
|
|
107
|
+
compilerOptions: {
|
|
108
|
+
target: 'ES2020',
|
|
109
|
+
lib: ['dom', 'dom.iterable', 'esnext'],
|
|
110
|
+
allowJs: true,
|
|
111
|
+
skipLibCheck: true,
|
|
112
|
+
strict: true,
|
|
113
|
+
forceConsistentCasingInFileNames: true,
|
|
114
|
+
noEmit: true,
|
|
115
|
+
esModuleInterop: true,
|
|
116
|
+
module: 'esnext',
|
|
117
|
+
moduleResolution: 'bundler',
|
|
118
|
+
resolveJsonModule: true,
|
|
119
|
+
isolatedModules: true,
|
|
120
|
+
jsx: 'preserve',
|
|
121
|
+
incremental: true,
|
|
122
|
+
plugins: [{ name: 'next' }],
|
|
123
|
+
paths: {
|
|
124
|
+
'@/*': ['./*']
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts'],
|
|
128
|
+
exclude: ['node_modules']
|
|
129
|
+
};
|
|
130
|
+
await fs_extra_1.default.writeJSON(path_1.default.join(frontendPath, 'tsconfig.json'), tsconfig, { spaces: 2 });
|
|
131
|
+
// Create app directory structure
|
|
132
|
+
const appPath = path_1.default.join(frontendPath, 'app');
|
|
133
|
+
await fs_extra_1.default.ensureDir(appPath);
|
|
134
|
+
// Create layout
|
|
135
|
+
const layout = `import './globals.css'
|
|
136
|
+
import type { Metadata } from 'next'
|
|
137
|
+
|
|
138
|
+
export const metadata: Metadata = {
|
|
139
|
+
title: '${config.projectName}',
|
|
140
|
+
description: 'Generated with Projects Init CLI',
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default function RootLayout({
|
|
144
|
+
children,
|
|
145
|
+
}: {
|
|
146
|
+
children: React.ReactNode
|
|
147
|
+
}) {
|
|
148
|
+
return (
|
|
149
|
+
<html lang="en">
|
|
150
|
+
<body>{children}</body>
|
|
151
|
+
</html>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
`;
|
|
155
|
+
await fs_extra_1.default.writeFile(path_1.default.join(appPath, 'layout.tsx'), layout);
|
|
156
|
+
// Create page
|
|
157
|
+
const page = `export default function Home() {
|
|
158
|
+
return (
|
|
159
|
+
<main className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
|
|
160
|
+
<div className="bg-white rounded-lg shadow-xl p-8 max-w-md w-full">
|
|
161
|
+
<h1 className="text-4xl font-bold text-gray-800 mb-4">
|
|
162
|
+
Welcome to ${config.projectName}
|
|
163
|
+
</h1>
|
|
164
|
+
<p className="text-gray-600 mb-6">
|
|
165
|
+
Your Next.js application with Tailwind CSS is ready!
|
|
166
|
+
</p>
|
|
167
|
+
<div className="space-y-2">
|
|
168
|
+
<div className="flex items-center text-sm text-gray-500">
|
|
169
|
+
<span className="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
|
|
170
|
+
Next.js 14
|
|
171
|
+
</div>
|
|
172
|
+
<div className="flex items-center text-sm text-gray-500">
|
|
173
|
+
<span className="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
|
|
174
|
+
Tailwind CSS
|
|
175
|
+
</div>
|
|
176
|
+
<div className="flex items-center text-sm text-gray-500">
|
|
177
|
+
<span className="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
|
|
178
|
+
TypeScript
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</main>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
`;
|
|
186
|
+
await fs_extra_1.default.writeFile(path_1.default.join(appPath, 'page.tsx'), page);
|
|
187
|
+
// Create globals.css
|
|
188
|
+
const globalsCss = `@tailwind base;
|
|
189
|
+
@tailwind components;
|
|
190
|
+
@tailwind utilities;
|
|
191
|
+
`;
|
|
192
|
+
await fs_extra_1.default.writeFile(path_1.default.join(appPath, 'globals.css'), globalsCss);
|
|
193
|
+
// Create test directory and setup
|
|
194
|
+
const testPath = path_1.default.join(frontendPath, 'src', 'test');
|
|
195
|
+
await fs_extra_1.default.ensureDir(testPath);
|
|
196
|
+
const testSetup = `import '@testing-library/jest-dom'
|
|
197
|
+
`;
|
|
198
|
+
await fs_extra_1.default.writeFile(path_1.default.join(testPath, 'setup.ts'), testSetup);
|
|
199
|
+
// Create example test
|
|
200
|
+
const testExample = `import { describe, it, expect } from 'vitest'
|
|
201
|
+
import { render, screen } from '@testing-library/react'
|
|
202
|
+
import Home from '../app/page'
|
|
203
|
+
|
|
204
|
+
describe('Home Page', () => {
|
|
205
|
+
it('renders welcome message', () => {
|
|
206
|
+
render(<Home />)
|
|
207
|
+
expect(screen.getByText(/Welcome to ${config.projectName}/i)).toBeInTheDocument()
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
`;
|
|
211
|
+
await fs_extra_1.default.writeFile(path_1.default.join(testPath, 'page.test.tsx'), testExample);
|
|
212
|
+
// Create deployment files
|
|
213
|
+
await generateFrontendDeployment(frontendPath, config);
|
|
214
|
+
}
|
|
215
|
+
async function generateFrontendDeployment(frontendPath, config) {
|
|
216
|
+
// Netlify configuration
|
|
217
|
+
const netlifyToml = `[build]
|
|
218
|
+
command = "npm run build"
|
|
219
|
+
publish = ".next"
|
|
220
|
+
|
|
221
|
+
[[plugins]]
|
|
222
|
+
package = "@netlify/plugin-nextjs"
|
|
223
|
+
|
|
224
|
+
[build.environment]
|
|
225
|
+
NODE_VERSION = "20"
|
|
226
|
+
`;
|
|
227
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'netlify.toml'), netlifyToml);
|
|
228
|
+
// Render configuration
|
|
229
|
+
const renderYaml = `services:
|
|
230
|
+
- type: web
|
|
231
|
+
name: ${config.projectName}-frontend
|
|
232
|
+
env: node
|
|
233
|
+
buildCommand: npm install && npm run build
|
|
234
|
+
startCommand: npm start
|
|
235
|
+
envVars:
|
|
236
|
+
- key: NODE_ENV
|
|
237
|
+
value: production
|
|
238
|
+
`;
|
|
239
|
+
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'render.yaml'), renderYaml);
|
|
240
|
+
}
|