@svton/cli 1.0.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/LICENSE +21 -0
- package/README.md +138 -0
- package/bin/svton.js +9 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +750 -0
- package/dist/index.mjs +722 -0
- package/package.json +75 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/index.ts
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
|
|
12
|
+
// src/commands/create.ts
|
|
13
|
+
import inquirer from "inquirer";
|
|
14
|
+
import chalk2 from "chalk";
|
|
15
|
+
import ora from "ora";
|
|
16
|
+
import fs3 from "fs-extra";
|
|
17
|
+
import path2 from "path";
|
|
18
|
+
import validateNpmPackageName from "validate-npm-package-name";
|
|
19
|
+
|
|
20
|
+
// src/utils/template.ts
|
|
21
|
+
import fs2 from "fs-extra";
|
|
22
|
+
|
|
23
|
+
// src/utils/logger.ts
|
|
24
|
+
import chalk from "chalk";
|
|
25
|
+
var logger = {
|
|
26
|
+
info: (message) => {
|
|
27
|
+
console.log(message);
|
|
28
|
+
},
|
|
29
|
+
success: (message) => {
|
|
30
|
+
console.log(chalk.green(message));
|
|
31
|
+
},
|
|
32
|
+
warn: (message) => {
|
|
33
|
+
console.log(chalk.yellow(message));
|
|
34
|
+
},
|
|
35
|
+
error: (message) => {
|
|
36
|
+
console.log(chalk.red(message));
|
|
37
|
+
},
|
|
38
|
+
debug: (message) => {
|
|
39
|
+
if (process.env.DEBUG) {
|
|
40
|
+
console.log(chalk.gray(`[DEBUG] ${message}`));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/utils/copy-template.ts
|
|
46
|
+
import fs from "fs-extra";
|
|
47
|
+
import path from "path";
|
|
48
|
+
async function copyTemplateFiles(config) {
|
|
49
|
+
const { template, projectPath } = config;
|
|
50
|
+
const cliDir = path.dirname(path.dirname(__dirname));
|
|
51
|
+
const frameworkRoot = path.dirname(path.dirname(cliDir));
|
|
52
|
+
const templateDir = path.join(frameworkRoot, "templates");
|
|
53
|
+
logger.debug(`Copying template files from: ${templateDir}`);
|
|
54
|
+
if (!await fs.pathExists(templateDir)) {
|
|
55
|
+
logger.warn("Template directory not found, using built-in minimal templates");
|
|
56
|
+
await copyBuiltInTemplates(config);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const originalCwd = process.cwd();
|
|
60
|
+
process.chdir(projectPath);
|
|
61
|
+
try {
|
|
62
|
+
switch (template) {
|
|
63
|
+
case "full-stack":
|
|
64
|
+
await copyBackendTemplate(templateDir, config);
|
|
65
|
+
await copyAdminTemplate(templateDir, config);
|
|
66
|
+
await copyMobileTemplate(templateDir, config);
|
|
67
|
+
await copyTypesTemplate(templateDir, config);
|
|
68
|
+
break;
|
|
69
|
+
case "backend-only":
|
|
70
|
+
await copyBackendTemplate(templateDir, config);
|
|
71
|
+
await copyTypesTemplate(templateDir, config);
|
|
72
|
+
break;
|
|
73
|
+
case "admin-only":
|
|
74
|
+
await copyAdminTemplate(templateDir, config);
|
|
75
|
+
await copyTypesTemplate(templateDir, config);
|
|
76
|
+
break;
|
|
77
|
+
case "mobile-only":
|
|
78
|
+
await copyMobileTemplate(templateDir, config);
|
|
79
|
+
await copyTypesTemplate(templateDir, config);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
} finally {
|
|
83
|
+
process.chdir(originalCwd);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function copyBackendTemplate(sourceDir, config) {
|
|
87
|
+
const sourcePath = path.join(sourceDir, "apps/backend");
|
|
88
|
+
const destPath = "apps/backend";
|
|
89
|
+
await fs.ensureDir(destPath);
|
|
90
|
+
await fs.copy(sourcePath, destPath, {
|
|
91
|
+
filter: (src) => {
|
|
92
|
+
const relativePath = path.relative(sourcePath, src);
|
|
93
|
+
return !relativePath.includes("node_modules") && !relativePath.includes("dist") && !relativePath.includes(".env") && !relativePath.includes(".env.local");
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
await replacePackageNames(destPath, config);
|
|
97
|
+
logger.debug("Backend template copied");
|
|
98
|
+
}
|
|
99
|
+
async function copyAdminTemplate(sourceDir, config) {
|
|
100
|
+
const sourcePath = path.join(sourceDir, "apps/admin");
|
|
101
|
+
const destPath = "apps/admin";
|
|
102
|
+
await fs.ensureDir(destPath);
|
|
103
|
+
await fs.copy(sourcePath, destPath, {
|
|
104
|
+
filter: (src) => {
|
|
105
|
+
const relativePath = path.relative(sourcePath, src);
|
|
106
|
+
return !relativePath.includes("node_modules") && !relativePath.includes(".next") && !relativePath.includes(".env.local");
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
await replacePackageNames(destPath, config);
|
|
110
|
+
logger.debug("Admin template copied");
|
|
111
|
+
}
|
|
112
|
+
async function copyMobileTemplate(sourceDir, config) {
|
|
113
|
+
const sourcePath = path.join(sourceDir, "apps/mobile");
|
|
114
|
+
const destPath = "apps/mobile";
|
|
115
|
+
await fs.ensureDir(destPath);
|
|
116
|
+
await fs.copy(sourcePath, destPath, {
|
|
117
|
+
filter: (src) => {
|
|
118
|
+
const relativePath = path.relative(sourcePath, src);
|
|
119
|
+
return !relativePath.includes("node_modules") && !relativePath.includes("dist");
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
await replacePackageNames(destPath, config);
|
|
123
|
+
logger.debug("Mobile template copied");
|
|
124
|
+
}
|
|
125
|
+
async function copyTypesTemplate(sourceDir, config) {
|
|
126
|
+
const sourcePath = path.join(sourceDir, "packages/types");
|
|
127
|
+
const destPath = "packages/types";
|
|
128
|
+
await fs.ensureDir(destPath);
|
|
129
|
+
await fs.copy(sourcePath, destPath, {
|
|
130
|
+
filter: (src) => {
|
|
131
|
+
const relativePath = path.relative(sourcePath, src);
|
|
132
|
+
return !relativePath.includes("node_modules") && !relativePath.includes("dist");
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
await replacePackageNames(destPath, config);
|
|
136
|
+
logger.debug("Types package copied");
|
|
137
|
+
}
|
|
138
|
+
async function replacePackageNames(directory, config) {
|
|
139
|
+
const { projectName, orgName } = config;
|
|
140
|
+
const filesToUpdate = await findFilesToUpdate(directory);
|
|
141
|
+
for (const filePath of filesToUpdate) {
|
|
142
|
+
try {
|
|
143
|
+
let content = await fs.readFile(filePath, "utf8");
|
|
144
|
+
content = content.replace(/@svton\//g, `${orgName}/`).replace(/community-next/g, projectName).replace(/community-helper/g, projectName).replace(/社区助手/g, projectName);
|
|
145
|
+
await fs.writeFile(filePath, content);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
logger.debug(`Failed to update file ${filePath}: ${error}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function findFilesToUpdate(directory) {
|
|
152
|
+
const files = [];
|
|
153
|
+
const traverse = async (dir) => {
|
|
154
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
const fullPath = path.join(dir, entry.name);
|
|
157
|
+
if (entry.isDirectory()) {
|
|
158
|
+
await traverse(fullPath);
|
|
159
|
+
} else if (entry.isFile()) {
|
|
160
|
+
const ext = path.extname(entry.name);
|
|
161
|
+
const shouldUpdate = [".json", ".ts", ".tsx", ".js", ".jsx", ".md", ".yaml", ".yml", ".env.example"].includes(ext);
|
|
162
|
+
if (shouldUpdate) {
|
|
163
|
+
files.push(fullPath);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
await traverse(directory);
|
|
169
|
+
return files;
|
|
170
|
+
}
|
|
171
|
+
async function copyBuiltInTemplates(config) {
|
|
172
|
+
logger.info("Creating minimal project structure...");
|
|
173
|
+
const { template } = config;
|
|
174
|
+
if (template === "full-stack" || template === "backend-only") {
|
|
175
|
+
await createMinimalBackend(config);
|
|
176
|
+
}
|
|
177
|
+
if (template === "full-stack" || template === "admin-only") {
|
|
178
|
+
await createMinimalAdmin(config);
|
|
179
|
+
}
|
|
180
|
+
if (template === "full-stack" || template === "mobile-only") {
|
|
181
|
+
await createMinimalMobile(config);
|
|
182
|
+
}
|
|
183
|
+
await createMinimalTypes(config);
|
|
184
|
+
}
|
|
185
|
+
async function createMinimalBackend(config) {
|
|
186
|
+
const dir = "apps/backend";
|
|
187
|
+
await fs.ensureDir(dir);
|
|
188
|
+
const packageJson = {
|
|
189
|
+
name: `${config.orgName}/backend`,
|
|
190
|
+
version: "1.0.0",
|
|
191
|
+
description: "Backend API server",
|
|
192
|
+
scripts: {
|
|
193
|
+
build: "nest build",
|
|
194
|
+
dev: "nest start --watch",
|
|
195
|
+
start: "node dist/main",
|
|
196
|
+
lint: 'eslint "src/**/*.{ts,tsx}"',
|
|
197
|
+
"type-check": "tsc --noEmit"
|
|
198
|
+
},
|
|
199
|
+
dependencies: {
|
|
200
|
+
"@nestjs/common": "^10.3.0",
|
|
201
|
+
"@nestjs/core": "^10.3.0",
|
|
202
|
+
"@nestjs/platform-express": "^10.3.0",
|
|
203
|
+
"reflect-metadata": "^0.2.1",
|
|
204
|
+
"rxjs": "^7.8.1"
|
|
205
|
+
},
|
|
206
|
+
devDependencies: {
|
|
207
|
+
"@nestjs/cli": "^10.2.1",
|
|
208
|
+
"@types/node": "^20.10.0",
|
|
209
|
+
"typescript": "^5.3.0"
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
await fs.writeJson(path.join(dir, "package.json"), packageJson, { spaces: 2 });
|
|
213
|
+
}
|
|
214
|
+
async function createMinimalAdmin(config) {
|
|
215
|
+
const dir = "apps/admin";
|
|
216
|
+
await fs.ensureDir(dir);
|
|
217
|
+
const packageJson = {
|
|
218
|
+
name: `${config.orgName}/admin`,
|
|
219
|
+
version: "1.0.0",
|
|
220
|
+
description: "Admin panel",
|
|
221
|
+
scripts: {
|
|
222
|
+
dev: "next dev -p 3001",
|
|
223
|
+
build: "next build",
|
|
224
|
+
start: "next start -p 3001",
|
|
225
|
+
lint: "next lint",
|
|
226
|
+
"type-check": "tsc --noEmit"
|
|
227
|
+
},
|
|
228
|
+
dependencies: {
|
|
229
|
+
"next": "^15.5.0",
|
|
230
|
+
"react": "^19.0.0",
|
|
231
|
+
"react-dom": "^19.0.0"
|
|
232
|
+
},
|
|
233
|
+
devDependencies: {
|
|
234
|
+
"@types/node": "^22.10.2",
|
|
235
|
+
"@types/react": "^19.0.2",
|
|
236
|
+
"@types/react-dom": "^19.0.2",
|
|
237
|
+
"typescript": "^5.7.3"
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
await fs.writeJson(path.join(dir, "package.json"), packageJson, { spaces: 2 });
|
|
241
|
+
}
|
|
242
|
+
async function createMinimalMobile(config) {
|
|
243
|
+
const dir = "apps/mobile";
|
|
244
|
+
await fs.ensureDir(dir);
|
|
245
|
+
const packageJson = {
|
|
246
|
+
name: `${config.orgName}/mobile`,
|
|
247
|
+
version: "1.0.0",
|
|
248
|
+
description: "Mobile application",
|
|
249
|
+
scripts: {
|
|
250
|
+
"build:weapp": "taro build --type weapp",
|
|
251
|
+
"dev:weapp": "taro build --type weapp --watch",
|
|
252
|
+
dev: "npm run dev:weapp",
|
|
253
|
+
lint: 'eslint "src/**/*.{ts,tsx}"',
|
|
254
|
+
"type-check": "tsc --noEmit"
|
|
255
|
+
},
|
|
256
|
+
dependencies: {
|
|
257
|
+
"@tarojs/components": "3.6.23",
|
|
258
|
+
"@tarojs/runtime": "3.6.23",
|
|
259
|
+
"@tarojs/taro": "3.6.23",
|
|
260
|
+
"@tarojs/react": "3.6.23",
|
|
261
|
+
"react": "^18.2.0"
|
|
262
|
+
},
|
|
263
|
+
devDependencies: {
|
|
264
|
+
"@tarojs/cli": "3.6.23",
|
|
265
|
+
"@types/react": "^18.2.45",
|
|
266
|
+
"typescript": "^5.3.3"
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
await fs.writeJson(path.join(dir, "package.json"), packageJson, { spaces: 2 });
|
|
270
|
+
}
|
|
271
|
+
async function createMinimalTypes(config) {
|
|
272
|
+
const dir = "packages/types";
|
|
273
|
+
await fs.ensureDir(dir);
|
|
274
|
+
const packageJson = {
|
|
275
|
+
name: `${config.orgName}/types`,
|
|
276
|
+
version: "1.0.0",
|
|
277
|
+
description: "Shared type definitions",
|
|
278
|
+
main: "./dist/index.js",
|
|
279
|
+
types: "./dist/index.d.ts",
|
|
280
|
+
scripts: {
|
|
281
|
+
build: "tsup src/index.ts --format cjs,esm --dts",
|
|
282
|
+
dev: "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
283
|
+
"type-check": "tsc --noEmit"
|
|
284
|
+
},
|
|
285
|
+
devDependencies: {
|
|
286
|
+
"tsup": "^8.0.1",
|
|
287
|
+
"typescript": "^5.3.3"
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
await fs.writeJson(path.join(dir, "package.json"), packageJson, { spaces: 2 });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/utils/template.ts
|
|
294
|
+
async function generateFromTemplate(config) {
|
|
295
|
+
await createRootFiles(config);
|
|
296
|
+
await copyTemplateFiles({
|
|
297
|
+
projectName: config.projectName,
|
|
298
|
+
orgName: config.orgName,
|
|
299
|
+
template: config.template,
|
|
300
|
+
projectPath: config.projectPath
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
async function createRootFiles(config) {
|
|
304
|
+
const { projectName, orgName, packageManager } = config;
|
|
305
|
+
const packageJson = {
|
|
306
|
+
name: projectName,
|
|
307
|
+
version: "1.0.0",
|
|
308
|
+
private: true,
|
|
309
|
+
description: `Full-stack application based on Svton architecture`,
|
|
310
|
+
scripts: {
|
|
311
|
+
dev: "turbo run dev",
|
|
312
|
+
[`dev:backend`]: `turbo run dev --filter=${orgName}/backend`,
|
|
313
|
+
[`dev:admin`]: `turbo run dev --filter=${orgName}/admin`,
|
|
314
|
+
[`dev:mobile`]: `turbo run dev --filter=${orgName}/mobile`,
|
|
315
|
+
build: "turbo run build",
|
|
316
|
+
lint: "turbo run lint",
|
|
317
|
+
"type-check": "turbo run type-check",
|
|
318
|
+
clean: "turbo run clean && rm -rf node_modules"
|
|
319
|
+
},
|
|
320
|
+
devDependencies: {
|
|
321
|
+
turbo: "^1.11.0",
|
|
322
|
+
typescript: "^5.3.0",
|
|
323
|
+
"@types/node": "^20.10.0",
|
|
324
|
+
prettier: "^3.1.0",
|
|
325
|
+
eslint: "^8.55.0"
|
|
326
|
+
},
|
|
327
|
+
packageManager: `${packageManager}@8.12.0`,
|
|
328
|
+
engines: {
|
|
329
|
+
node: ">=18.0.0",
|
|
330
|
+
pnpm: ">=8.0.0"
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
await fs2.writeJson("package.json", packageJson, { spaces: 2 });
|
|
334
|
+
const workspaceConfig = `packages:
|
|
335
|
+
- 'apps/*'
|
|
336
|
+
- 'packages/*'
|
|
337
|
+
`;
|
|
338
|
+
await fs2.writeFile("pnpm-workspace.yaml", workspaceConfig);
|
|
339
|
+
const turboConfig = {
|
|
340
|
+
"$schema": "https://turbo.build/schema.json",
|
|
341
|
+
pipeline: {
|
|
342
|
+
build: {
|
|
343
|
+
dependsOn: ["^build"],
|
|
344
|
+
outputs: ["dist/**", ".next/**", "build/**"]
|
|
345
|
+
},
|
|
346
|
+
dev: {
|
|
347
|
+
cache: false,
|
|
348
|
+
persistent: true
|
|
349
|
+
},
|
|
350
|
+
lint: {
|
|
351
|
+
outputs: []
|
|
352
|
+
},
|
|
353
|
+
"type-check": {
|
|
354
|
+
dependsOn: ["^build"],
|
|
355
|
+
outputs: []
|
|
356
|
+
},
|
|
357
|
+
clean: {
|
|
358
|
+
cache: false
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
await fs2.writeJson("turbo.json", turboConfig, { spaces: 2 });
|
|
363
|
+
const gitignore = `# Dependencies
|
|
364
|
+
node_modules/
|
|
365
|
+
.pnpm-store/
|
|
366
|
+
|
|
367
|
+
# Build outputs
|
|
368
|
+
dist/
|
|
369
|
+
build/
|
|
370
|
+
.next/
|
|
371
|
+
.turbo/
|
|
372
|
+
|
|
373
|
+
# Environment
|
|
374
|
+
.env
|
|
375
|
+
.env.local
|
|
376
|
+
.env.*.local
|
|
377
|
+
|
|
378
|
+
# IDE
|
|
379
|
+
.idea/
|
|
380
|
+
.vscode/
|
|
381
|
+
*.swp
|
|
382
|
+
*.swo
|
|
383
|
+
|
|
384
|
+
# OS
|
|
385
|
+
.DS_Store
|
|
386
|
+
Thumbs.db
|
|
387
|
+
|
|
388
|
+
# Logs
|
|
389
|
+
*.log
|
|
390
|
+
npm-debug.log*
|
|
391
|
+
yarn-debug.log*
|
|
392
|
+
yarn-error.log*
|
|
393
|
+
|
|
394
|
+
# Testing
|
|
395
|
+
coverage/
|
|
396
|
+
|
|
397
|
+
# Misc
|
|
398
|
+
*.tsbuildinfo
|
|
399
|
+
`;
|
|
400
|
+
await fs2.writeFile(".gitignore", gitignore);
|
|
401
|
+
const npmrc = `auto-install-peers=true
|
|
402
|
+
strict-peer-dependencies=false
|
|
403
|
+
`;
|
|
404
|
+
await fs2.writeFile(".npmrc", npmrc);
|
|
405
|
+
const dockerCompose = `version: '3.8'
|
|
406
|
+
|
|
407
|
+
services:
|
|
408
|
+
mysql:
|
|
409
|
+
image: mysql:8.0
|
|
410
|
+
container_name: ${projectName}-mysql
|
|
411
|
+
restart: unless-stopped
|
|
412
|
+
environment:
|
|
413
|
+
MYSQL_ROOT_PASSWORD: root123456
|
|
414
|
+
MYSQL_DATABASE: ${projectName}
|
|
415
|
+
MYSQL_USER: ${projectName}
|
|
416
|
+
MYSQL_PASSWORD: ${projectName}123456
|
|
417
|
+
ports:
|
|
418
|
+
- '3306:3306'
|
|
419
|
+
volumes:
|
|
420
|
+
- mysql_data:/var/lib/mysql
|
|
421
|
+
command: --default-authentication-plugin=mysql_native_password
|
|
422
|
+
|
|
423
|
+
redis:
|
|
424
|
+
image: redis:7-alpine
|
|
425
|
+
container_name: ${projectName}-redis
|
|
426
|
+
restart: unless-stopped
|
|
427
|
+
ports:
|
|
428
|
+
- '6379:6379'
|
|
429
|
+
volumes:
|
|
430
|
+
- redis_data:/data
|
|
431
|
+
|
|
432
|
+
volumes:
|
|
433
|
+
mysql_data:
|
|
434
|
+
redis_data:
|
|
435
|
+
`;
|
|
436
|
+
await fs2.writeFile("docker-compose.yml", dockerCompose);
|
|
437
|
+
const readme = await generateReadme(config);
|
|
438
|
+
await fs2.writeFile("README.md", readme);
|
|
439
|
+
}
|
|
440
|
+
async function generateReadme(config) {
|
|
441
|
+
const { projectName, orgName, template } = config;
|
|
442
|
+
return `# ${projectName}
|
|
443
|
+
|
|
444
|
+
> Based on Svton architecture - Full-stack application
|
|
445
|
+
|
|
446
|
+
## \u{1F680} Quick Start
|
|
447
|
+
|
|
448
|
+
### Prerequisites
|
|
449
|
+
|
|
450
|
+
- Node.js >= 18.0.0
|
|
451
|
+
- pnpm >= 8.0.0
|
|
452
|
+
- Docker (for MySQL and Redis)
|
|
453
|
+
|
|
454
|
+
### Installation
|
|
455
|
+
|
|
456
|
+
\`\`\`bash
|
|
457
|
+
# Install dependencies
|
|
458
|
+
pnpm install
|
|
459
|
+
|
|
460
|
+
# Start databases
|
|
461
|
+
docker-compose up -d
|
|
462
|
+
|
|
463
|
+
# Configure environment variables
|
|
464
|
+
cp apps/backend/.env.example apps/backend/.env
|
|
465
|
+
|
|
466
|
+
# Generate Prisma client
|
|
467
|
+
pnpm --filter ${orgName}/backend prisma:generate
|
|
468
|
+
|
|
469
|
+
# Run database migrations
|
|
470
|
+
pnpm --filter ${orgName}/backend prisma:migrate
|
|
471
|
+
|
|
472
|
+
# Start development servers
|
|
473
|
+
pnpm dev
|
|
474
|
+
\`\`\`
|
|
475
|
+
|
|
476
|
+
### Services
|
|
477
|
+
|
|
478
|
+
| Service | URL |
|
|
479
|
+
|---------|-----|
|
|
480
|
+
| Backend API | http://localhost:3000 |
|
|
481
|
+
| Admin Panel | http://localhost:3001 |
|
|
482
|
+
| API Docs | http://localhost:3000/api-docs |
|
|
483
|
+
|
|
484
|
+
## \u{1F4C1} Project Structure
|
|
485
|
+
|
|
486
|
+
\`\`\`
|
|
487
|
+
${projectName}/
|
|
488
|
+
\u251C\u2500\u2500 apps/
|
|
489
|
+
${template === "full-stack" || template === "backend-only" ? "\u2502 \u251C\u2500\u2500 backend/ # " + orgName + "/backend - NestJS API" : ""}
|
|
490
|
+
${template === "full-stack" || template === "admin-only" ? "\u2502 \u251C\u2500\u2500 admin/ # " + orgName + "/admin - Next.js Admin Panel" : ""}
|
|
491
|
+
${template === "full-stack" || template === "mobile-only" ? "\u2502 \u2514\u2500\u2500 mobile/ # " + orgName + "/mobile - Taro Mini Program" : ""}
|
|
492
|
+
\u251C\u2500\u2500 packages/
|
|
493
|
+
\u2502 \u2514\u2500\u2500 types/ # ${orgName}/types - Shared Type Definitions
|
|
494
|
+
\u251C\u2500\u2500 package.json
|
|
495
|
+
\u251C\u2500\u2500 pnpm-workspace.yaml
|
|
496
|
+
\u251C\u2500\u2500 turbo.json
|
|
497
|
+
\u2514\u2500\u2500 docker-compose.yml
|
|
498
|
+
\`\`\`
|
|
499
|
+
|
|
500
|
+
## \u{1F6E0}\uFE0F Commands
|
|
501
|
+
|
|
502
|
+
\`\`\`bash
|
|
503
|
+
pnpm dev # Start all services
|
|
504
|
+
pnpm dev:backend # Start backend only
|
|
505
|
+
pnpm dev:admin # Start admin panel only
|
|
506
|
+
pnpm dev:mobile # Start mobile app only
|
|
507
|
+
pnpm build # Build all projects
|
|
508
|
+
pnpm lint # Run linting
|
|
509
|
+
pnpm clean # Clean build artifacts
|
|
510
|
+
\`\`\`
|
|
511
|
+
|
|
512
|
+
## \u{1F4DA} Documentation
|
|
513
|
+
|
|
514
|
+
- [Svton Architecture](https://github.com/svton/svton)
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
Generated with \`create-svton-app\`
|
|
519
|
+
`;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// src/utils/install.ts
|
|
523
|
+
import { execSync } from "child_process";
|
|
524
|
+
async function installDependencies(packageManager) {
|
|
525
|
+
try {
|
|
526
|
+
const command = getInstallCommand(packageManager);
|
|
527
|
+
logger.debug(`Running: ${command}`);
|
|
528
|
+
execSync(command, {
|
|
529
|
+
stdio: "inherit",
|
|
530
|
+
cwd: process.cwd()
|
|
531
|
+
});
|
|
532
|
+
} catch (error) {
|
|
533
|
+
throw new Error(`Failed to install dependencies with ${packageManager}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function getInstallCommand(packageManager) {
|
|
537
|
+
switch (packageManager) {
|
|
538
|
+
case "npm":
|
|
539
|
+
return "npm install";
|
|
540
|
+
case "yarn":
|
|
541
|
+
return "yarn install";
|
|
542
|
+
case "pnpm":
|
|
543
|
+
return "pnpm install";
|
|
544
|
+
default:
|
|
545
|
+
throw new Error(`Unsupported package manager: ${packageManager}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/utils/git.ts
|
|
550
|
+
import { execSync as execSync2 } from "child_process";
|
|
551
|
+
async function initGit(projectName) {
|
|
552
|
+
try {
|
|
553
|
+
try {
|
|
554
|
+
execSync2("git status", { stdio: "ignore" });
|
|
555
|
+
logger.debug("Git repository already exists, skipping initialization");
|
|
556
|
+
return;
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
execSync2("git init", { stdio: "ignore" });
|
|
560
|
+
execSync2("git add .", { stdio: "ignore" });
|
|
561
|
+
execSync2(`git commit -m "feat: initialize ${projectName} project"`, {
|
|
562
|
+
stdio: "ignore"
|
|
563
|
+
});
|
|
564
|
+
logger.debug("Git repository initialized successfully");
|
|
565
|
+
} catch (error) {
|
|
566
|
+
logger.warn("Failed to initialize Git repository");
|
|
567
|
+
logger.debug(error instanceof Error ? error.message : String(error));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/commands/create.ts
|
|
572
|
+
async function createProject(projectName, options = {}) {
|
|
573
|
+
try {
|
|
574
|
+
const validation = validateNpmPackageName(projectName);
|
|
575
|
+
if (!validation.validForNewPackages) {
|
|
576
|
+
logger.error(`Invalid project name: ${projectName}`);
|
|
577
|
+
if (validation.errors) {
|
|
578
|
+
validation.errors.forEach((error) => logger.error(`- ${error}`));
|
|
579
|
+
}
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
const projectPath = path2.resolve(process.cwd(), projectName);
|
|
583
|
+
if (await fs3.pathExists(projectPath)) {
|
|
584
|
+
logger.error(`Directory ${projectName} already exists!`);
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
logger.info(chalk2.blue("\u{1F680} Welcome to Svton App Generator!"));
|
|
588
|
+
logger.info("");
|
|
589
|
+
const answers = await inquirer.prompt([
|
|
590
|
+
{
|
|
591
|
+
type: "input",
|
|
592
|
+
name: "org",
|
|
593
|
+
message: "Organization name (will be used as @org/package-name):",
|
|
594
|
+
default: options.org || projectName,
|
|
595
|
+
validate: (input) => {
|
|
596
|
+
if (!input.trim()) return "Organization name is required";
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
type: "list",
|
|
602
|
+
name: "template",
|
|
603
|
+
message: "Choose a template:",
|
|
604
|
+
choices: [
|
|
605
|
+
{ name: "Full Stack (Backend + Admin + Mobile)", value: "full-stack" },
|
|
606
|
+
{ name: "Backend Only (NestJS + Prisma)", value: "backend-only" },
|
|
607
|
+
{ name: "Admin Only (Next.js)", value: "admin-only" },
|
|
608
|
+
{ name: "Mobile Only (Taro)", value: "mobile-only" }
|
|
609
|
+
],
|
|
610
|
+
default: options.template || "full-stack"
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
type: "list",
|
|
614
|
+
name: "packageManager",
|
|
615
|
+
message: "Package manager:",
|
|
616
|
+
choices: ["pnpm", "npm", "yarn"],
|
|
617
|
+
default: options.packageManager || "pnpm"
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
type: "confirm",
|
|
621
|
+
name: "installDeps",
|
|
622
|
+
message: "Install dependencies?",
|
|
623
|
+
default: !options.skipInstall
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
type: "confirm",
|
|
627
|
+
name: "initGit",
|
|
628
|
+
message: "Initialize Git repository?",
|
|
629
|
+
default: !options.skipGit
|
|
630
|
+
}
|
|
631
|
+
]);
|
|
632
|
+
const config = {
|
|
633
|
+
projectName,
|
|
634
|
+
orgName: answers.org.startsWith("@") ? answers.org : `@${answers.org}`,
|
|
635
|
+
template: answers.template,
|
|
636
|
+
packageManager: answers.packageManager,
|
|
637
|
+
installDeps: answers.installDeps,
|
|
638
|
+
initGit: answers.initGit,
|
|
639
|
+
projectPath
|
|
640
|
+
};
|
|
641
|
+
logger.info("");
|
|
642
|
+
logger.info(chalk2.cyan("\u{1F4CB} Project Configuration:"));
|
|
643
|
+
logger.info(` Project Name: ${chalk2.white(config.projectName)}`);
|
|
644
|
+
logger.info(` Organization: ${chalk2.white(config.orgName)}`);
|
|
645
|
+
logger.info(` Template: ${chalk2.white(config.template)}`);
|
|
646
|
+
logger.info(` Package Manager: ${chalk2.white(config.packageManager)}`);
|
|
647
|
+
logger.info(` Install Dependencies: ${chalk2.white(config.installDeps ? "Yes" : "No")}`);
|
|
648
|
+
logger.info(` Initialize Git: ${chalk2.white(config.initGit ? "Yes" : "No")}`);
|
|
649
|
+
logger.info("");
|
|
650
|
+
const { proceed } = await inquirer.prompt([
|
|
651
|
+
{
|
|
652
|
+
type: "confirm",
|
|
653
|
+
name: "proceed",
|
|
654
|
+
message: "Proceed with project creation?",
|
|
655
|
+
default: true
|
|
656
|
+
}
|
|
657
|
+
]);
|
|
658
|
+
if (!proceed) {
|
|
659
|
+
logger.info(chalk2.yellow("Project creation cancelled."));
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
await createProjectFromTemplate(config);
|
|
663
|
+
logger.info("");
|
|
664
|
+
logger.success(chalk2.green("\u{1F389} Project created successfully!"));
|
|
665
|
+
logger.info("");
|
|
666
|
+
logger.info(chalk2.cyan("Next steps:"));
|
|
667
|
+
logger.info(` ${chalk2.gray("$")} cd ${projectName}`);
|
|
668
|
+
if (!config.installDeps) {
|
|
669
|
+
logger.info(` ${chalk2.gray("$")} ${config.packageManager} install`);
|
|
670
|
+
}
|
|
671
|
+
if (config.template === "full-stack" || config.template === "backend-only") {
|
|
672
|
+
logger.info(` ${chalk2.gray("$")} docker-compose up -d`);
|
|
673
|
+
logger.info(` ${chalk2.gray("$")} ${config.packageManager} --filter ${config.orgName}/backend prisma:generate`);
|
|
674
|
+
logger.info(` ${chalk2.gray("$")} ${config.packageManager} --filter ${config.orgName}/backend prisma:migrate`);
|
|
675
|
+
}
|
|
676
|
+
logger.info(` ${chalk2.gray("$")} ${config.packageManager} dev`);
|
|
677
|
+
logger.info("");
|
|
678
|
+
logger.info(chalk2.gray("Happy coding! \u{1F680}"));
|
|
679
|
+
} catch (error) {
|
|
680
|
+
logger.error("Failed to create project:");
|
|
681
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async function createProjectFromTemplate(config) {
|
|
686
|
+
const spinner = ora("Creating project...").start();
|
|
687
|
+
try {
|
|
688
|
+
await fs3.ensureDir(config.projectPath);
|
|
689
|
+
process.chdir(config.projectPath);
|
|
690
|
+
spinner.text = "Generating project files...";
|
|
691
|
+
await generateFromTemplate(config);
|
|
692
|
+
if (config.installDeps) {
|
|
693
|
+
spinner.text = "Installing dependencies...";
|
|
694
|
+
await installDependencies(config.packageManager);
|
|
695
|
+
}
|
|
696
|
+
if (config.initGit) {
|
|
697
|
+
spinner.text = "Initializing Git repository...";
|
|
698
|
+
await initGit(config.projectName);
|
|
699
|
+
}
|
|
700
|
+
spinner.succeed("Project created successfully!");
|
|
701
|
+
} catch (error) {
|
|
702
|
+
spinner.fail("Failed to create project");
|
|
703
|
+
throw error;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// package.json
|
|
708
|
+
var version = "1.0.0";
|
|
709
|
+
|
|
710
|
+
// src/index.ts
|
|
711
|
+
function cli() {
|
|
712
|
+
const program = new Command();
|
|
713
|
+
program.name("svton").description("Svton CLI - Create full-stack applications with NestJS, Next.js, and Taro").version(version);
|
|
714
|
+
program.command("create <project-name>").alias("init").alias("new").description("Create a new Svton project").option("-o, --org <name>", "Organization name (default: project name)").option("--skip-install", "Skip installing dependencies").option("--skip-git", "Skip Git initialization").option("-t, --template <template>", "Template to use", "full-stack").option("-p, --package-manager <pm>", "Package manager to use", "pnpm").action(createProject);
|
|
715
|
+
program.parse();
|
|
716
|
+
}
|
|
717
|
+
if (__require.main === module) {
|
|
718
|
+
cli();
|
|
719
|
+
}
|
|
720
|
+
export {
|
|
721
|
+
cli
|
|
722
|
+
};
|