@blocklet/component-studio-cli 0.4.145 → 0.4.147

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.
@@ -2,7 +2,8 @@ import chalk from 'chalk';
2
2
  import { Command } from 'commander';
3
3
  import inquirer from 'inquirer';
4
4
  import { camelCase, upperFirst } from 'lodash-es';
5
- import { existsSync, mkdirSync, readdirSync } from 'node:fs';
5
+ import { nanoid } from 'nanoid';
6
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
6
7
  import { join, relative } from 'node:path';
7
8
  import ora from 'ora';
8
9
  import { copyTemplateFilesExcept, loadTemplates, selectTemplate, validateTemplate, getTemplatePath, getProjectPath, replaceInDirectory, findClosestMetadataDir, checkTargetDirectoryExists, } from '../utils/helper.js';
@@ -124,6 +125,11 @@ export function createAddCommand() {
124
125
  '{{component_name}}': componentName.toLowerCase(),
125
126
  '{{ComponentName}}': componentName.charAt(0).toUpperCase() + componentName.slice(1),
126
127
  });
128
+ // 替换 @metadata.json 中的 id,确保实例不重复
129
+ const metadataPath = join(targetDir, '@metadata.json');
130
+ const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
131
+ metadata.id = nanoid();
132
+ writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
127
133
  spinner.succeed(chalk.green(`Component "${componentName}" created successfully!`));
128
134
  console.log(`\n🚀 Component added to ${chalk.cyan(relative(projectPath, targetDir))}`);
129
135
  console.log(`\n💡 Next steps: import and use your component in your application`);
