@blocklet/component-studio-cli 0.4.134

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.
Files changed (84) hide show
  1. package/README.md +84 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +11 -0
  4. package/dist/commands/add.d.ts +2 -0
  5. package/dist/commands/add.js +140 -0
  6. package/dist/commands/component-studio.d.ts +2 -0
  7. package/dist/commands/component-studio.js +70 -0
  8. package/dist/commands/dev.d.ts +2 -0
  9. package/dist/commands/dev.js +87 -0
  10. package/dist/commands/init.d.ts +2 -0
  11. package/dist/commands/init.js +114 -0
  12. package/dist/commands/migrate.d.ts +2 -0
  13. package/dist/commands/migrate.js +177 -0
  14. package/dist/commands/update.d.ts +2 -0
  15. package/dist/commands/update.js +22 -0
  16. package/dist/utils/display-logo.d.ts +2 -0
  17. package/dist/utils/display-logo.js +24 -0
  18. package/dist/utils/helper.d.ts +97 -0
  19. package/dist/utils/helper.js +479 -0
  20. package/package.json +58 -0
  21. package/templates/add/components/HelloWorld/@metadata.json +433 -0
  22. package/templates/add/components/HelloWorld/@template.json +4 -0
  23. package/templates/add/components/HelloWorld/index.tsx +560 -0
  24. package/templates/add/tools/cursor-rules/.cursor/rules/@metadata-json.mdc +549 -0
  25. package/templates/add/tools/cursor-rules/.cursor/rules/component-studio.mdc +138 -0
  26. package/templates/add/tools/cursor-rules/.cursor/rules/index-tsx.mdc +192 -0
  27. package/templates/add/tools/cursor-rules/@template.json +4 -0
  28. package/templates/init/0-basic/@template.json +4 -0
  29. package/templates/init/0-basic/README.md +30 -0
  30. package/templates/init/0-basic/package.json +26 -0
  31. package/templates/init/0-basic/src/HelloWorld/@metadata.json +433 -0
  32. package/templates/init/0-basic/src/HelloWorld/index.tsx +560 -0
  33. package/templates/init/1-professional/@template.json +4 -0
  34. package/templates/init/1-professional/README.md +75 -0
  35. package/templates/init/1-professional/biome.json +36 -0
  36. package/templates/init/1-professional/global.d.ts +131 -0
  37. package/templates/init/1-professional/package.json +73 -0
  38. package/templates/init/1-professional/scripts/bump-version.mjs +35 -0
  39. package/templates/init/1-professional/src/atoms/AnimationWrapper.tsx +95 -0
  40. package/templates/init/1-professional/src/atoms/ArrayTable.tsx +114 -0
  41. package/templates/init/1-professional/src/atoms/Card.tsx +52 -0
  42. package/templates/init/1-professional/src/atoms/ContentWrapper.tsx +72 -0
  43. package/templates/init/1-professional/src/atoms/CopyrightFooter.tsx +31 -0
  44. package/templates/init/1-professional/src/atoms/DataDisplays.tsx +157 -0
  45. package/templates/init/1-professional/src/atoms/GradientTitle.tsx +64 -0
  46. package/templates/init/1-professional/src/atoms/Logo.tsx +58 -0
  47. package/templates/init/1-professional/src/atoms/index.ts +27 -0
  48. package/templates/init/1-professional/src/components/HelloWorld/@metadata.json +433 -0
  49. package/templates/init/1-professional/src/components/HelloWorld/index.tsx +224 -0
  50. package/templates/init/1-professional/src/type.d.ts +42 -0
  51. package/templates/init/1-professional/src/utils/index.ts +1 -0
  52. package/templates/init/1-professional/tsconfig.json +102 -0
  53. package/templates/init/1-professional/version +1 -0
  54. package/templates/init/2-blank/@template.json +4 -0
  55. package/templates/init/2-blank/README.md +27 -0
  56. package/templates/init/2-blank/package.json +26 -0
  57. package/templates/workspace/.component-studio-runtime/_theme.tsx +4 -0
  58. package/templates/workspace/.editorconfig +23 -0
  59. package/templates/workspace/.env +1 -0
  60. package/templates/workspace/.init-component-studio +0 -0
  61. package/templates/workspace/LICENSE +13 -0
  62. package/templates/workspace/README.md +127 -0
  63. package/templates/workspace/api/dev.ts +21 -0
  64. package/templates/workspace/api/src/index.ts +50 -0
  65. package/templates/workspace/api/src/libs/auth.ts +17 -0
  66. package/templates/workspace/api/src/libs/env.ts +6 -0
  67. package/templates/workspace/api/src/libs/logger.ts +3 -0
  68. package/templates/workspace/api/src/routes/index.ts +12 -0
  69. package/templates/workspace/api/third.d.ts +17 -0
  70. package/templates/workspace/biome.json +36 -0
  71. package/templates/workspace/blocklet.md +8 -0
  72. package/templates/workspace/blocklet.yml +58 -0
  73. package/templates/workspace/index.html +15 -0
  74. package/templates/workspace/logo.png +0 -0
  75. package/templates/workspace/package.json +125 -0
  76. package/templates/workspace/pnpm-workspace.yaml +3 -0
  77. package/templates/workspace/scripts/build-clean.mjs +6 -0
  78. package/templates/workspace/scripts/bump-version.mjs +39 -0
  79. package/templates/workspace/scripts/init-component-studio.mjs +36 -0
  80. package/templates/workspace/tsconfig.api.json +12 -0
  81. package/templates/workspace/tsconfig.json +102 -0
  82. package/templates/workspace/version +1 -0
  83. package/templates/workspace/vite-server.config.ts +39 -0
  84. package/templates/workspace/vite.config.ts +68 -0
