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