@@ -5,7 +5,7 @@ import fs from 'fs-extra';
5
5
  import { existsSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  import ora from 'ora';
8
- import { getWorkspaceTemplatePath, installDependencies, getWorkspacePath, getProjectPath } from '../utils/helper.js';
8
+ import { getWorkspaceTemplatePath, installDependencies, getWorkspacePath, getProjectPath, replaceInDirectoryFileNameWithExample, } from '../utils/helper.js';
9
9
  async function createDevServer(projectPath, _options) {
10
10
  // 1. 创建工作区
11
11
  const workspacePath = getWorkspacePath();
@@ -15,8 +15,8 @@ async function createDevServer(projectPath, _options) {
15
15
  if (!existsSync(workspacePath)) {
16
16
  const workspaceTemplatePath = getWorkspaceTemplatePath();
17
17
  await fs.copy(workspaceTemplatePath, workspacePath);
18
- // 修改 workspace 目录中的 .npmrc_example 文件 为 .npmrc,因为 npm 包默认会把 .npmrc 忽略
19
- await fs.rename(join(workspacePath, '.npmrc_example'), join(workspacePath, '.npmrc'));
18
+ // 修改 workspace 目录中,_example 后缀的文件名,删除 _example 后缀
19
+ await replaceInDirectoryFileNameWithExample(workspacePath);
20
20
  workspaceSpinner.succeed(chalk.green(`Workspace created successfully! ${workspacePathText}`));
21
21
  }
22
22
  else {
@@ -24,15 +24,36 @@ async function createDevServer(projectPath, _options) {
24
24
  }
25
25
  // 3. 确保目录中有projects目录
26
26
  const projectsDir = join(workspacePath, 'projects');
27
+ const projectLinkDir = join(projectsDir, 'user-project');
28
+ const projectLinkSpinner = ora({ text: 'Setting up project to workspace...', color: 'blue' }).start();
29
+ // 检查是否已经正确链接
30
+ let needsNewLink = true;
27
31
  if (existsSync(projectsDir)) {
28
- await fs.remove(projectsDir);
32
+ try {
33
+ const stats = await fs.lstat(projectLinkDir);
34
+ if (stats.isSymbolicLink()) {
35
+ const currentTarget = await fs.readlink(projectLinkDir);
36
+ if (currentTarget === projectPath) {
37
+ needsNewLink = false;
38
+ projectLinkSpinner.succeed(chalk.green('Project already linked to workspace!'));
39
+ }
40
+ else {
41
+ await fs.remove(projectsDir);
42
+ }
43
+ }
44
+ else {
45
+ await fs.remove(projectsDir);
46
+ }
47
+ }
48
+ catch (error) {
49
+ await fs.remove(projectsDir);
50
+ }
51
+ }
52
+ if (needsNewLink) {
53
+ await fs.ensureDir(projectsDir);
54
+ await fs.ensureSymlink(projectPath, projectLinkDir);
55
+ projectLinkSpinner.succeed(chalk.green('Project linked to workspace successfully!'));
29
56
  }
30
- await fs.ensureDir(projectsDir);
31
- // 4. 将用户项目目录链接到目录的projects下
32
- const projectLinkSpinner = ora({ text: 'Setting up project to workspace...', color: 'blue' }).start();
33
- const projectLinkDir = join(projectsDir, 'user-project');
34
- await fs.ensureSymlink(projectPath, projectLinkDir);
35
- projectLinkSpinner.succeed(chalk.green('Project linked to workspace successfully!'));
36
57
  let shouldInstallWorkspaceDeps = !(await fs.exists(join(workspacePath, 'node_modules')));
37
58
  let shouldInstallProjectDeps = !(await fs.exists(join(projectPath, 'node_modules')));
38
59
  // 5. 安装依赖
@@ -53,14 +74,24 @@ async function createDevServer(projectPath, _options) {
53
74
  const devProcess = spawn('pnpm', ['run', 'dev'], {
54
75
  cwd: workspacePath,
55
76
  stdio: 'inherit',
77
+ detached: false,
78
+ });
79
+ // 确保子进程能正确接收和处理信号
80
+ process.on('SIGINT', () => {
81
+ devProcess.kill('SIGINT');
82
+ });
83
+ process.on('SIGTERM', () => {
84
+ devProcess.kill('SIGTERM');
56
85
  });
57
86
  devServerSpinner.succeed(chalk.green('Development server created successfully!'));
58
87
  // 返回清理函数
59
88
  return {
89
+ shouldInstallWorkspaceDeps,
90
+ shouldInstallProjectDeps,
60
91
  close: async () => {
61
- if (devProcess && !devProcess.killed) {
92
+ if (devProcess) {
62
93
  const closeSpinner = ora({ text: 'Shutting down development server...', color: 'yellow' }).start();
63
- devProcess.kill();
94
+ devProcess.kill('SIGTERM');
64
95
  closeSpinner.succeed(chalk.green('Development server stopped'));
65
96
  }
66
97
  },
@@ -69,20 +100,23 @@ async function createDevServer(projectPath, _options) {
69
100
  export function createDevCommand() {
70
101
  return new Command('dev').description('Start development server').action(async (options) => {
71
102
  const projectPath = getProjectPath();
103
+ const server = await createDevServer(projectPath, options);
104
+ // 处理终止信号
105
+ const handleTermination = async (signal) => {
106
+ console.log(chalk.yellow(`\nReceived ${signal} signal. Shutting down...`));
107
+ await server.close();
108
+ process.exit(0);
109
+ };
72
110
  try {
73
- const server = await createDevServer(projectPath, options);
74
- // 处理终止信号
75
- const handleTermination = async () => {
76
- await server.close();
77
- process.exit(0);
78
- };
79
- process.on('SIGINT', handleTermination);
80
- process.on('SIGTERM', handleTermination);
81
- process.on('SIGHUP', handleTermination);
111
+ // 使用 once 而不是 on,确保信号处理程序只被注册一次
112
+ process.once('SIGINT', () => handleTermination('SIGINT'));
113
+ process.once('SIGTERM', () => handleTermination('SIGTERM'));
114
+ process.once('SIGHUP', () => handleTermination('SIGHUP'));
82
115
  }
83
116
  catch (error) {
84
117
  console.error(chalk.red('Failed to start development server:'));
85
118
  console.error(error);
119
+ await server.close();
86
120
  process.exit(1);
87
121
  }
88
122
  });
@@ -5,7 +5,7 @@ import inquirer from 'inquirer';
5
5
  import { existsSync, mkdirSync, readdirSync } from 'node:fs';
6
6
  import { isAbsolute, join, relative, resolve } from 'node:path';
7
7
  import ora from 'ora';
8
- import { copyTemplateFilesExcept, mergePackageJson, loadTemplates, selectTemplate, validateTemplate, getTemplatePath, } from '../utils/helper.js';
8
+ import { copyTemplateFilesExcept, mergePackageJson, loadTemplates, selectTemplate, validateTemplate, getTemplatePath, replaceInDirectoryFileNameWithExample, } from '../utils/helper.js';
9
9
  export function createInitCommand() {
10
10
  return new Command('init')
11
11
  .description('Initialize a new component project')
@@ -92,6 +92,8 @@ export function createInitCommand() {
92
92
  try {
93
93
  // 使用新的函数复制文件,排除@template.json
94
94
  await copyTemplateFilesExcept(templatePath, path);
95
+ // 替换目录中的文件名带 _example 后缀的文件名,删除 _example 后缀
96
+ await replaceInDirectoryFileNameWithExample(path);
95
97
  spinner.succeed('Copy template files successfully!');
96
98
  }
97
99
  catch (error) {
@@ -104,10 +106,9 @@ export function createInitCommand() {
104
106
  await mergePackageJson(path, templatePath);
105
107
  spinner.succeed('Merge package.json successfully!');
106
108
  console.log(chalk.green('\n🎉 Create project successfully!'));
107
- console.log(`\nNext steps:`);
109
+ console.log(`\n💡 Next steps:`);
108
110
  console.log(` 1. ${chalk.cyan(`cd ${relative(process.cwd(), path)}`)}`);
109
111
  console.log(` 2. ${chalk.cyan('pnpm run dev')}`);
110
- console.log(`\nHappy coding! 🚀`);
111
112
  })
112
113
  .showHelpAfterError(true)
113
114
  .showSuggestionAfterError(true);
@@ -66,6 +66,11 @@ export declare function replaceInFile(filePath: string, replacements: Record<str
66
66
  * @param replacements - 替换对象,键为占位符,值为替换内容
67
67
  */
68
68
  export declare function replaceInDirectory(dirPath: string, replacements: Record<string, string>): Promise<void>;
69
+ /**
70
+ * 替换目录中的文件名带 _example 后缀的文件名,删除 _example 后缀
71
+ * @param dirPath - 目录路径
72
+ */
73
+ export declare function replaceInDirectoryFileNameWithExample(dirPath: string): Promise<void>;
69
74
  /**
70
75
  * 查找最接近指定目录的包含@metadata.json的目录
71
76
  * @param startDir - 起始目录
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { execSync, spawn } from 'child_process';
3
+ import { rename } from 'fs-extra';
3
4
  import { glob } from 'glob';
4
5
  import inquirer from 'inquirer';
5
6
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
@@ -103,18 +104,33 @@ export async function installDependencies(dirs, operation = 'install') {
103
104
  try {
104
105
  const installPromises = Object.entries(dirs).map(([dirType, dirPath]) => {
105
106
  return new Promise((resolve, reject) => {
107
+ let errorOutput = '';
106
108
  const process = spawn('pnpm', ['update:deps'], {
107
109
  cwd: dirPath,
108
- // stdio: 'inherit',
110
+ stdio: ['inherit', 'pipe', 'pipe'], // 将 stdout 和 stderr 设为 pipe
111
+ });
112
+ // 收集错误输出
113
+ process.stderr?.on('data', (data) => {
114
+ errorOutput += data.toString();
115
+ });
116
+ // 收集标准输出(可能包含错误信息)
117
+ process.stdout?.on('data', (data) => {
118
+ const output = data.toString();
119
+ if (output.toLowerCase().includes('error')) {
120
+ errorOutput += output;
121
+ }
109
122
  });
110
123
  process.on('close', (code) => {
111
124
  if (code === 0) {
112
125
  resolve();
113
126
  }
114
127
  else {
115
- reject(new Error(`${dirType} dependencies install failed with code ${code}`));
128
+ reject(new Error(`${dirType} dependencies install failed with code ${code}.\nError details:\n${errorOutput?.trimStart()}`));
116
129
  }
117
130
  });
131
+ process.on('error', (error) => {
132
+ reject(new Error(`${dirType} process error: ${error.message}\n${errorOutput}`));
133
+ });
118
134
  });
119
135
  });
120
136
  // 制作一个假进度条,每秒增加 1%
@@ -141,7 +157,7 @@ export async function installDependencies(dirs, operation = 'install') {
141
157
  }
142
158
  catch (error) {
143
159
  depsSpinner.fail(chalk.red('Failed to install dependencies'));
144
- console.error(error);
160
+ console.error(error.message); // 只输出错误消息,包含了我们收集的详细错误信息
145
161
  return false;
146
162
  }
147
163
  }
@@ -353,6 +369,27 @@ export async function replaceInDirectory(dirPath, replacements) {
353
369
  throw error;
354
370
  }
355
371
  }
372
+ /**
373
+ * 替换目录中的文件名带 _example 后缀的文件名,删除 _example 后缀
374
+ * @param dirPath - 目录路径
375
+ */
376
+ export async function replaceInDirectoryFileNameWithExample(dirPath) {
377
+ try {
378
+ // 读取目录内容
379
+ const entries = await readdir(dirPath, { withFileTypes: true });
380
+ // 处理每个条目
381
+ for (const entry of entries) {
382
+ if (entry.isFile() && entry.name.endsWith('_example')) {
383
+ const newName = entry.name.replace('_example', '');
384
+ await rename(join(dirPath, entry.name), join(dirPath, newName));
385
+ }
386
+ }
387
+ }
388
+ catch (error) {
389
+ console.error(chalk.red(`Error replacing directory file names in ${dirPath}:`), error);
390
+ throw error;
391
+ }
392
+ }
356
393
  /**
357
394
  * 查找最接近指定目录的包含@metadata.json的目录
358
395
  * @param startDir - 起始目录
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/component-studio-cli",
3
- "version": "0.4.145",
3
+ "version": "0.4.147",
4
4
  "description": "CLI for Component Studio",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -37,6 +37,7 @@
37
37
  "gradient-string": "^3.0.0",
38
38
  "inquirer": "^12.5.2",
39
39
  "lodash-es": "^4.17.21",
40
+ "nanoid": "^3.3.7",
40
41
  "node-fetch": "^2.7.0",
41
42
  "ora": "^8.2.0",
42
43
  "pretty-error": "^4.0.0",
@@ -0,0 +1,36 @@
1
+ # Dependencies
2
+ node_modules
3
+ .pnp
4
+ .pnp.js
5
+
6
+ # Production
7
+ dist
8
+ build
9
+ lib
10
+
11
+ # Misc
12
+ .DS_Store
13
+ .env.local
14
+ .env.development.local
15
+ .env.test.local
16
+ .env.production.local
17
+
18
+ # Debug
19
+ npm-debug.log*
20
+ yarn-debug.log*
21
+ yarn-error.log*
22
+
23
+ # Editor
24
+ .idea
25
+ .vscode/*
26
+ !.vscode/extensions.json
27
+ !.vscode/settings.json
28
+ *.suo
29
+ *.ntvs*
30
+ *.njsproj
31
+ *.sln
32
+ *.sw?
33
+
34
+ # Logs
35
+ logs
36
+ *.log
@@ -0,0 +1,36 @@
1
+ # Dependencies
2
+ node_modules
3
+ .pnp
4
+ .pnp.js
5
+
6
+ # Production
7
+ dist
8
+ build
9
+ lib
10
+
11
+ # Misc
12
+ .DS_Store
13
+ .env.local
14
+ .env.development.local
15
+ .env.test.local
16
+ .env.production.local
17
+
18
+ # Debug
19
+ npm-debug.log*
20
+ yarn-debug.log*
21
+ yarn-error.log*
22
+
23
+ # Editor
24
+ .idea
25
+ .vscode/*
26
+ !.vscode/extensions.json
27
+ !.vscode/settings.json
28
+ *.suo
29
+ *.ntvs*
30
+ *.njsproj
31
+ *.sln
32
+ *.sw?
33
+
34
+ # Logs
35
+ logs
36
+ *.log
@@ -0,0 +1,36 @@
1
+ # Dependencies
2
+ node_modules
3
+ .pnp
4
+ .pnp.js
5
+
6
+ # Production
7
+ dist
8
+ build
9
+ lib
10
+
11
+ # Misc
12
+ .DS_Store
13
+ .env.local
14
+ .env.development.local
15
+ .env.test.local
16
+ .env.production.local
17
+
18
+ # Debug
19
+ npm-debug.log*
20
+ yarn-debug.log*
21
+ yarn-error.log*
22
+
23
+ # Editor
24
+ .idea
25
+ .vscode/*
26
+ !.vscode/extensions.json
27
+ !.vscode/settings.json
28
+ *.suo
29
+ *.ntvs*
30
+ *.njsproj
31
+ *.sln
32
+ *.sw?
33
+
34
+ # Logs
35
+ logs
36
+ *.log
@@ -0,0 +1,29 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ .pnp
5
+ .pnp.js
6
+ .DS_Store
7
+ node_modules
8
+
9
+ # testing
10
+ coverage
11
+
12
+ # production
13
+ build
14
+ dist
15
+ dist-ssr
16
+ .blocklet
17
+ .next
18
+
19
+ # local env files
20
+ *.local
21
+
22
+ # Log files
23
+ logs
24
+ *.log
25
+ npm-debug.log*
26
+ yarn-debug.log*
27
+ yarn-error.log*
28
+ pnpm-debug.log*
29
+ lerna-debug.log*