@@ -0,0 +1,479 @@
1
+ import chalk from 'chalk';
2
+ import { spawn } from 'child_process';
3
+ import { execSync } from 'child_process';
4
+ import { glob } from 'glob';
5
+ import inquirer from 'inquirer';
6
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
7
+ import { cp } from 'node:fs/promises';
8
+ import { readdir, mkdir, readFile, writeFile } from 'node:fs/promises';
9
+ import { join, resolve, dirname, basename, relative } from 'node:path';
10
+ import ora from 'ora';
11
+ import os from 'os';
12
+ const workspaceVersion = '0.1.0';
13
+ const METADATA_FILE_NAME = '@metadata.json';
14
+ // 获取CLI内置的workspace模板路径
15
+ export function getWorkspaceTemplatePath() {
16
+ return join(__dirname, '../../templates/workspace');
17
+ }
18
+ // 获取用户电脑的workspace路径
19
+ export function getWorkspacePath() {
20
+ const homeDir = os.homedir();
21
+ const workspaceHash = workspaceVersion;
22
+ const workspaceDir = join(homeDir, `.component-studio`, workspaceHash);
23
+ return workspaceDir;
24
+ }
25
+ // 获取当前项目路径
26
+ export function getProjectPath() {
27
+ const projectPath = resolve(process.cwd());
28
+ return projectPath;
29
+ }
30
+ /**
31
+ * 复制模板文件,但排除某些特定文件
32
+ * @param sourcePath - 源模板路径
33
+ * @param targetPath - 目标项目路径
34
+ */
35
+ export async function copyTemplateFilesExcept(sourcePath, targetPath) {
36
+ // 需要排除的文件
37
+ const excludeFiles = ['@template.json', 'package.json'];
38
+ try {
39
+ // 读取源目录中的所有文件和目录
40
+ const entries = await readdir(sourcePath, { withFileTypes: true });
41
+ for (const entry of entries) {
42
+ const sourceName = join(sourcePath, entry.name);
43
+ const targetName = join(targetPath, entry.name);
44
+ // 如果是目录,递归复制
45
+ if (entry.isDirectory()) {
46
+ await mkdir(targetName, { recursive: true });
47
+ await copyTemplateFilesExcept(sourceName, targetName);
48
+ }
49
+ // 如果是文件且不在排除列表中,则复制
50
+ else if (!excludeFiles.includes(entry.name)) {
51
+ await cp(sourceName, targetName);
52
+ }
53
+ }
54
+ }
55
+ catch (error) {
56
+ throw new Error(`Failed to copy template files: ${error.message}`);
57
+ }
58
+ }
59
+ /**
60
+ * 合并基础 package.json 和模板的 package.json
61
+ * @param basePath - 基础包路径
62
+ * @param templatePath - 模板路径
63
+ */
64
+ export async function mergePackageJson(basePath, templatePath) {
65
+ const excludeKeys = ['name', 'version'];
66
+ const basePackagePath = join(basePath, 'package.json');
67
+ const templatePackagePath = join(templatePath, 'package.json');
68
+ if (!existsSync(templatePackagePath)) {
69
+ return; // 没有模板 package.json,不需要合并
70
+ }
71
+ try {
72
+ // 读取基础 package.json
73
+ const basePackage = JSON.parse(await readFile(basePackagePath, 'utf8'));
74
+ // 读取模板 package.json
75
+ const templatePackage = JSON.parse(await readFile(templatePackagePath, 'utf8'));
76
+ // 合并包,保留基础包的 name 和 version
77
+ const name = basePackage.name;
78
+ const version = basePackage.version;
79
+ const mergedPackage = {
80
+ name, // 保留原名称
81
+ version, // 保留原版本
82
+ // 合并其余属性,但不覆盖name和version
83
+ ...Object.fromEntries(Object.entries(templatePackage).filter(([key]) => !excludeKeys.includes(key))),
84
+ // 合并依赖
85
+ dependencies: { ...basePackage.dependencies, ...templatePackage.dependencies },
86
+ devDependencies: { ...basePackage.devDependencies, ...templatePackage.devDependencies },
87
+ scripts: { ...basePackage.scripts, ...templatePackage.scripts },
88
+ };
89
+ // 写回合并后的 package.json
90
+ await writeFile(basePackagePath, JSON.stringify(mergedPackage, null, 2));
91
+ }
92
+ catch (error) {
93
+ console.warn(chalk.yellow('Failed to merge package.json files:', error));
94
+ }
95
+ }
96
+ /**
97
+ * 在指定目录安装依赖
98
+ * @param dirs - 需要安装依赖的目录对象,key为目录描述,value为目录路径
99
+ * @returns 安装结果
100
+ */
101
+ export async function installDependencies(dirs, operation = 'install') {
102
+ const baseDepsText = operation === 'update' ? 'Updating latest dependencies...' : 'Installing latest dependencies...';
103
+ const depsSpinner = ora({ text: baseDepsText, color: 'blue' }).start();
104
+ try {
105
+ const installPromises = Object.entries(dirs).map(([dirType, dirPath]) => {
106
+ return new Promise((resolve, reject) => {
107
+ const process = spawn('pnpm', ['update:deps'], {
108
+ cwd: dirPath,
109
+ // stdio: 'inherit',
110
+ });
111
+ process.on('close', (code) => {
112
+ if (code === 0) {
113
+ resolve();
114
+ }
115
+ else {
116
+ reject(new Error(`${dirType} dependencies install failed with code ${code}`));
117
+ }
118
+ });
119
+ });
120
+ });
121
+ // 制作一个假进度条,每秒增加 1%
122
+ let progress = 0;
123
+ let progressStep = 1;
124
+ const interval = setInterval(() => {
125
+ if (progress < 99.9) {
126
+ progress = parseFloat((progress + progressStep).toFixed(1));
127
+ depsSpinner.text = `${baseDepsText} ${progress}%`;
128
+ if (progress >= 70) {
129
+ progressStep = 0.3;
130
+ }
131
+ if (progress >= 90) {
132
+ progressStep = 0.1;
133
+ }
134
+ }
135
+ }, 1000);
136
+ await Promise.all(installPromises);
137
+ clearInterval(interval);
138
+ depsSpinner.succeed(chalk.green(operation === 'update'
139
+ ? 'Update latest dependencies successfully!'
140
+ : 'Install latest dependencies successfully!'));
141
+ return true;
142
+ }
143
+ catch (error) {
144
+ depsSpinner.fail(chalk.red('Failed to install dependencies'));
145
+ console.error(error);
146
+ return false;
147
+ }
148
+ }
149
+ /**
150
+ * 从模板目录读取所有可用模板
151
+ * @param templatesDir - 模板目录路径,默认为 templates/init
152
+ * @returns 可用模板数组
153
+ */
154
+ export function loadTemplates(templatesDir) {
155
+ // 如果没有提供路径,使用默认路径
156
+ const templatesDirPath = templatesDir;
157
+ if (!existsSync(templatesDirPath)) {
158
+ console.warn(chalk.yellow(`Templates directory not found: ${templatesDirPath}`));
159
+ return [];
160
+ }
161
+ try {
162
+ const templateFolders = readdirSync(templatesDirPath, { withFileTypes: true })
163
+ .filter((dirent) => dirent.isDirectory())
164
+ .map((dirent) => dirent.name);
165
+ return templateFolders.flatMap((folder) => {
166
+ const folderPath = join(templatesDirPath, folder);
167
+ // 检查是否是直接包含@template.json的目录
168
+ const metadataPath = join(folderPath, '@template.json');
169
+ if (existsSync(metadataPath)) {
170
+ try {
171
+ const metadata = JSON.parse(readFileSync(metadataPath, 'utf8'));
172
+ return [
173
+ {
174
+ key: folder,
175
+ name: metadata.name || folder,
176
+ description: metadata.description || `The description of this template is empty`,
177
+ },
178
+ ];
179
+ }
180
+ catch (error) {
181
+ console.warn(chalk.yellow(`Failed to parse metadata for template "${folder}"`));
182
+ }
183
+ }
184
+ // 检查子目录
185
+ const subFolders = readdirSync(folderPath, { withFileTypes: true })
186
+ .filter((dirent) => dirent.isDirectory())
187
+ .map((dirent) => dirent.name);
188
+ return subFolders.map((subFolder) => {
189
+ const subFolderPath = join(folderPath, subFolder);
190
+ const subMetadataPath = join(subFolderPath, '@template.json');
191
+ if (existsSync(subMetadataPath)) {
192
+ try {
193
+ const metadata = JSON.parse(readFileSync(subMetadataPath, 'utf8'));
194
+ return {
195
+ key: `${folder}/${subFolder}`,
196
+ name: metadata.name || subFolder,
197
+ description: metadata.description || `The description of this template is empty`,
198
+ };
199
+ }
200
+ catch (error) {
201
+ console.warn(chalk.yellow(`Failed to parse metadata for template "${folder}/${subFolder}"`));
202
+ }
203
+ }
204
+ // 默认元数据
205
+ return {
206
+ key: `${folder}/${subFolder}`,
207
+ name: subFolder,
208
+ description: `The description of this template is empty`,
209
+ };
210
+ });
211
+ });
212
+ }
213
+ catch (error) {
214
+ console.error(chalk.red('Error loading templates:'), error);
215
+ return [];
216
+ }
217
+ }
218
+ /**
219
+ * 交互式选择模板,支持模板分组
220
+ * @param templates - 模板数组
221
+ * @param defaultTemplate - 可选的默认模板键名
222
+ * @returns Promise<string> 选择的模板键名
223
+ */
224
+ export async function selectTemplate(templates, defaultTemplate, groupByDir) {
225
+ // 构建模板选择列表,按目录分组
226
+ const templateGroups = {};
227
+ templates.forEach((template) => {
228
+ // 使用第一级目录作为分组
229
+ const groupName = groupByDir ? template.key.split('/')[0] || 'other' : 'ungrouped';
230
+ if (!templateGroups[groupName]) {
231
+ templateGroups[groupName] = [];
232
+ }
233
+ templateGroups[groupName].push(template);
234
+ });
235
+ // 构建带分组的选项
236
+ const choices = [];
237
+ // 为每个分组添加标题和选项
238
+ Object.entries(templateGroups).forEach(([group, groupTemplates]) => {
239
+ if (groupByDir) {
240
+ // 添加分组标题
241
+ choices.push(new inquirer.Separator(`====== ${group.toUpperCase()} =====`));
242
+ }
243
+ // 添加该分组下的模板
244
+ groupTemplates.forEach((t) => {
245
+ choices.push({
246
+ name: `${t.name}${t.description ? ` - ${t.description}` : ''}`,
247
+ value: t.key,
248
+ });
249
+ });
250
+ });
251
+ // 确保有选择选项
252
+ const validChoices = choices.filter((c) => !c.disabled);
253
+ if (validChoices.length === 0) {
254
+ console.log(chalk.red('No valid templates found. Please check your installation.'));
255
+ process.exit(1);
256
+ }
257
+ // 默认选项:优先使用指定的默认模板,然后是basic模板,最后是第一个有效选项
258
+ let defaultChoice = defaultTemplate;
259
+ if (!defaultChoice) {
260
+ const basicTemplate = templates.find((t) => t.name === 'basic');
261
+ if (basicTemplate) {
262
+ defaultChoice = basicTemplate.key;
263
+ }
264
+ else {
265
+ // 找到第一个非分隔符的有效选项
266
+ const firstValid = choices.find((c) => !c.disabled);
267
+ defaultChoice = firstValid ? firstValid.value : '';
268
+ }
269
+ }
270
+ const { template } = await inquirer.prompt([
271
+ {
272
+ type: 'list',
273
+ name: 'template',
274
+ message: 'Select a template:',
275
+ choices,
276
+ default: defaultChoice,
277
+ },
278
+ ]);
279
+ return template;
280
+ }
281
+ /**
282
+ * 验证所选模板是否存在
283
+ * @param templateKey - 模板键名
284
+ * @param templates - 模板数组
285
+ * @returns 选中的模板或null
286
+ */
287
+ export function validateTemplate(templateKey, templates) {
288
+ const selectedTemplate = templates.find((t) => t.key === templateKey);
289
+ if (!selectedTemplate) {
290
+ console.log(chalk.red(`Template "${templateKey}" is not supported.`));
291
+ console.log(chalk.yellow('Available templates:'));
292
+ templates.forEach((t) => {
293
+ console.log(` - ${chalk.cyan(t.name)}${t.description ? `: ${t.description}` : ''}`);
294
+ });
295
+ return null;
296
+ }
297
+ return selectedTemplate;
298
+ }
299
+ /**
300
+ * 获取模板路径
301
+ * @param templateKey - 模板键名
302
+ * @param templatesBaseDir - 模板基础目录,默认为 templates/init
303
+ * @returns 模板完整路径
304
+ */
305
+ export function getTemplatePath(templateKey, templatesBaseDir) {
306
+ return join(templatesBaseDir, templateKey);
307
+ }
308
+ /**
309
+ * 在文件中批量替换占位符
310
+ * @param filePath - 文件路径
311
+ * @param replacements - 替换对象,键为占位符,值为替换内容
312
+ */
313
+ export async function replaceInFile(filePath, replacements) {
314
+ try {
315
+ // 读取文件内容
316
+ let content = await readFile(filePath, 'utf8');
317
+ // 执行所有替换
318
+ for (const [placeholder, value] of Object.entries(replacements)) {
319
+ const regex = new RegExp(placeholder, 'g');
320
+ content = content.replace(regex, value);
321
+ }
322
+ // 写回文件
323
+ await writeFile(filePath, content, 'utf8');
324
+ }
325
+ catch (error) {
326
+ console.error(chalk.red(`Error replacing placeholders in ${filePath}:`), error);
327
+ throw error;
328
+ }
329
+ }
330
+ /**
331
+ * 在目录中递归替换所有文件中的占位符
332
+ * @param dirPath - 目录路径
333
+ * @param replacements - 替换对象,键为占位符,值为替换内容
334
+ */
335
+ export async function replaceInDirectory(dirPath, replacements) {
336
+ try {
337
+ // 读取目录内容
338
+ const entries = await readdir(dirPath, { withFileTypes: true });
339
+ // 处理每个条目
340
+ for (const entry of entries) {
341
+ const fullPath = join(dirPath, entry.name);
342
+ if (entry.isDirectory()) {
343
+ // 如果是目录,递归处理
344
+ await replaceInDirectory(fullPath, replacements);
345
+ }
346
+ else if (entry.isFile()) {
347
+ // 如果是文件,执行替换
348
+ await replaceInFile(fullPath, replacements);
349
+ }
350
+ }
351
+ }
352
+ catch (error) {
353
+ console.error(chalk.red(`Error replacing placeholders in directory ${dirPath}:`), error);
354
+ throw error;
355
+ }
356
+ }
357
+ /**
358
+ * 查找最接近指定目录的包含@metadata.json的目录
359
+ * @param startDir - 起始目录
360
+ * @returns 找到的最近目录或null
361
+ */
362
+ export async function findClosestMetadataDir(startDir) {
363
+ try {
364
+ // 从startDir开始向上查找所有包含@metadata.json的目录
365
+ const metadataFiles = await glob(`**/${METADATA_FILE_NAME}`, {
366
+ cwd: startDir,
367
+ absolute: true,
368
+ });
369
+ // 按照目录层级深度排序,找到最近的目录
370
+ if (metadataFiles.length > 0) {
371
+ // 按路径长度排序,最短的路径通常是最接近startDir的
372
+ metadataFiles.sort((a, b) => a.length - b.length);
373
+ const firstFile = metadataFiles[0];
374
+ if (firstFile) {
375
+ const closestMetadataDir = dirname(firstFile);
376
+ return dirname(closestMetadataDir);
377
+ }
378
+ }
379
+ }
380
+ catch (error) {
381
+ console.warn(chalk.yellow('Error searching for metadata files:'), error);
382
+ }
383
+ return null;
384
+ }
385
+ /**
386
+ * 检查目标目录是否存在,如存在则询问是否覆盖
387
+ * @param targetDir - 目标目录路径
388
+ * @param itemType - 项目类型名称 (如 'component', 'tool')
389
+ * @param targetBaseDir - 基础目录路径(用于显示相对路径)
390
+ * @returns 是否可以继续操作(不存在或用户同意覆盖)
391
+ */
392
+ export async function checkTargetDirectoryExists(targetDir, targetBaseDir) {
393
+ const projectPath = getProjectPath();
394
+ // 检查目标目录是否已存在
395
+ if (existsSync(targetDir)) {
396
+ const answers = await inquirer.prompt([
397
+ {
398
+ type: 'confirm',
399
+ name: 'overwrite',
400
+ message: `The "${basename(targetDir)}" already exists in ${relative(projectPath, targetBaseDir) || projectPath}. Do you want to overwrite it?`,
401
+ default: false,
402
+ },
403
+ ]);
404
+ if (!answers.overwrite) {
405
+ console.log(chalk.yellow('Operation cancelled.'));
406
+ return false;
407
+ }
408
+ }
409
+ return true;
410
+ }
411
+ // 检查包是否已安装
412
+ export function isPackageInstalled(packageName, projectPath) {
413
+ try {
414
+ // 检查node_modules中是否存在该包
415
+ return existsSync(join(projectPath, 'node_modules', packageName));
416
+ }
417
+ catch (error) {
418
+ return false;
419
+ }
420
+ }
421
+ // 检查文件中是否引用了特定包
422
+ export function checkPackageReference(filePath, packageName) {
423
+ try {
424
+ const content = readFileSync(filePath, 'utf-8');
425
+ // 查找导入语句 (支持各种导入格式)
426
+ const importRegex = new RegExp(`(import|from)\\s+['"]${packageName.replace(/\//g, '\\/')}(\\/[^'"]+)?['"]`, 'g');
427
+ return importRegex.test(content);
428
+ }
429
+ catch (error) {
430
+ return false;
431
+ }
432
+ }
433
+ // 安装缺失的包
434
+ export function installMissingPackage(packageName, projectPath) {
435
+ try {
436
+ console.log(chalk.yellow(`\nDetected ${packageName} dependency. Installing...`));
437
+ // 尝试使用pnpm安装
438
+ execSync(`cd "${projectPath}" && pnpm add ${packageName}`, { stdio: 'inherit' });
439
+ return true;
440
+ }
441
+ catch (error) {
442
+ console.error(chalk.red(`Failed to install ${packageName}: ${error.message}`));
443
+ return false;
444
+ }
445
+ }
446
+ /**
447
+ * 检查系统中是否存在指定命令
448
+ * @param command - 要检查的命令名称
449
+ * @returns 命令是否存在
450
+ */
451
+ export function isCommandAvailable(command) {
452
+ try {
453
+ const isWindows = process.platform === 'win32';
454
+ const whichCommand = isWindows ? 'where' : 'which';
455
+ execSync(`${whichCommand} ${command}`, { stdio: 'ignore' });
456
+ return true;
457
+ }
458
+ catch (error) {
459
+ return false;
460
+ }
461
+ }
462
+ /**
463
+ * 安装全局NPM包
464
+ * @param packageName - 要安装的包名
465
+ * @returns 安装是否成功
466
+ */
467
+ export async function installGlobalPackage(packageName) {
468
+ const spinner = ora(`Installing ${packageName} globally...`).start();
469
+ try {
470
+ execSync(`npm install -g ${packageName}`, { stdio: 'ignore' });
471
+ spinner.succeed(`${packageName} installed successfully!`);
472
+ return true;
473
+ }
474
+ catch (error) {
475
+ spinner.fail(`Failed to install ${packageName}`);
476
+ console.error(error);
477
+ return false;
478
+ }
479
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@blocklet/component-studio-cli",
3
+ "version": "0.4.134",
4
+ "description": "CLI for Component Studio",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "author": "Arcblock <blocklet@arcblock.io> https://github.com/blocklet",
9
+ "homepage": "https://github.com/blocklet/pages-kit#readme",
10
+ "license": "ISC",
11
+ "bin": {
12
+ "component-studio": "dist/cli.js"
13
+ },
14
+ "type": "module",
15
+ "files": [
16
+ "dist",
17
+ "workspace",
18
+ "LICENSE",
19
+ "README.md",
20
+ "CHANGELOG.md",
21
+ "templates"
22
+ ],
23
+ "exports": {
24
+ "./*": "./dist/*"
25
+ },
26
+ "typesVersions": {
27
+ "*": {
28
+ "*": [
29
+ "./dist/*"
30
+ ]
31
+ }
32
+ },
33
+ "dependencies": {
34
+ "chalk": "^5.4.1",
35
+ "commander": "^13.1.0",
36
+ "fs-extra": "^11.2.0",
37
+ "glob": "^11.0.1",
38
+ "gradient-string": "^3.0.0",
39
+ "inquirer": "^12.5.2",
40
+ "ora": "^8.2.0",
41
+ "pretty-error": "^4.0.0",
42
+ "tar": "^7.4.3",
43
+ "@blocklet/pages-kit-block-studio": "^0.4.134"
44
+ },
45
+ "devDependencies": {
46
+ "@types/glob": "^8.1.0",
47
+ "@types/node": "^22.14.1",
48
+ "npm-run-all": "^4.1.5",
49
+ "rimraf": "^6.0.1",
50
+ "typescript": "^5.8.3"
51
+ },
52
+ "scripts": {
53
+ "lint": "tsc --noEmit",
54
+ "build": "pnpm run clean && tsc --build tsconfig.build.json",
55
+ "clean": "rimraf dist tsconfig.build.tsbuildinfo",
56
+ "test": "node --test"
57
+ }
58
+ }