esa-cli 0.0.2-beta.14 → 0.0.2-beta.15

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.
@@ -9,14 +9,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { execSync } from 'child_process';
11
11
  import path from 'path';
12
+ import { confirm as clackConfirm, isCancel, log, outro } from '@clack/prompts';
12
13
  import chalk from 'chalk';
13
14
  import fs from 'fs-extra';
14
- import inquirer from 'inquirer';
15
15
  import t from '../../i18n/index.js';
16
16
  import logger from '../../libs/logger.js';
17
17
  import Template from '../../libs/templates/index.js';
18
18
  import { getDirName } from '../../utils/fileUtils/base.js';
19
- import { getProjectConfig } from '../../utils/fileUtils/index.js';
19
+ import { generateConfigFile, getCliConfig, getProjectConfig, getTemplatesConfig, templateHubPath, updateProjectConfigFile } from '../../utils/fileUtils/index.js';
20
+ import { execCommand } from '../../utils/command.js';
21
+ import promptParameter from '../../utils/prompt.js';
22
+ import Haikunator from 'haikunator';
23
+ import { commitAndDeployVersion } from '../common/utils.js';
24
+ import { exit } from 'process';
20
25
  export const getTemplateInstances = (templateHubPath) => {
21
26
  return fs
22
27
  .readdirSync(templateHubPath)
@@ -131,12 +136,11 @@ export function checkAndUpdatePackage(packageName) {
131
136
  ' ' +
132
137
  t('display_latest_esa_template_version').d(`Latest esa-template version:`) +
133
138
  chalk.green(latestVersion));
134
- const { isUpdate } = yield inquirer.prompt({
135
- type: 'confirm',
136
- name: 'isUpdate',
139
+ logger.stopSpinner();
140
+ const isUpdate = yield clackConfirm({
137
141
  message: t('is_update_to_latest_version').d('Do you want to update templates to latest version?')
138
142
  });
139
- if (isUpdate) {
143
+ if (!isCancel(isUpdate) && isUpdate) {
140
144
  spinner.start(t('template_updating').d('Updating templates to latest...'));
141
145
  execSync(`rm -rf node_modules/${packageName}`, {
142
146
  cwd: packageJsonPath
@@ -185,3 +189,475 @@ export const getAllFrameworkConfig = () => {
185
189
  const json = JSON.parse(jsonc);
186
190
  return json;
187
191
  };
192
+ export function getInitParamsFromArgv(argv) {
193
+ const a = argv;
194
+ const HaikunatorCtor = Haikunator;
195
+ const haikunator = new HaikunatorCtor();
196
+ const params = {
197
+ name: ''
198
+ };
199
+ if (a.yes) {
200
+ params.name = haikunator.haikunate();
201
+ params.git = true;
202
+ params.deploy = true;
203
+ params.template = 'Hello World';
204
+ params.framework = undefined;
205
+ params.language = undefined;
206
+ params.yes = true;
207
+ }
208
+ if (typeof a.name === 'string')
209
+ params.name = a.name;
210
+ if (typeof a.template === 'string' && a.template) {
211
+ params.template = a.template;
212
+ params.framework = undefined;
213
+ params.language = undefined;
214
+ }
215
+ else {
216
+ const fw = a.framework;
217
+ const lang = a.language;
218
+ if (fw) {
219
+ params.framework = fw;
220
+ }
221
+ if (lang) {
222
+ params.language = lang;
223
+ }
224
+ }
225
+ if (typeof a.git === 'boolean')
226
+ params.git = Boolean(a.git);
227
+ if (typeof a.deploy === 'boolean')
228
+ params.deploy = Boolean(a.deploy);
229
+ return params;
230
+ }
231
+ // 配置项目名称
232
+ export const configProjectName = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
233
+ if (initParams.name) {
234
+ log.step(`Project name configured ${initParams.name}`);
235
+ return;
236
+ }
237
+ const name = (yield promptParameter({
238
+ type: 'text',
239
+ question: `${t('init_input_name').d('Enter the name of edgeRoutine:')}`,
240
+ label: 'Project name',
241
+ validate: (input) => {
242
+ const regex = /^[a-z0-9-]{2,}$/;
243
+ if (!regex.test(input)) {
244
+ return t('init_name_error').d('Error: The project name must be at least 2 characters long and can only contain lowercase letters, numbers, and hyphens.');
245
+ }
246
+ return true;
247
+ }
248
+ }));
249
+ initParams.name = name;
250
+ });
251
+ export const configCategory = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
252
+ if (initParams.category || initParams.framework || initParams.template) {
253
+ return;
254
+ }
255
+ const initMode = (yield promptParameter({
256
+ type: 'select',
257
+ question: 'How would you like to initialize the project?',
258
+ label: 'Init mode',
259
+ choices: [
260
+ { name: 'Framework Starter', value: 'framework' },
261
+ {
262
+ name: 'Function Template',
263
+ value: 'template'
264
+ }
265
+ ]
266
+ }));
267
+ initParams.category = initMode;
268
+ });
269
+ /*
270
+ 选择模板
271
+ 如果选择的是framework,则选择具体的模版 vue /react等
272
+ 如果选择的是template,则选择具体的模版 esa template
273
+ */
274
+ export const configTemplate = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
275
+ if (initParams.template) {
276
+ log.step(`Template configured ${initParams.template}`);
277
+ return;
278
+ }
279
+ if (initParams.framework) {
280
+ log.step(`Framework configured ${initParams.framework}`);
281
+ return;
282
+ }
283
+ if (initParams.category === 'template') {
284
+ const templateItems = prepareTemplateItems();
285
+ const selectedTemplatePath = yield promptParameter({
286
+ type: 'multiLevelSelect',
287
+ question: 'Select a template:',
288
+ treeItems: templateItems
289
+ });
290
+ if (!selectedTemplatePath)
291
+ return null;
292
+ // TODO
293
+ initParams.template = selectedTemplatePath;
294
+ }
295
+ else {
296
+ const allFrameworkConfig = getAllFrameworkConfig();
297
+ const fw = (yield promptParameter({
298
+ type: 'select',
299
+ question: 'Select a framework',
300
+ label: 'Framework',
301
+ choices: Object.keys(allFrameworkConfig).map((fw) => {
302
+ var _a;
303
+ return ({
304
+ name: allFrameworkConfig[fw].label,
305
+ value: fw,
306
+ hint: (_a = allFrameworkConfig[fw]) === null || _a === void 0 ? void 0 : _a.hint
307
+ });
308
+ })
309
+ }));
310
+ initParams.framework = fw;
311
+ }
312
+ });
313
+ export const configLanguage = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
314
+ if (initParams.language) {
315
+ log.info(`Language configured ${initParams.language}`);
316
+ return;
317
+ }
318
+ const framework = initParams.framework;
319
+ if (!framework) {
320
+ log.info('Framework config not configured, language skipped');
321
+ return;
322
+ }
323
+ const frameworkConfig = getFrameworkConfig(framework);
324
+ if (frameworkConfig.language) {
325
+ const language = (yield promptParameter({
326
+ type: 'select',
327
+ question: t('init_language_select').d('Select programming language:'),
328
+ label: 'Language',
329
+ choices: [
330
+ {
331
+ name: t('init_language_typescript').d('TypeScript (.ts) - Type-safe JavaScript, recommended'),
332
+ value: 'typescript'
333
+ },
334
+ {
335
+ name: t('init_language_javascript').d('JavaScript (.js) - Traditional JavaScript'),
336
+ value: 'javascript'
337
+ }
338
+ ],
339
+ defaultValue: 'typescript'
340
+ }));
341
+ initParams.language = language;
342
+ }
343
+ });
344
+ export const createProject = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
345
+ var _a;
346
+ if (initParams.template) {
347
+ // resolve template value: it may be a filesystem path or a template title
348
+ let selectedTemplatePath = initParams.template;
349
+ if (!path.isAbsolute(selectedTemplatePath) ||
350
+ !fs.existsSync(selectedTemplatePath)) {
351
+ const instances = getTemplateInstances(templateHubPath);
352
+ const matched = instances.find((it) => it.title === initParams.template);
353
+ if (matched) {
354
+ selectedTemplatePath = matched.path;
355
+ }
356
+ }
357
+ if (!fs.existsSync(selectedTemplatePath)) {
358
+ outro(`Project creation failed: cannot resolve template "${initParams.template}"`);
359
+ exit(1);
360
+ }
361
+ const res = yield initializeProject(selectedTemplatePath, initParams.name);
362
+ if (!res) {
363
+ outro(`Project creation failed`);
364
+ exit(1);
365
+ }
366
+ }
367
+ if (initParams.framework) {
368
+ const framework = initParams.framework;
369
+ const frameworkConfig = getFrameworkConfig(framework);
370
+ const command = frameworkConfig.command;
371
+ const templateFlag = ((_a = frameworkConfig.language) === null || _a === void 0 ? void 0 : _a[initParams.language || 'typescript']) || '';
372
+ const extraParams = frameworkConfig.params || '';
373
+ const full = `${command} ${initParams.name} ${templateFlag} ${extraParams}`.trim();
374
+ const res = yield execCommand(['sh', '-lc', full], {
375
+ interactive: true,
376
+ startText: `Starting to execute framework command: ${chalk.gray(full)}`,
377
+ doneText: `Framework command executed: ${chalk.bold(full)}`
378
+ });
379
+ if (!res.success) {
380
+ outro(`Framework command execution failed`);
381
+ exit(1);
382
+ }
383
+ }
384
+ });
385
+ export const installDependencies = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
386
+ if (initParams.template) {
387
+ return;
388
+ }
389
+ const targetPath = path.join(process.cwd(), initParams.name);
390
+ const res = yield execCommand(['npm', 'install'], {
391
+ cwd: targetPath,
392
+ useSpinner: true,
393
+ silent: true,
394
+ startText: 'Installing dependencies',
395
+ doneText: 'Dependencies installed'
396
+ });
397
+ if (!res.success) {
398
+ outro(`Dependencies installation failed`);
399
+ exit(1);
400
+ }
401
+ });
402
+ /**
403
+ * Apply configured file edits (方式1: overwrite) after project scaffold
404
+ */
405
+ export const applyFileEdits = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
406
+ var _a;
407
+ if (!initParams.framework) {
408
+ return true;
409
+ }
410
+ const frameworkConfig = getFrameworkConfig(initParams.framework || '');
411
+ const edits = frameworkConfig.fileEdits || [];
412
+ if (!edits.length)
413
+ return true;
414
+ logger.startSubStep(`Applying file edits`);
415
+ const __dirname = getDirName(import.meta.url);
416
+ try {
417
+ const toRegexFromGlob = (pattern) => {
418
+ // Very small glob subset: *, ?, {a,b,c}
419
+ let escaped = pattern
420
+ .replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&') // escape regex specials first
421
+ .replace(/\\\*/g, '.*')
422
+ .replace(/\\\?/g, '.');
423
+ // restore and convert {a,b} to (a|b)
424
+ escaped = escaped.replace(/\\\{([^}]+)\\\}/g, (_, inner) => {
425
+ const parts = inner.split(',').map((s) => s.trim());
426
+ return `(${parts.join('|')})`;
427
+ });
428
+ return new RegExp('^' + escaped + '$');
429
+ };
430
+ const targetPath = path.join(process.cwd(), initParams.name);
431
+ const listRootFiles = () => {
432
+ try {
433
+ return fs.readdirSync(targetPath);
434
+ }
435
+ catch (_a) {
436
+ return [];
437
+ }
438
+ };
439
+ for (const edit of edits) {
440
+ if (((_a = edit.when) === null || _a === void 0 ? void 0 : _a.language) && initParams.language) {
441
+ if (edit.when.language !== initParams.language)
442
+ continue;
443
+ }
444
+ const createIfMissing = edit.createIfMissing !== false;
445
+ let matchedFiles = [];
446
+ if (edit.matchType === 'exact') {
447
+ matchedFiles = [edit.match];
448
+ }
449
+ else if (edit.matchType === 'glob') {
450
+ const regex = toRegexFromGlob(edit.match);
451
+ matchedFiles = listRootFiles().filter((name) => regex.test(name));
452
+ }
453
+ else if (edit.matchType === 'regex') {
454
+ const regex = new RegExp(edit.match);
455
+ matchedFiles = listRootFiles().filter((name) => regex.test(name));
456
+ }
457
+ // if no matched files and allowed to create, provide a reasonable default for common patterns
458
+ if (!matchedFiles.length && createIfMissing) {
459
+ if (edit.matchType === 'exact') {
460
+ matchedFiles = [edit.match];
461
+ }
462
+ else if (edit.matchType === 'glob' &&
463
+ /next\.config\.\{.*\}/.test(edit.match)) {
464
+ matchedFiles = ['next.config.ts'];
465
+ }
466
+ }
467
+ if (!matchedFiles.length)
468
+ continue;
469
+ // resolve content
470
+ let payload = null;
471
+ if (edit.fromFile) {
472
+ const absFrom = path.isAbsolute(edit.fromFile)
473
+ ? edit.fromFile
474
+ : path.join(__dirname, edit.fromFile);
475
+ payload = fs.readFileSync(absFrom, 'utf-8');
476
+ }
477
+ else if (typeof edit.content === 'string') {
478
+ payload = edit.content;
479
+ }
480
+ for (const rel of matchedFiles) {
481
+ const abs = path.join(targetPath, rel);
482
+ if (payload == null)
483
+ continue;
484
+ fs.ensureDirSync(path.dirname(abs));
485
+ fs.writeFileSync(abs, payload, 'utf-8');
486
+ }
487
+ }
488
+ logger.endSubStep('File edits applied');
489
+ return true;
490
+ }
491
+ catch (_b) {
492
+ outro(`File edits application failed`);
493
+ exit(1);
494
+ }
495
+ });
496
+ export const installESACli = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
497
+ const targetPath = path.join(process.cwd(), initParams.name);
498
+ const res = yield execCommand(['npm', 'install', '-D', 'esa-cli'], {
499
+ cwd: targetPath,
500
+ useSpinner: true,
501
+ silent: true,
502
+ startText: 'Installing ESA CLI',
503
+ doneText: 'ESA CLI installed in the project'
504
+ });
505
+ if (!res.success) {
506
+ outro(`ESA CLI installation failed`);
507
+ exit(1);
508
+ }
509
+ });
510
+ export const updateConfigFile = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
511
+ var _a, _b;
512
+ const targetPath = path.join(process.cwd(), initParams.name);
513
+ const configFormat = 'jsonc';
514
+ logger.startSubStep(`Updating config file`);
515
+ try {
516
+ if (initParams.framework) {
517
+ const frameworkConfig = getFrameworkConfig(initParams.framework);
518
+ const assetsDirectory = (_a = frameworkConfig.assets) === null || _a === void 0 ? void 0 : _a.directory;
519
+ const notFoundStrategy = (_b = frameworkConfig.assets) === null || _b === void 0 ? void 0 : _b.notFoundStrategy;
520
+ yield generateConfigFile(initParams.name, {
521
+ assets: assetsDirectory ? { directory: assetsDirectory } : undefined
522
+ }, targetPath, configFormat, notFoundStrategy);
523
+ }
524
+ else {
525
+ // TODO revise template config file later
526
+ // console.log(
527
+ // 'test:',
528
+ // initParams.name,
529
+ // undefined,
530
+ // targetPath,
531
+ // configFormat
532
+ // );
533
+ // logger.startSubStep(`Updating config file`);
534
+ // await generateConfigFile(initParams.name, undefined, targetPath, 'toml');
535
+ }
536
+ logger.endSubStep('Config file updated');
537
+ }
538
+ catch (_c) {
539
+ outro(`Config file update failed`);
540
+ exit(1);
541
+ }
542
+ });
543
+ export const initGit = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
544
+ const frameworkConfig = getFrameworkConfig(initParams.framework || '');
545
+ if ((frameworkConfig === null || frameworkConfig === void 0 ? void 0 : frameworkConfig.useGit) === false) {
546
+ log.step('Git skipped');
547
+ return true;
548
+ }
549
+ const gitInstalled = yield isGitInstalled();
550
+ if (!gitInstalled) {
551
+ log.step('You have not installed Git, Git skipped');
552
+ return true;
553
+ }
554
+ if (!initParams.git) {
555
+ const initGit = (yield promptParameter({
556
+ type: 'confirm',
557
+ question: t('init_git').d('Do you want to init git in your project?'),
558
+ label: 'Init git',
559
+ defaultValue: false
560
+ }));
561
+ initParams.git = initGit;
562
+ }
563
+ if (initParams.git) {
564
+ const targetPath = path.join(process.cwd(), initParams.name);
565
+ const res = yield execCommand(['git', 'init'], {
566
+ cwd: targetPath
567
+ });
568
+ if (!res.success) {
569
+ outro(`Git initialization failed`);
570
+ exit(1);
571
+ }
572
+ }
573
+ return true;
574
+ });
575
+ export function getGitVersion() {
576
+ return __awaiter(this, void 0, void 0, function* () {
577
+ try {
578
+ const stdout = yield execCommand(['git', '--version'], {
579
+ useSpinner: false,
580
+ silent: true,
581
+ captureOutput: true
582
+ });
583
+ const gitVersion = stdout.stdout.replace(/^git\s+version\s+/, '');
584
+ return gitVersion;
585
+ }
586
+ catch (_a) {
587
+ log.error('Failed to get Git version');
588
+ return null;
589
+ }
590
+ });
591
+ }
592
+ export function isGitInstalled() {
593
+ return __awaiter(this, void 0, void 0, function* () {
594
+ return (yield getGitVersion()) !== null;
595
+ });
596
+ }
597
+ export const buildProject = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
598
+ if (initParams.template) {
599
+ return;
600
+ }
601
+ const targetPath = path.join(process.cwd(), initParams.name);
602
+ const res = yield execCommand(['npm', 'run', 'build'], {
603
+ useSpinner: true,
604
+ silent: true,
605
+ startText: 'Building project',
606
+ doneText: 'Project built',
607
+ cwd: targetPath
608
+ });
609
+ if (!res.success) {
610
+ outro(`Build project failed`);
611
+ exit(1);
612
+ }
613
+ });
614
+ export function prepareTemplateItems() {
615
+ var _a;
616
+ const templateInstanceList = getTemplateInstances(templateHubPath);
617
+ const templateConfig = getTemplatesConfig();
618
+ const cliConfig = getCliConfig();
619
+ const lang = (_a = cliConfig === null || cliConfig === void 0 ? void 0 : cliConfig.lang) !== null && _a !== void 0 ? _a : 'en';
620
+ return transferTemplatesToSelectItem(templateConfig, templateInstanceList, lang);
621
+ }
622
+ export const deployProject = (initParams) => __awaiter(void 0, void 0, void 0, function* () {
623
+ if (!initParams.deploy) {
624
+ log.step('Deploy project skipped');
625
+ return;
626
+ }
627
+ const targetPath = path.join(process.cwd(), initParams.name);
628
+ const res = yield commitAndDeployVersion(initParams.name, undefined, undefined, 'Init project', targetPath, 'all');
629
+ if (!res) {
630
+ outro(`Deploy project failed`);
631
+ exit(1);
632
+ }
633
+ });
634
+ export function initializeProject(selectedTemplatePath, name) {
635
+ return __awaiter(this, void 0, void 0, function* () {
636
+ const selectTemplate = new Template(selectedTemplatePath, name);
637
+ const projectConfig = getProjectConfig(selectedTemplatePath);
638
+ if (!projectConfig) {
639
+ logger.notInProject();
640
+ return null;
641
+ }
642
+ const targetPath = path.join(process.cwd(), name);
643
+ if (fs.existsSync(targetPath)) {
644
+ logger.block();
645
+ logger.tree([
646
+ `${chalk.bgRed(' ERROR ')} ${chalk.bold.red(t('init_abort').d('Initialization aborted'))}`,
647
+ `${chalk.gray(t('reason').d('Reason:'))} ${chalk.red(t('dir_already_exists').d('Target directory already exists'))}`,
648
+ `${chalk.gray(t('path').d('Path:'))} ${chalk.cyan(targetPath)}`,
649
+ chalk.gray(t('try').d('Try one of the following:')),
650
+ `- ${chalk.white(t('try_diff_name').d('Choose a different project name'))}`,
651
+ `- ${chalk.white(t('try_remove').d('Remove the directory:'))} ${chalk.yellow(`rm -rf "${name}”`)}`,
652
+ `- ${chalk.white(t('try_another_dir').d('Run the command in another directory'))}`
653
+ ]);
654
+ logger.block();
655
+ return null;
656
+ }
657
+ yield fs.copy(selectedTemplatePath, targetPath);
658
+ projectConfig.name = name;
659
+ yield updateProjectConfigFile(projectConfig, targetPath);
660
+ yield preInstallDependencies(targetPath);
661
+ return { template: selectTemplate, targetPath };
662
+ });
663
+ }