gridsum-vue3-pc 1.1.1 → 1.2.1

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.
@@ -15,6 +15,8 @@ const { version: CLI_VERSION } = JSON.parse(
15
15
 
16
16
  const MIN_NODE_VERSION = 18;
17
17
  const DEFAULT_NAME = 'vue3-project';
18
+ const VALID_CICD = ['none', 'github', 'gitlab', 'jenkins'];
19
+ const DIR_NAME_RE = /^[a-zA-Z0-9-_]+$/;
18
20
 
19
21
  const TEMPLATES = {
20
22
  base: path.join(__dirname, '../template/base'),
@@ -26,10 +28,6 @@ const TEMPLATES = {
26
28
  }
27
29
  };
28
30
 
29
- const VALID_CICD = ['none', 'github', 'gitlab', 'jenkins'];
30
-
31
- const DIR_NAME_RE = /^[a-zA-Z0-9-_]+$/;
32
-
33
31
  const helpMessage = `
34
32
  ${pc.bold(pc.cyan('gridsum-vue3-pc'))} ${pc.dim(`v${CLI_VERSION}`)}
35
33
 
@@ -43,6 +41,7 @@ ${pc.bold('Options:')}
43
41
  -c, --cicd <type> CI/CD type ${pc.dim('(github, gitlab, jenkins, none)')}
44
42
  -f, --force Force overwrite if directory exists
45
43
  --auto-start Auto start dev server after creation
44
+ --no-git Skip git initialization
46
45
  --no-auto-install Skip automatic dependency installation
47
46
  --no-interactive Run in non-interactive mode
48
47
  -h, --help Display this message
@@ -60,8 +59,8 @@ ${pc.bold('Examples:')}
60
59
  `;
61
60
 
62
61
  function formatTargetDir(input) {
63
- const dir = input.trim().replace(/[\/\\]+$/g, '');
64
- if (!dir || /[<>:"|?*\x00-\x1f]/.test(dir) || dir.includes('..') || dir.includes('/') || dir.includes('\\')) {
62
+ const dir = input.trim().replace(/[/\\]+$/g, '');
63
+ if (!dir || /[<>:"|?*\\/]/.test(dir) || dir.includes('..') || [...dir].some(c => c < ' ')) {
65
64
  return '';
66
65
  }
67
66
  return dir;
@@ -72,12 +71,7 @@ function isValidPackageName(name) {
72
71
  }
73
72
 
74
73
  function toValidPackageName(name) {
75
- return name
76
- .trim()
77
- .toLowerCase()
78
- .replace(/\s+/g, '-')
79
- .replace(/^[._]/, '')
80
- .replace(/[^a-z\d\-~]+/g, '-');
74
+ return name.trim().toLowerCase().replace(/\s+/g, '-').replace(/^[._]/, '').replace(/[^a-z\d\-~]+/g, '-');
81
75
  }
82
76
 
83
77
  function isEmpty(dir) {
@@ -91,21 +85,17 @@ function emptyDir(dir) {
91
85
  for (const file of fs.readdirSync(dir)) {
92
86
  if (file === '.git') continue;
93
87
  const target = path.resolve(dir, file);
94
- const namespaced = target.length > 260 ? path.toNamespacedPath(target) : target;
95
- fs.rmSync(namespaced, { recursive: true, force: true });
88
+ fs.rmSync(target.length > 260 ? path.toNamespacedPath(target) : target, { recursive: true, force: true });
96
89
  }
97
90
  }
98
91
 
99
92
  function copyDir(srcDir, destDir) {
100
- if (!fs.existsSync(srcDir)) {
101
- throw new Error(`Template directory not found: ${srcDir}`);
102
- }
93
+ if (!fs.existsSync(srcDir)) throw new Error(`Template directory not found: ${srcDir}`);
103
94
  fs.mkdirSync(destDir, { recursive: true });
104
95
  for (const file of fs.readdirSync(srcDir)) {
105
96
  const srcFile = path.resolve(srcDir, file);
106
97
  const destFile = path.resolve(destDir, file);
107
- const stat = fs.statSync(srcFile);
108
- if (stat.isDirectory()) {
98
+ if (fs.statSync(srcFile).isDirectory()) {
109
99
  copyDir(srcFile, destFile);
110
100
  } else {
111
101
  fs.copyFileSync(srcFile, destFile);
@@ -128,30 +118,16 @@ function getInstallCommand(pkgManager) {
128
118
  }
129
119
 
130
120
  function getRunCommand(pkgManager, script) {
131
- switch (pkgManager) {
132
- case 'yarn':
133
- case 'pnpm':
134
- case 'bun':
135
- return [pkgManager, script];
136
- default:
137
- return [pkgManager, 'run', script];
138
- }
121
+ return ['yarn', 'pnpm', 'bun'].includes(pkgManager)
122
+ ? [pkgManager, script]
123
+ : [pkgManager, 'run', script];
139
124
  }
140
125
 
141
126
  function runCommand(command, options = {}) {
142
127
  const [cmd, ...args] = command;
143
- const result = spawn.sync(cmd, args, {
144
- ...options,
145
- stdio: 'inherit',
146
- });
147
-
148
- if (result.error) {
149
- throw new Error(`Failed to execute "${cmd}": ${result.error.message}`);
150
- }
151
-
152
- if (result.status != null && result.status > 0) {
153
- throw new Error(`"${cmd}" exited with code ${result.status}`);
154
- }
128
+ const result = spawn.sync(cmd, args, { ...options, stdio: 'inherit' });
129
+ if (result.error) throw new Error(`Failed to execute "${cmd}": ${result.error.message}`);
130
+ if (result.status != null && result.status > 0) throw new Error(`"${cmd}" exited with code ${result.status}`);
155
131
  }
156
132
 
157
133
  function checkNodeVersion() {
@@ -170,107 +146,73 @@ Please upgrade Node.js:
170
146
 
171
147
  function parseArgs() {
172
148
  const argv = mri(process.argv.slice(2), {
173
- boolean: ['help', 'version', 'force', 'auto-start', 'no-interactive', 'no-auto-install'],
149
+ boolean: ['help', 'version', 'force', 'auto-start', 'no-interactive', 'no-auto-install', 'no-git'],
174
150
  string: ['name', 'title', 'cicd'],
175
- alias: {
176
- h: 'help',
177
- v: 'version',
178
- n: 'name',
179
- t: 'title',
180
- c: 'cicd',
181
- f: 'force',
182
- },
151
+ alias: { h: 'help', v: 'version', n: 'name', t: 'title', c: 'cicd', f: 'force' },
183
152
  });
184
-
185
153
  argv._targetDir = argv._[0];
186
154
  argv.interactive = !argv['no-interactive'];
187
155
  argv.autoInstall = argv['no-auto-install'] ? false : undefined;
188
-
156
+ argv.git = argv['no-git'] !== undefined ? !argv['no-git'] : undefined;
189
157
  return argv;
190
158
  }
191
159
 
192
- function scaffoldProject(root, answers, packageName) {
193
- fs.mkdirSync(root, { recursive: true });
194
-
195
- copyDir(TEMPLATES.base, root);
196
- copyDir(TEMPLATES.ts, root);
197
-
198
- if (answers.cicd !== 'none') {
199
- const cicdDir = TEMPLATES.cicd[answers.cicd];
200
- if (!cicdDir || !fs.existsSync(cicdDir)) {
201
- throw new Error(`CI/CD template "${answers.cicd}" not found at expected path: ${cicdDir}`);
202
- }
203
- copyDir(cicdDir, root);
204
- }
160
+ function validateOptions(options) {
161
+ const errors = [];
162
+ if (options.name && !DIR_NAME_RE.test(options.name))
163
+ errors.push('Project name must contain only letters, numbers, dash and underscore');
164
+ if (options.cicd && !VALID_CICD.includes(options.cicd))
165
+ errors.push(`Invalid CI/CD type "${options.cicd}". Valid: ${VALID_CICD.join(', ')}`);
166
+ if (options.title && !/^[a-zA-Z0-9_\-\u4e00-\u9fa5 ]+$/.test(options.title))
167
+ errors.push('Project title contains invalid characters');
168
+ return errors;
169
+ }
205
170
 
171
+ function updateProjectFiles(root, answers, packageName) {
206
172
  const pkgPath = path.join(root, 'package.json');
207
173
  if (fs.existsSync(pkgPath)) {
208
- try {
209
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
210
- pkg.name = packageName;
211
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
212
- } catch (e) {
213
- throw new Error(`Failed to update package.json: ${e.message}`);
214
- }
174
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
175
+ pkg.name = packageName;
176
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
215
177
  }
216
178
 
217
179
  for (const file of ['.env', '.env.production']) {
218
180
  const filePath = path.join(root, file);
219
181
  if (fs.existsSync(filePath)) {
220
- try {
221
- let content = fs.readFileSync(filePath, 'utf-8');
222
- content = content.replace(/^VITE_APP_TITLE=.*$/m, `VITE_APP_TITLE=${answers.title}`);
223
- fs.writeFileSync(filePath, content);
224
- } catch (e) {
225
- throw new Error(`Failed to update ${file}: ${e.message}`);
226
- }
182
+ let content = fs.readFileSync(filePath, 'utf-8');
183
+ content = content.replace(/^VITE_APP_TITLE=.*$/m, `VITE_APP_TITLE=${answers.title}`);
184
+ fs.writeFileSync(filePath, content);
227
185
  }
228
186
  }
229
187
 
230
188
  const indexPath = path.join(root, 'index.html');
231
189
  if (fs.existsSync(indexPath)) {
232
- try {
233
- let content = fs.readFileSync(indexPath, 'utf-8');
234
- content = content.replace(/<title>[^<]*<\/title>/, `<title>${answers.title}</title>`);
235
- fs.writeFileSync(indexPath, content);
236
- } catch (e) {
237
- throw new Error(`Failed to update index.html: ${e.message}`);
238
- }
190
+ let content = fs.readFileSync(indexPath, 'utf-8');
191
+ content = content.replace(/<title>[^<]*<\/title>/, `<title>${answers.title}</title>`);
192
+ fs.writeFileSync(indexPath, content);
239
193
  }
240
194
  }
241
195
 
242
- function validateOptions(options) {
243
- const errors = [];
244
-
245
- if (options.name && !DIR_NAME_RE.test(options.name)) {
246
- errors.push('Project name must contain only letters, numbers, dash and underscore');
247
- }
248
-
249
- if (options.cicd && !VALID_CICD.includes(options.cicd)) {
250
- errors.push(`Invalid CI/CD type "${options.cicd}". Valid: ${VALID_CICD.join(', ')}`);
251
- }
252
-
253
- if (options.title && !/^[a-zA-Z0-9_\-\u4e00-\u9fa5 ]+$/.test(options.title)) {
254
- errors.push('Project title contains invalid characters (allowed: letters, numbers, Chinese, spaces, dash, underscore)');
196
+ function initGit(root) {
197
+ try {
198
+ runCommand(['git', 'init', '--initial-branch=main'], { cwd: root, stdio: 'pipe' });
199
+ const gitignore = path.join(root, '.gitignore');
200
+ if (fs.existsSync(gitignore)) {
201
+ runCommand(['git', 'add', '.'], { cwd: root, stdio: 'pipe' });
202
+ runCommand(['git', 'commit', '-m', 'Initial project scaffold'], { cwd: root, stdio: 'pipe' });
203
+ }
204
+ return true;
205
+ } catch {
206
+ return false;
255
207
  }
256
-
257
- return errors;
258
208
  }
259
209
 
260
210
  async function main() {
261
211
  checkNodeVersion();
262
-
263
212
  const argv = parseArgs();
264
213
 
265
- if (argv.help) {
266
- console.log(helpMessage);
267
- process.exit(0);
268
- }
269
-
270
- if (argv.version) {
271
- console.log(`v${CLI_VERSION}`);
272
- process.exit(0);
273
- }
214
+ if (argv.help) { console.log(helpMessage); process.exit(0); }
215
+ if (argv.version) { console.log(`v${CLI_VERSION}`); process.exit(0); }
274
216
 
275
217
  const validationErrors = validateOptions(argv);
276
218
  if (validationErrors.length > 0) {
@@ -283,13 +225,9 @@ async function main() {
283
225
  const isInteractive = argv.interactive && process.stdin.isTTY;
284
226
  const pkgManager = detectPackageManager();
285
227
 
286
- const cancel = (exitCode = 0) => {
287
- prompts.cancel('Operation cancelled');
288
- process.exit(exitCode);
289
- };
228
+ const cancel = (exitCode = 0) => { prompts.cancel('Operation cancelled'); process.exit(exitCode); };
290
229
 
291
230
  let targetDir = argv._targetDir ? formatTargetDir(argv._targetDir) : undefined;
292
-
293
231
  if (targetDir === '') {
294
232
  console.error(`\n${pc.red('Error:')} Invalid directory name "${argv._targetDir}"\n`);
295
233
  process.exit(1);
@@ -303,12 +241,8 @@ async function main() {
303
241
  placeholder: DEFAULT_NAME,
304
242
  validate: (value) => {
305
243
  const dir = formatTargetDir(value);
306
- if (!dir || dir.length === 0) {
307
- return 'Project name cannot be empty';
308
- }
309
- if (!DIR_NAME_RE.test(dir)) {
310
- return 'Only letters, numbers, dash and underscore allowed';
311
- }
244
+ if (!dir || dir.length === 0) return 'Project name cannot be empty';
245
+ if (!DIR_NAME_RE.test(dir)) return 'Only letters, numbers, dash and underscore allowed';
312
246
  },
313
247
  });
314
248
  if (prompts.isCancel(result)) return cancel();
@@ -322,7 +256,6 @@ async function main() {
322
256
 
323
257
  if (fs.existsSync(root) && !isEmpty(root)) {
324
258
  let action = argv.force ? 'overwrite' : undefined;
325
-
326
259
  if (!action) {
327
260
  if (isInteractive) {
328
261
  const result = await prompts.select({
@@ -339,17 +272,10 @@ async function main() {
339
272
  action = 'cancel';
340
273
  }
341
274
  }
342
-
343
275
  switch (action) {
344
- case 'overwrite':
345
- emptyDir(root);
346
- break;
347
- case 'ignore':
348
- break;
349
- case 'cancel': {
350
- const exitCode = argv.interactive ? 0 : 1;
351
- return cancel(exitCode);
352
- }
276
+ case 'overwrite': emptyDir(root); break;
277
+ case 'ignore': break;
278
+ case 'cancel': return cancel(argv.interactive ? 0 : 1);
353
279
  }
354
280
  }
355
281
 
@@ -360,11 +286,7 @@ async function main() {
360
286
  message: 'Package name:',
361
287
  defaultValue: toValidPackageName(packageName),
362
288
  placeholder: toValidPackageName(packageName),
363
- validate: (value) => {
364
- if (value && !isValidPackageName(value)) {
365
- return 'Invalid package.json name (must match: @scope/name or name)';
366
- }
367
- },
289
+ validate: (value) => { if (value && !isValidPackageName(value)) return 'Invalid package.json name'; },
368
290
  });
369
291
  if (prompts.isCancel(result)) return cancel();
370
292
  packageName = result;
@@ -373,11 +295,7 @@ async function main() {
373
295
  }
374
296
  }
375
297
 
376
- let answers = {
377
- name: targetDir,
378
- title: argv.title || 'Vue3 PC Template',
379
- cicd: argv.cicd || 'none',
380
- };
298
+ let answers = { name: targetDir, title: argv.title || 'Vue3 PC Template', cicd: argv.cicd || 'none' };
381
299
 
382
300
  if (isInteractive) {
383
301
  const title = await prompts.text({
@@ -403,32 +321,44 @@ async function main() {
403
321
  }
404
322
 
405
323
  const s = isInteractive ? prompts.spinner() : null;
406
- if (s) {
407
- s.start('Scaffolding project...');
408
- } else {
409
- prompts.log.step('Scaffolding project...');
410
- }
324
+ if (s) { s.start('Scaffolding project...'); } else { prompts.log.step('Scaffolding project...'); }
411
325
 
412
326
  try {
413
- scaffoldProject(root, answers, packageName);
327
+ fs.mkdirSync(root, { recursive: true });
328
+ copyDir(TEMPLATES.base, root);
329
+ copyDir(TEMPLATES.ts, root);
330
+
331
+ if (answers.cicd !== 'none') {
332
+ const cicdDir = TEMPLATES.cicd[answers.cicd];
333
+ if (!cicdDir || !fs.existsSync(cicdDir))
334
+ throw new Error(`CI/CD template "${answers.cicd}" not found`);
335
+ copyDir(cicdDir, root);
336
+ }
337
+
338
+ updateProjectFiles(root, answers, packageName);
414
339
  } catch (e) {
415
340
  if (s) s.stop(pc.red('Scaffolding failed'));
416
341
  console.error(`\n${pc.red('Error:')} ${e.message}\n`);
417
342
  process.exit(1);
418
343
  }
419
344
 
420
- if (s) {
421
- s.stop('Project created successfully!');
422
- } else {
423
- prompts.log.success('Project created successfully!');
345
+ if (s) { s.stop('Project created successfully!'); } else { prompts.log.success('Project created successfully!'); }
346
+
347
+ let doGit = argv.git;
348
+ if (doGit === undefined && isInteractive) {
349
+ const result = await prompts.confirm({ message: 'Initialize git repository?', initialValue: true });
350
+ if (prompts.isCancel(result)) return cancel();
351
+ doGit = result;
352
+ }
353
+ if (doGit) {
354
+ if (initGit(root)) {
355
+ process.stdout.write(`${pc.green('✓')} Git repository initialized\n`);
356
+ }
424
357
  }
425
358
 
426
359
  let autoInstall = argv.autoInstall;
427
360
  if (autoInstall === undefined && isInteractive) {
428
- const result = await prompts.confirm({
429
- message: `Install dependencies with ${pkgManager}?`,
430
- initialValue: true,
431
- });
361
+ const result = await prompts.confirm({ message: `Install dependencies with ${pkgManager}?`, initialValue: true });
432
362
  if (prompts.isCancel(result)) return cancel();
433
363
  autoInstall = result;
434
364
  }
@@ -438,7 +368,7 @@ async function main() {
438
368
  try {
439
369
  runCommand(getInstallCommand(pkgManager), { cwd: root });
440
370
  process.stdout.write(`${pc.green('✓')} Dependencies installed!\n`);
441
- } catch (e) {
371
+ } catch (_) {
442
372
  process.stdout.write(`\n${pc.red('✗')} Installation failed\n`);
443
373
  console.error(` ${pc.dim('You can retry manually:')}`);
444
374
  console.error(` ${pc.cyan(`cd ${targetDir} && ${pkgManager} install${pkgManager === 'npm' ? ' --legacy-peer-deps' : ''}`)}\n`);
@@ -448,10 +378,7 @@ async function main() {
448
378
 
449
379
  let autoStart = argv['auto-start'];
450
380
  if (autoStart === undefined && isInteractive && autoInstall) {
451
- const result = await prompts.confirm({
452
- message: 'Start dev server now?',
453
- initialValue: false,
454
- });
381
+ const result = await prompts.confirm({ message: 'Start dev server now?', initialValue: false });
455
382
  if (prompts.isCancel(result)) return cancel();
456
383
  autoStart = result;
457
384
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gridsum-vue3-pc",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Gridsum Vue3 Vite PC Template Generator - 快速生成基于 Vue3 + Vite + TypeScript 的企业级 PC 端项目",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -41,7 +41,6 @@
41
41
  "type": "github",
42
42
  "url": "https://github.com/sponsors/gridsum"
43
43
  },
44
-
45
44
  "engines": {
46
45
  "node": ">=18.0.0"
47
46
  },
@@ -52,11 +51,12 @@
52
51
  "picocolors": "^1.1.1"
53
52
  },
54
53
  "devDependencies": {
54
+ "@eslint/js": "^9.0.0",
55
55
  "@types/cross-spawn": "^6.0.6",
56
56
  "eslint": "^10.5.0"
57
57
  },
58
58
  "scripts": {
59
- "lint": "eslint bin/ --ext .mjs --fix",
59
+ "lint": "eslint bin/ --fix",
60
60
  "test": "node bin/create-vue3-pc.mjs --help",
61
61
  "test:create": "node bin/create-vue3-pc.mjs test-project --name test-project --title \"Test Project\" --no-interactive && npm --prefix test-project install --legacy-peer-deps && npm --prefix test-project run lint && npm --prefix test-project run typecheck && npm --prefix test-project run build",
62
62
  "posttest:create": "node -e \"const fs = require('fs'); if (fs.existsSync('test-project')) fs.rmSync('test-project', { recursive: true, force: true }); console.log('cleaned up test-project');\"",
@@ -8,13 +8,15 @@ module.exports = {
8
8
  extends: [
9
9
  'eslint:recommended',
10
10
  'plugin:vue/vue3-recommended',
11
- 'prettier',
12
11
  ],
12
+ ignorePatterns: ['dist/', 'node_modules/'],
13
+ parser: '@typescript-eslint/parser',
13
14
  parserOptions: {
14
15
  ecmaVersion: 2021,
15
16
  sourceType: 'module',
17
+ extraFileExtensions: ['.vue'],
16
18
  },
17
- plugins: ['vue'],
19
+ plugins: ['vue', '@typescript-eslint'],
18
20
  rules: {
19
21
  'vue/multi-word-component-names': 'off',
20
22
  'no-unused-vars': 'warn',
@@ -1,6 +1,3 @@
1
1
  module.exports = {
2
- extends: [
3
- 'stylelint-config-standard',
4
- 'stylelint-config-prettier',
5
- ],
2
+ extends: ['stylelint-config-standard'],
6
3
  };
@@ -3,7 +3,7 @@ FROM node:20-alpine AS builder
3
3
  WORKDIR /app
4
4
 
5
5
  COPY package.json package-lock.json* ./
6
- RUN npm ci
6
+ RUN npm install --legacy-peer-deps
7
7
 
8
8
  COPY . .
9
9
  RUN npm run build