@blocklet/component-studio-cli 0.6.14 → 0.6.16

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 (28) hide show
  1. package/dist/commands/add.js +77 -1
  2. package/dist/commands/build.d.ts +2 -0
  3. package/dist/commands/build.js +68 -0
  4. package/dist/commands/bump-version.d.ts +2 -0
  5. package/dist/commands/bump-version.js +100 -0
  6. package/dist/commands/component-studio.js +4 -0
  7. package/dist/commands/dev.js +12 -85
  8. package/dist/utils/helper.d.ts +15 -0
  9. package/dist/utils/helper.js +114 -9
  10. package/package.json +5 -2
  11. package/templates/add/tools/teamwork-tools/.github/workflows/ci.yml +35 -0
  12. package/templates/add/tools/teamwork-tools/.github/workflows/code-reviewer.yml +40 -0
  13. package/templates/add/tools/teamwork-tools/.github/workflows/pr-title.yml +21 -0
  14. package/templates/add/tools/teamwork-tools/.github/workflows/upload-to-blocklet-store.yml +71 -0
  15. package/templates/add/tools/teamwork-tools/.github/workflows/version-check.yml +20 -0
  16. package/templates/add/tools/teamwork-tools/@template.json +4 -0
  17. package/templates/init/0-basic/.gitignore_example +4 -1
  18. package/templates/init/0-basic/package.json +4 -1
  19. package/templates/init/1-professional/.gitignore_example +4 -1
  20. package/templates/init/1-professional/biome.json +1 -1
  21. package/templates/init/1-professional/package.json +7 -5
  22. package/templates/init/2-blank/.gitignore_example +4 -1
  23. package/templates/init/2-blank/package.json +4 -1
  24. package/templates/workspace/.gitignore_example +4 -1
  25. package/templates/workspace/biome.json +1 -1
  26. package/templates/workspace/package.json +3 -3
  27. package/templates/init/1-professional/scripts/bump-version.mjs +0 -35
  28. package/templates/init/1-professional/version +0 -1
@@ -1,11 +1,15 @@
1
+ import AdmZip from 'adm-zip';
1
2
  import chalk from 'chalk';
2
3
  import { Command } from 'commander';
3
4
  import inquirer from 'inquirer';
4
5
  import { camelCase, upperFirst } from 'lodash-es';
5
6
  import { nanoid } from 'nanoid';
6
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
7
8
  import { join, relative } from 'node:path';
8
9
  import ora from 'ora';
10
+ import os from 'os';
11
+ import * as tar from 'tar';
12
+ import { BLOCKLET_INFO_DIR } from '../utils/helper.js';
9
13
  import { copyTemplateFilesExcept, loadTemplates, selectTemplate, validateTemplate, getTemplatePath, getProjectPath, replaceInDirectory, findClosestMetadataDir, checkTargetDirectoryExists, } from '../utils/helper.js';
10
14
  // 模板目录常量
11
15
  const TOOLS_DIR = 'tools';
@@ -60,6 +64,78 @@ export function createAddCommand() {
60
64
  if (!canContinue) {
61
65
  process.exit(0);
62
66
  }
67
+ // Notice: 如果是 teamwork-tools 模板,则需要获取 Resource Blocklet 路径
68
+ if (selectedTemplate.name === 'Teamwork Tools') {
69
+ const { resourceBlockletPath } = await inquirer.prompt([
70
+ {
71
+ type: 'input',
72
+ name: 'resourceBlockletPath',
73
+ message: 'Please provide the latest Resource Blocklet path (e.g. /Users/your-username/xxxxx-0.0.1.{zip,tgz}): \n ',
74
+ validate: (input) => {
75
+ if (!existsSync(input))
76
+ return 'Resource Blocklet path does not exist.';
77
+ // 支持 .zip 和 .tgz 文件
78
+ if (!input.endsWith('.zip') && !input.endsWith('.tgz')) {
79
+ return 'Resource Blocklet path must be a .zip or .tgz file.';
80
+ }
81
+ return true;
82
+ },
83
+ },
84
+ ]);
85
+ // 如果存在,解压到 tmp 目录
86
+ const tmpDir = join(os.tmpdir(), Math.random().toString(36).substring(2, 15));
87
+ if (existsSync(tmpDir)) {
88
+ rmSync(tmpDir, { recursive: true });
89
+ }
90
+ mkdirSync(tmpDir, { recursive: true });
91
+ let extractDir;
92
+ if (resourceBlockletPath.endsWith('.zip')) {
93
+ // Extract zip file
94
+ const zip = new AdmZip(resourceBlockletPath);
95
+ zip.extractAllTo(tmpDir, true);
96
+ // Read blocklet.json to get tarball name
97
+ const blockletJsonPath = join(tmpDir, 'blocklet.json');
98
+ if (!existsSync(blockletJsonPath)) {
99
+ console.log(chalk.red('blocklet.json not found in the extracted zip file.'));
100
+ process.exit(1);
101
+ }
102
+ const blockletJson = JSON.parse(readFileSync(blockletJsonPath, 'utf-8'));
103
+ const tarballName = blockletJson.dist?.tarball;
104
+ if (!tarballName) {
105
+ console.log(chalk.red('No tarball found in blocklet.json'));
106
+ process.exit(1);
107
+ }
108
+ // Extract .tgz file
109
+ const tarballPath = join(tmpDir, tarballName);
110
+ if (!existsSync(tarballPath)) {
111
+ console.log(chalk.red(`Tarball file not found: ${tarballName}`));
112
+ process.exit(1);
113
+ }
114
+ extractDir = join(tmpDir, 'extracted');
115
+ mkdirSync(extractDir, { recursive: true });
116
+ await tar.x({ file: tarballPath, cwd: extractDir });
117
+ }
118
+ else if (resourceBlockletPath.endsWith('.tgz')) {
119
+ // Extract .tgz file directly
120
+ extractDir = tmpDir;
121
+ await tar.x({ file: resourceBlockletPath, cwd: extractDir });
122
+ }
123
+ else {
124
+ console.log(chalk.red('Unsupported file format.'));
125
+ process.exit(1);
126
+ }
127
+ // Validate extracted structure - check for package directory
128
+ const packageDir = join(extractDir, 'package');
129
+ if (!existsSync(packageDir)) {
130
+ console.log(chalk.red('Invalid Resource Blocklet: missing "package" directory.'));
131
+ console.log(chalk.red('This does not appear to be a standard Resource Blocklet package.'));
132
+ process.exit(1);
133
+ }
134
+ // console.log(chalk.green(`Resource Blocklet extracted to: ${chalk.cyan(packageDir)}`));
135
+ // copy package to project
136
+ await copyTemplateFilesExcept(packageDir, join(projectPath, BLOCKLET_INFO_DIR));
137
+ spinner.succeed(chalk.green(`Resource Blocklet extracted successfully!`));
138
+ }
63
139
  // 对于工具类模板,直接复制到项目根目录
64
140
  spinner.start(`Adding tool "${selectedTemplate.name}" to project...`);
65
141
  try {
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function createBuildCommand(): Command;
@@ -0,0 +1,68 @@
1
+ import { spawn } from 'child_process';
2
+ import { Command } from 'commander';
3
+ import { readFile } from 'fs/promises';
4
+ import { join } from 'path';
5
+ import { parse } from 'yaml';
6
+ import { BLOCKLET_INFO_DIR, getProjectPath, setupWorkspaceEnvironment } from '../utils/helper.js';
7
+ export function createBuildCommand() {
8
+ return new Command('build')
9
+ .description('Build project')
10
+ .option('-c, --components <components>', 'Build components, please use comma to separate')
11
+ .option('-b, --bundle-resource-blocklet', 'Bundle resource blocklet, default is false')
12
+ .action(async (options) => {
13
+ const projectPath = getProjectPath();
14
+ const { workspacePath } = await setupWorkspaceEnvironment(projectPath);
15
+ const bundleDir = join(projectPath, BLOCKLET_INFO_DIR);
16
+ const extraEnvs = {};
17
+ if (options.components) {
18
+ extraEnvs.BLOCK_FILTER = options.components;
19
+ }
20
+ if (options.bundleResourceBlocklet) {
21
+ // 读取 projectPath 的 yaml 文件,提取里面的 resources 信息
22
+ const projectYaml = await readFile(join(bundleDir, 'blocklet.yml'), 'utf-8');
23
+ const projectYamlData = parse(projectYaml);
24
+ const { bundles } = projectYamlData.resource;
25
+ const bundleInfo = bundles[0];
26
+ if (bundleInfo) {
27
+ // 复制 resource-blocklet 到指定目录
28
+ extraEnvs.EXPORT_DIR = join(bundleDir, 'resources', bundleInfo.did, bundleInfo.type);
29
+ }
30
+ }
31
+ const buildProcess = spawn('pnpm', ['run', 'build-lib'], {
32
+ stdio: 'inherit',
33
+ cwd: workspacePath,
34
+ shell: true,
35
+ env: {
36
+ ...process.env,
37
+ FORCE_COLOR: '1',
38
+ NODE_OPTIONS: '--max_old_space_size=16384',
39
+ NODE_ENV: 'production',
40
+ ...extraEnvs,
41
+ },
42
+ });
43
+ await new Promise((resolve, reject) => {
44
+ buildProcess.on('close', (code) => {
45
+ if (code === 0) {
46
+ if (options.bundleResourceBlocklet) {
47
+ spawn('npx', ['-y', '@blocklet/cli', 'bundle', '--create-release'], {
48
+ stdio: 'inherit',
49
+ cwd: bundleDir,
50
+ shell: true,
51
+ env: {
52
+ ...process.env,
53
+ FORCE_COLOR: '1',
54
+ },
55
+ });
56
+ }
57
+ resolve(null);
58
+ }
59
+ else
60
+ reject(new Error(`Build process exited with code ${code}`));
61
+ });
62
+ buildProcess.on('error', (error) => {
63
+ console.error('Build process error:', error);
64
+ reject(error);
65
+ });
66
+ });
67
+ });
68
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function createBumpVersionCommand(): Command;
@@ -0,0 +1,100 @@
1
+ import chalk from 'chalk';
2
+ import { execSync } from 'child_process';
3
+ import { Command } from 'commander';
4
+ import { readFile, writeFile, ensureFile } from 'fs-extra';
5
+ import inquirer from 'inquirer';
6
+ import ora from 'ora';
7
+ import { join } from 'path';
8
+ import { getProjectPath, BLOCKLET_INFO_DIR } from '../utils/helper.js';
9
+ export function createBumpVersionCommand() {
10
+ return new Command('bump-version')
11
+ .description('Bump version for project')
12
+ .option('-c, --changelog <changelog>', 'Custom changelog content')
13
+ .action(async (options) => {
14
+ const projectPath = getProjectPath();
15
+ const spinner = ora('Bumping version...');
16
+ try {
17
+ // Step 1: Bump version using bumpp
18
+ execSync(`npx -y bumpp --no-tag --no-commit --no-push package.json`, {
19
+ stdio: 'inherit',
20
+ cwd: projectPath,
21
+ });
22
+ // Step 2: Read new version and write to version file
23
+ const packageJsonPath = join(projectPath, 'package.json');
24
+ const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
25
+ const newVersion = packageJson.version;
26
+ await writeFile(join(projectPath, 'version'), newVersion);
27
+ spinner.succeed(chalk.green(`Version bumped to ${newVersion}`));
28
+ // Step 3: get blockletInfo path
29
+ const blockletInfoPath = join(projectPath, BLOCKLET_INFO_DIR);
30
+ // Step 4: Prepare changelog
31
+ let newChangelog = '';
32
+ const now = new Date();
33
+ const currentDate = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
34
+ const title = `## ${newVersion} (${currentDate})`;
35
+ if (options.changelog) {
36
+ newChangelog = options.changelog;
37
+ console.log(chalk.blue(`Using custom changelog: ${newChangelog}`));
38
+ }
39
+ else {
40
+ // Ask user for custom changelog input
41
+ const { userChangelog } = await inquirer.prompt([
42
+ {
43
+ type: 'input',
44
+ name: 'userChangelog',
45
+ message: 'Enter changelog content (leave empty to use git log):',
46
+ default: '',
47
+ },
48
+ ]);
49
+ if (userChangelog?.trim()) {
50
+ newChangelog = userChangelog.trim();
51
+ console.log(chalk.blue(`Using user input changelog: ${newChangelog}`));
52
+ }
53
+ else {
54
+ // Fallback to git log
55
+ try {
56
+ const gitLogResult = execSync('git log --pretty=format:"- %s" "main"...HEAD', {
57
+ encoding: 'utf-8',
58
+ cwd: projectPath,
59
+ });
60
+ newChangelog = gitLogResult.trim();
61
+ console.log(chalk.blue('Using git log for changelog'));
62
+ }
63
+ catch (error) {
64
+ console.error(chalk.red('Could not get git log, using default changelog.'));
65
+ newChangelog = '- Manual changelog entry needed';
66
+ }
67
+ }
68
+ }
69
+ // Step 5: Update blockletInfoPath version
70
+ try {
71
+ // Update blocklet version
72
+ execSync(`npx -y @blocklet/cli version ${newVersion}`, {
73
+ stdio: 'inherit',
74
+ cwd: blockletInfoPath,
75
+ });
76
+ // Update changelog
77
+ const changelogPath = join(blockletInfoPath, 'CHANGELOG.md');
78
+ await ensureFile(changelogPath);
79
+ let oldChangelog = '';
80
+ try {
81
+ oldChangelog = await readFile(changelogPath, 'utf-8');
82
+ }
83
+ catch {
84
+ // File doesn't exist, that's OK
85
+ }
86
+ const changelog = [title, newChangelog, oldChangelog].filter((item) => !!item).join('\n\n');
87
+ await writeFile(changelogPath, changelog);
88
+ }
89
+ catch (error) {
90
+ console.error(chalk.red(`Failed to update blocklet at ${blockletInfoPath}:`, error));
91
+ }
92
+ console.log(chalk.green('✅ Bump version successfully!'));
93
+ }
94
+ catch (error) {
95
+ console.log(chalk.red('Bump version failed'));
96
+ console.error(error);
97
+ process.exit(1);
98
+ }
99
+ });
100
+ }
@@ -4,6 +4,8 @@ import inquirer from 'inquirer';
4
4
  import pkg from '../../package.json' with { type: 'json' };
5
5
  import { isCommandAvailable, installGlobalPackage } from '../utils/helper.js';
6
6
  import { createAddCommand } from './add.js';
7
+ import { createBuildCommand } from './build.js';
8
+ import { createBumpVersionCommand } from './bump-version.js';
7
9
  import { createDevCommand } from './dev.js';
8
10
  import { createInitCommand } from './init.js';
9
11
  import { createMigrateCommand } from './migrate.js';
@@ -58,6 +60,8 @@ export function createComponentStudioCommand() {
58
60
  .addCommand(createUpdateCommand()) // 更新依赖
59
61
  .addCommand(createAddCommand()) // 添加组件
60
62
  .addCommand(createMigrateCommand()) // 迁移组件
63
+ .addCommand(createBuildCommand()) // 构建组件
64
+ .addCommand(createBumpVersionCommand()) // 版本升级
61
65
  .showHelpAfterError(true)
62
66
  .showSuggestionAfterError(true)
63
67
  .action(() => {
@@ -1,92 +1,16 @@
1
1
  import chalk from 'chalk';
2
2
  import { spawn } from 'child_process';
3
3
  import { Command } from 'commander';
4
- import fs from 'fs-extra';
5
- import { existsSync } from 'node:fs';
6
- import { join, dirname } from 'node:path';
7
4
  import ora from 'ora';
8
- import { getWorkspaceTemplatePath, installDependencies, getWorkspacePath, getProjectPath, replaceInDirectoryFileNameWithExample, checkShouldInstallDependencies, } from '../utils/helper.js';
5
+ import { getProjectPath, setupWorkspaceEnvironment } from '../utils/helper.js';
9
6
  async function createDevServer(projectPath, options) {
10
- // 1. 创建工作区
11
- const workspacePath = getWorkspacePath(options.version);
12
- const workspaceSpinner = ora({ text: 'Creating workspace...', color: 'blue' }).start();
13
- const workspacePathText = chalk.blue(`The workspace directory: ${workspacePath}`);
14
- if (options.version) {
15
- console.log(chalk.cyan(`Using version: ${options.version}`));
16
- }
17
- // 2. 准备基础环境
18
- if (!existsSync(workspacePath)) {
19
- const workspaceTemplatePath = getWorkspaceTemplatePath();
20
- const workspaceParentDir = dirname(workspacePath);
21
- // Selectively clean workspace parent directory, preserving @ prefixed directories
22
- if (existsSync(workspaceParentDir)) {
23
- const items = await fs.readdir(workspaceParentDir);
24
- await Promise.all(items
25
- .filter((item) => !item.startsWith('@'))
26
- .map((item) => fs.remove(join(workspaceParentDir, item))));
27
- }
28
- else {
29
- // 创建 workspace parent 目录
30
- await fs.ensureDir(workspaceParentDir);
31
- }
32
- // 复制 workspace 模板
33
- await fs.copy(workspaceTemplatePath, workspacePath);
34
- // 修改 workspace 目录中,_example 后缀的文件名,删除 _example 后缀
35
- await replaceInDirectoryFileNameWithExample(workspacePath);
36
- workspaceSpinner.succeed(chalk.green(`Workspace created successfully! ${workspacePathText}`));
37
- }
38
- else {
39
- workspaceSpinner.succeed(chalk.green(`Using existing workspace! ${workspacePathText}`));
40
- }
41
- // 3. 确保目录中有projects目录
42
- const projectsDir = join(workspacePath, 'projects');
43
- const projectLinkDir = join(projectsDir, 'user-project');
44
- const projectLinkSpinner = ora({ text: 'Setting up project to workspace...', color: 'blue' }).start();
45
- // 检查是否已经正确链接
46
- let needsNewLink = true;
47
- if (existsSync(projectsDir)) {
48
- try {
49
- const stats = await fs.lstat(projectLinkDir);
50
- if (stats.isSymbolicLink()) {
51
- const currentTarget = await fs.readlink(projectLinkDir);
52
- if (currentTarget === projectPath) {
53
- needsNewLink = false;
54
- projectLinkSpinner.succeed(chalk.green('Project already linked to workspace!'));
55
- }
56
- else {
57
- await fs.remove(projectsDir);
58
- }
59
- }
60
- else {
61
- await fs.remove(projectsDir);
62
- }
63
- }
64
- catch (error) {
65
- await fs.remove(projectsDir);
66
- }
67
- }
68
- if (needsNewLink) {
69
- await fs.ensureDir(projectsDir);
70
- await fs.ensureSymlink(projectPath, projectLinkDir);
71
- projectLinkSpinner.succeed(chalk.green('Project linked to workspace successfully!'));
72
- }
73
- let shouldInstallWorkspaceDeps = await checkShouldInstallDependencies(workspacePath);
74
- let shouldInstallProjectDeps = await checkShouldInstallDependencies(projectPath);
75
- // 5. 安装依赖
76
- if (shouldInstallWorkspaceDeps || shouldInstallProjectDeps) {
77
- const installDeps = {};
78
- if (shouldInstallProjectDeps) {
79
- installDeps.Project = projectPath;
80
- }
81
- if (shouldInstallWorkspaceDeps) {
82
- installDeps.Workspace = workspacePath;
83
- }
84
- const result = await installDependencies(installDeps);
85
- if (!result) {
86
- process.exit(1);
87
- }
88
- }
89
- const devServerSpinner = ora({ text: 'Creating development server...', color: 'blue' }).start();
7
+ // Setup workspace environment
8
+ const { workspacePath, shouldInstallWorkspaceDeps, shouldInstallProjectDeps } = await setupWorkspaceEnvironment(projectPath, options);
9
+ // Start development server
10
+ const devServerSpinner = ora({
11
+ text: 'Creating development server...',
12
+ color: 'blue',
13
+ }).start();
90
14
  const devProcess = spawn('pnpm', ['run', 'dev'], {
91
15
  cwd: workspacePath,
92
16
  stdio: 'inherit',
@@ -110,7 +34,10 @@ async function createDevServer(projectPath, options) {
110
34
  shouldInstallProjectDeps,
111
35
  close: async () => {
112
36
  if (devProcess) {
113
- const closeSpinner = ora({ text: 'Shutting down development server...', color: 'yellow' }).start();
37
+ const closeSpinner = ora({
38
+ text: 'Shutting down development server...',
39
+ color: 'yellow',
40
+ }).start();
114
41
  devProcess.kill('SIGTERM');
115
42
  closeSpinner.succeed(chalk.green('Development server stopped'));
116
43
  }
@@ -1,3 +1,4 @@
1
+ export declare const BLOCKLET_INFO_DIR = ".blocklet-info";
1
2
  export declare function getWorkspaceTemplatePath(): string;
2
3
  export declare function getWorkspacePath(version?: string): string;
3
4
  export declare function getProjectPath(): string;
@@ -101,3 +102,17 @@ export declare function isCommandAvailable(command: string): boolean;
101
102
  */
102
103
  export declare function installGlobalPackage(packageName: string): Promise<boolean>;
103
104
  export declare function checkShouldInstallDependencies(dirPath: string): Promise<boolean>;
105
+ /**
106
+ * Setup workspace environment for development
107
+ * @param projectPath - Current project path
108
+ * @param options - Setup options
109
+ * @returns Setup result with dependency installation status
110
+ */
111
+ export declare function setupWorkspaceEnvironment(projectPath: string, options?: {
112
+ version?: string;
113
+ }): Promise<{
114
+ workspacePath: string;
115
+ projectPath: string;
116
+ shouldInstallWorkspaceDeps: boolean;
117
+ shouldInstallProjectDeps: boolean;
118
+ }>;
@@ -10,8 +10,9 @@ import { join, resolve, dirname, basename, relative } from 'node:path';
10
10
  import ora from 'ora';
11
11
  import os from 'os';
12
12
  import path from 'path';
13
- const DEFAULT_WORKSPACE_VERSION = '0.2.0';
13
+ const DEFAULT_WORKSPACE_VERSION = '0.2.1';
14
14
  const METADATA_FILE_NAME = '@metadata.json';
15
+ export const BLOCKLET_INFO_DIR = '.blocklet-info';
15
16
  // 获取CLI内置的workspace模板路径
16
17
  export function getWorkspaceTemplatePath() {
17
18
  return join(import.meta.dirname, '../../templates/workspace');
@@ -122,34 +123,39 @@ export async function installDependencies(dirs, operation = 'install') {
122
123
  // ignore error
123
124
  }
124
125
  let errorOutput = '';
125
- const depProcess = exec("npx -y taze -f -r -w -n '/arcblock|ocap|abtnode|blocklet|did-connect|did-comment|nedb/' minor && pnpm install -f && pnpm dedupe", {
126
+ let fullOutput = '';
127
+ const command = "npx -y taze -f -r -w -n '/arcblock|ocap|abtnode|blocklet|did-connect|did-comment|nedb/' minor && pnpm install -f --no-frozen-lockfile && pnpm dedupe";
128
+ const depProcess = exec(command, {
126
129
  cwd: dirPath,
127
130
  env: {
128
131
  ...process.env,
129
132
  NODE_ENV: 'development',
130
133
  },
131
134
  });
132
- // 收集错误输出
133
- depProcess.stderr?.on('data', (data) => {
134
- errorOutput += data.toString();
135
- });
136
- // 收集标准输出(可能包含错误信息)
135
+ // 收集所有输出
137
136
  depProcess.stdout?.on('data', (data) => {
138
137
  const output = data.toString();
138
+ fullOutput += output;
139
139
  if (output.toLowerCase().includes('error')) {
140
140
  errorOutput += output;
141
141
  }
142
142
  });
143
+ // 收集错误输出
144
+ depProcess.stderr?.on('data', (data) => {
145
+ const output = data.toString();
146
+ errorOutput += output;
147
+ fullOutput += output;
148
+ });
143
149
  depProcess.on('close', (code) => {
144
150
  if (code === 0) {
145
151
  resolve();
146
152
  }
147
153
  else {
148
- reject(new Error(`${dirType} dependencies install failed with code ${code}. ${errorOutput ? `\nError details:\n${errorOutput?.trimStart()}` : ''}}`));
154
+ reject(new Error(`${dirType} dependencies install failed with code ${code}. ${errorOutput ? `\nError details:\n${errorOutput?.trimStart()}` : ''}\n\nFull output:\n${fullOutput}`));
149
155
  }
150
156
  });
151
157
  depProcess.on('error', (error) => {
152
- reject(new Error(`${dirType} process error: ${error.message}\n${errorOutput}`));
158
+ reject(new Error(`${dirType} process error: ${error.message}\n${errorOutput}\n\nFull output:\n${fullOutput}`));
153
159
  });
154
160
  });
155
161
  })
@@ -540,3 +546,102 @@ export async function installGlobalPackage(packageName) {
540
546
  export async function checkShouldInstallDependencies(dirPath) {
541
547
  return !(await fs.exists(join(dirPath, 'node_modules'))) || !(await fs.exists(join(dirPath, 'pnpm-lock.yaml')));
542
548
  }
549
+ /**
550
+ * Setup workspace environment for development
551
+ * @param projectPath - Current project path
552
+ * @param options - Setup options
553
+ * @returns Setup result with dependency installation status
554
+ */
555
+ export async function setupWorkspaceEnvironment(projectPath, options = {}) {
556
+ // 1. Create workspace
557
+ const workspacePath = getWorkspacePath(options.version);
558
+ const workspaceSpinner = ora({
559
+ text: 'Creating workspace...',
560
+ color: 'blue',
561
+ }).start();
562
+ const workspacePathText = chalk.blue(`The workspace directory: ${workspacePath}`);
563
+ if (options.version) {
564
+ console.log(chalk.cyan(`Using version: ${options.version}`));
565
+ }
566
+ // 2. Prepare base environment
567
+ if (!existsSync(workspacePath)) {
568
+ const workspaceTemplatePath = getWorkspaceTemplatePath();
569
+ const workspaceParentDir = dirname(workspacePath);
570
+ // Selectively clean workspace parent directory, preserving @ prefixed directories
571
+ if (existsSync(workspaceParentDir)) {
572
+ const items = await fs.readdir(workspaceParentDir);
573
+ await Promise.all(items
574
+ .filter((item) => !item.startsWith('@'))
575
+ .map((item) => fs.remove(join(workspaceParentDir, item))));
576
+ }
577
+ else {
578
+ // Create workspace parent directory
579
+ await fs.ensureDir(workspaceParentDir);
580
+ }
581
+ // Copy workspace template
582
+ await fs.copy(workspaceTemplatePath, workspacePath);
583
+ // Replace _example suffix in workspace directory file names
584
+ await replaceInDirectoryFileNameWithExample(workspacePath);
585
+ workspaceSpinner.succeed(chalk.green(`Workspace created successfully! ${workspacePathText}`));
586
+ }
587
+ else {
588
+ workspaceSpinner.succeed(chalk.green(`Using existing workspace! ${workspacePathText}`));
589
+ }
590
+ // 3. Ensure projects directory exists
591
+ const projectsDir = join(workspacePath, 'projects');
592
+ const projectLinkDir = join(projectsDir, 'user-project');
593
+ const projectLinkSpinner = ora({
594
+ text: 'Setting up project to workspace...',
595
+ color: 'blue',
596
+ }).start();
597
+ // Check if already correctly linked
598
+ let needsNewLink = true;
599
+ if (existsSync(projectsDir)) {
600
+ try {
601
+ const stats = await fs.lstat(projectLinkDir);
602
+ if (stats.isSymbolicLink()) {
603
+ const currentTarget = await fs.readlink(projectLinkDir);
604
+ if (currentTarget === projectPath) {
605
+ needsNewLink = false;
606
+ projectLinkSpinner.succeed(chalk.green('Project already linked to workspace!'));
607
+ }
608
+ else {
609
+ await fs.remove(projectsDir);
610
+ }
611
+ }
612
+ else {
613
+ await fs.remove(projectsDir);
614
+ }
615
+ }
616
+ catch (error) {
617
+ await fs.remove(projectsDir);
618
+ }
619
+ }
620
+ if (needsNewLink) {
621
+ await fs.ensureDir(projectsDir);
622
+ await fs.ensureSymlink(projectPath, projectLinkDir);
623
+ projectLinkSpinner.succeed(chalk.green('Project linked to workspace successfully!'));
624
+ }
625
+ const shouldInstallWorkspaceDeps = await checkShouldInstallDependencies(workspacePath);
626
+ const shouldInstallProjectDeps = await checkShouldInstallDependencies(projectPath);
627
+ // Install dependencies
628
+ if (shouldInstallWorkspaceDeps || shouldInstallProjectDeps) {
629
+ const installDeps = {};
630
+ if (shouldInstallProjectDeps) {
631
+ installDeps.Project = projectPath;
632
+ }
633
+ if (shouldInstallWorkspaceDeps) {
634
+ installDeps.Workspace = workspacePath;
635
+ }
636
+ const result = await installDependencies(installDeps);
637
+ if (!result) {
638
+ process.exit(1);
639
+ }
640
+ }
641
+ return {
642
+ workspacePath,
643
+ projectPath,
644
+ shouldInstallWorkspaceDeps,
645
+ shouldInstallProjectDeps,
646
+ };
647
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/component-studio-cli",
3
- "version": "0.6.14",
3
+ "version": "0.6.16",
4
4
  "description": "CLI for Component Studio",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -30,6 +30,7 @@
30
30
  }
31
31
  },
32
32
  "dependencies": {
33
+ "adm-zip": "^0.5.16",
33
34
  "chalk": "^5.4.1",
34
35
  "commander": "^13.1.0",
35
36
  "fs-extra": "^11.2.0",
@@ -41,9 +42,11 @@
41
42
  "node-fetch": "^2.7.0",
42
43
  "ora": "^8.2.0",
43
44
  "pretty-error": "^4.0.0",
44
- "tar": "^7.4.3"
45
+ "tar": "^7.4.3",
46
+ "yaml": "^2.5.0"
45
47
  },
46
48
  "devDependencies": {
49
+ "@types/adm-zip": "^0.5.7",
47
50
  "@types/glob": "^8.1.0",
48
51
  "@types/node": "^22.14.1",
49
52
  "npm-run-all": "^4.1.5",
@@ -0,0 +1,35 @@
1
+ name: CI
2
+
3
+ env:
4
+ NODE_OPTIONS: '--max_old_space_size=6144'
5
+
6
+ on:
7
+ pull_request:
8
+ branches:
9
+ - main
10
+
11
+ jobs:
12
+ Deploy:
13
+ runs-on: ubuntu-latest
14
+
15
+ if: "contains(toJSON(github.event.commits.*.message), '[ci]')"
16
+
17
+ steps:
18
+ - name: Checkout repo
19
+ uses: actions/checkout@v4
20
+
21
+ - uses: pnpm/action-setup@v3
22
+ with:
23
+ version: 9
24
+
25
+ - name: Setup node
26
+ uses: actions/setup-node@v4
27
+ with:
28
+ node-version: 20
29
+ cache: pnpm
30
+
31
+ - name: Install dependencies
32
+ run: pnpm install
33
+
34
+ - name: Lint
35
+ run: pnpm lint
@@ -0,0 +1,40 @@
1
+ name: Code Review
2
+
3
+ permissions:
4
+ contents: read
5
+ pull-requests: write
6
+
7
+ on:
8
+ pull_request:
9
+ branches:
10
+ - main
11
+ pull_request_review_comment:
12
+ types: [created]
13
+
14
+ concurrency:
15
+ group: ${{ github.repository }}-${{ github.event.number || github.head_ref ||
16
+ github.sha }}-${{ github.workflow }}-${{ github.event_name ==
17
+ 'pull_request_review_comment' && 'pr_comment' || 'pr' }}
18
+ cancel-in-progress: ${{ github.event_name != 'pull_request_review_comment' }}
19
+
20
+ jobs:
21
+ review:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: blocklet/aigne-code-reviewer@v0.1.14
25
+ env:
26
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
28
+ DEBUG: "@aigne/*"
29
+ with:
30
+ debug: true
31
+ language: "zh-CN"
32
+ review_simple_changes: false
33
+ review_comment_lgtm: false
34
+ disable_review: true
35
+ path_filters: |
36
+ !core/types/**
37
+ !core/schema/lib/**
38
+ !**/types.js
39
+ !**/*.d.ts
40
+ !**/*.lock
@@ -0,0 +1,21 @@
1
+ name: 'Lint PR Title'
2
+
3
+ on:
4
+ pull_request:
5
+ types:
6
+ - opened
7
+ - reopened
8
+ - edited
9
+ - synchronize
10
+ branches:
11
+ - main
12
+ - dev
13
+ - master
14
+
15
+ jobs:
16
+ lint-title:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: ArcBlock/action-lint-pull-request-title@master
20
+ env:
21
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,71 @@
1
+ name: Upload To Blocklet Store
2
+
3
+ env:
4
+ NODE_OPTIONS: "--max_old_space_size=8192"
5
+
6
+ on:
7
+ push:
8
+ branches:
9
+ - main
10
+ - dev
11
+ - release
12
+
13
+ jobs:
14
+ Deploy:
15
+ runs-on: ubuntu-latest
16
+
17
+ if: "! contains(toJSON(github.event.commits.head_commit.message), '[skip ci]')"
18
+
19
+ steps:
20
+ - name: Set environment variables based on branch
21
+ run: |
22
+ echo "Current branch: ${{ github.ref_name }}"
23
+ if [ "${{ github.ref_name }}" == "main" ]; then
24
+ echo "STORE_ENDPOINT=${{ secrets.STORE_ENDPOINT_TEST }}" >> $GITHUB_ENV
25
+ echo "STORE_ACCESS_TOKEN=${{ secrets.STORE_ACCESS_TOKEN_TEST }}" >> $GITHUB_ENV
26
+ elif [ "${{ github.ref_name }}" == "dev" ]; then
27
+ echo "STORE_ENDPOINT=${{ secrets.STORE_ENDPOINT_DEV }}" >> $GITHUB_ENV
28
+ echo "STORE_ACCESS_TOKEN=${{ secrets.STORE_ACCESS_TOKEN_DEV }}" >> $GITHUB_ENV
29
+ elif [ "${{ github.ref_name }}" == "release" ]; then
30
+ echo "STORE_ENDPOINT=${{ secrets.STORE_ENDPOINT_PROD }}" >> $GITHUB_ENV
31
+ echo "STORE_ACCESS_TOKEN=${{ secrets.STORE_ACCESS_TOKEN_PROD }}" >> $GITHUB_ENV
32
+ else
33
+ echo "❌ Error: Branch '${{ github.ref_name }}' is not supported for upload to blocklet store"
34
+ echo "✅ Supported branches: main, dev, release"
35
+ exit 1
36
+ fi
37
+
38
+ - name: Checkout repo
39
+ uses: actions/checkout@v4
40
+
41
+ - uses: pnpm/action-setup@v3
42
+ with:
43
+ version: 9
44
+
45
+ - name: Setup node
46
+ uses: actions/setup-node@v4
47
+ with:
48
+ node-version: 22
49
+ cache: pnpm
50
+
51
+ - name: Using Component Studio to build project
52
+ run: npx -y @blocklet/cli@beta component build --bundle-resource-blocklet
53
+
54
+ - name: Blocklet workflow
55
+ if: "! contains(toJSON(github.event.commits.head_commit.message), '[skip blocklet]')"
56
+ uses: blocklet/action-workflow@v1
57
+ env:
58
+ COMPONENT_STORE_URL: ${{ env.STORE_ENDPOINT }}
59
+ with:
60
+ deps-server-version: beta
61
+ skip-deps: true
62
+ skip-release: true
63
+ skip-deploy: true
64
+ skip-bundle: true
65
+ skip-upload: false
66
+ bundle-command: pnpm -v # it means skip bundle
67
+ working-directory: .blocklet-info
68
+ store-endpoint: ${{ env.STORE_ENDPOINT }}
69
+ store-access-token: ${{ env.STORE_ACCESS_TOKEN }}
70
+ slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
71
+ github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,20 @@
1
+ name: 'Version Check'
2
+
3
+ on:
4
+ pull_request:
5
+ types:
6
+ - opened
7
+ - reopened
8
+ - edited
9
+ - synchronize
10
+ branches:
11
+ - main
12
+ - master
13
+ jobs:
14
+ main:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: action-version-check
18
+ uses: arcblock/action-version-check@master
19
+ env:
20
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "Teamwork Tools",
3
+ "description": "Teamwork Tools auto-adds necessary folders for collaboration and enables automatic packaging and publishing after bump-version upload (initialization requires specifying the Resource Blocklet path)."
4
+ }
@@ -33,4 +33,7 @@ yarn-error.log*
33
33
 
34
34
  # Logs
35
35
  logs
36
- *.log
36
+ *.log
37
+
38
+ .blocklet-info/resources
39
+ .blocklet-info/.blocklet
@@ -7,7 +7,10 @@
7
7
  "scripts": {
8
8
  "start": "blocklet component dev",
9
9
  "dev": "blocklet component dev",
10
- "update:deps": "blocklet component update"
10
+ "update:deps": "blocklet component update",
11
+ "update": "blocklet component update",
12
+ "bundle": "blocklet component build --bundle-resource-blocklet",
13
+ "bump-version": "blocklet component bump-version"
11
14
  },
12
15
  "lint-staged": {
13
16
  "*.{js,jsx,ts,tsx}": [
@@ -33,4 +33,7 @@ yarn-error.log*
33
33
 
34
34
  # Logs
35
35
  logs
36
- *.log
36
+ *.log
37
+
38
+ .blocklet-info/resources
39
+ .blocklet-info/.blocklet
@@ -11,7 +11,7 @@
11
11
  "noUnusedVariables": "error"
12
12
  },
13
13
  "suspicious": {
14
- "noExplicitAny": "warn"
14
+ "noExplicitAny": "off"
15
15
  },
16
16
  "style": {
17
17
  "noNonNullAssertion": "off",
@@ -12,13 +12,15 @@
12
12
  "scripts": {
13
13
  "dev": "blocklet component dev",
14
14
  "start": "blocklet component dev",
15
- "lint": "biome check . && tsc --noEmit",
15
+ "lint": "biome check ./src && tsc --noEmit",
16
16
  "lint:skip": "echo 'skip lint'",
17
- "lint:fix": "biome check --apply .",
18
- "format": "biome format --write .",
17
+ "lint:fix": "biome check --apply ./src",
18
+ "format": "biome format --write ./src",
19
19
  "prepare": "npx -y simple-git-hooks",
20
- "bump-version": "zx --quiet scripts/bump-version.mjs",
21
- "update:deps": "blocklet component update"
20
+ "update:deps": "blocklet component update",
21
+ "update": "blocklet component update",
22
+ "bundle": "blocklet component build --bundle-resource-blocklet",
23
+ "bump-version": "blocklet component bump-version"
22
24
  },
23
25
  "lint-staged": {
24
26
  "*.{mjs,js,jsx,ts,tsx,css,less,scss,json,graphql}": [
@@ -33,4 +33,7 @@ yarn-error.log*
33
33
 
34
34
  # Logs
35
35
  logs
36
- *.log
36
+ *.log
37
+
38
+ .blocklet-info/resources
39
+ .blocklet-info/.blocklet
@@ -7,7 +7,10 @@
7
7
  "scripts": {
8
8
  "start": "blocklet component dev",
9
9
  "dev": "blocklet component dev",
10
- "update:deps": "blocklet component update"
10
+ "update:deps": "blocklet component update",
11
+ "update": "blocklet component update",
12
+ "bundle": "blocklet component build --bundle-resource-blocklet",
13
+ "bump-version": "blocklet component bump-version"
11
14
  },
12
15
  "lint-staged": {
13
16
  "*.{js,jsx,ts,tsx}": [
@@ -28,4 +28,7 @@ yarn-error.log*
28
28
  pnpm-debug.log*
29
29
  lerna-debug.log*
30
30
 
31
- /lib
31
+ /lib
32
+
33
+ .blocklet-info/resources
34
+ .blocklet-info/.blocklet
@@ -11,7 +11,7 @@
11
11
  "noUnusedVariables": "error"
12
12
  },
13
13
  "suspicious": {
14
- "noExplicitAny": "warn"
14
+ "noExplicitAny": "off"
15
15
  },
16
16
  "style": {
17
17
  "noNonNullAssertion": "off",
@@ -15,10 +15,10 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "dev": "COMPONENT_STORE_URL=https://test.store.blocklet.dev blocklet dev",
18
- "lint": "biome check . && tsc --noEmit",
18
+ "lint": "biome check ./src && tsc --noEmit",
19
19
  "lint:skip": "echo 'skip lint'",
20
- "lint:fix": "biome check --apply .",
21
- "format": "biome format --write .",
20
+ "lint:fix": "biome check --apply ./src",
21
+ "format": "biome format --write ./src",
22
22
  "start": "vite-node -c vite-server.config.ts -w api/dev.ts",
23
23
  "clean": "node scripts/build-clean.mjs",
24
24
  "bundle": "echo 'skip bundle'",
@@ -1,35 +0,0 @@
1
- /* eslint-disable no-console */
2
- import { execSync } from 'child_process';
3
- import { $, chalk, fs } from 'zx';
4
-
5
- async function main() {
6
- execSync('bumpp --no-tag --no-commit --no-push package.json', { stdio: 'inherit' });
7
-
8
- const { version } = await fs.readJSON('package.json');
9
- await fs.writeFileSync('version', version);
10
-
11
- let newChangelog = '';
12
- try {
13
- const gitRes = await $`git log --pretty=format:"- %s" "main"...HEAD`;
14
- newChangelog = gitRes.stdout.trim();
15
- } catch {
16
- console.error(chalk.redBright('Could not get git log, please write changelog manually.'));
17
- }
18
-
19
- const now = new Date();
20
- const currentDate = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
21
- const title = `## ${version} (${currentDate})`;
22
-
23
- await fs.ensureFile('CHANGELOG.md');
24
- const oldChangelog = await fs.readFile('CHANGELOG.md', 'utf8');
25
- const changelog = [title, newChangelog, oldChangelog].filter((item) => !!item).join('\n\n');
26
- await fs.writeFile('CHANGELOG.md', changelog);
27
-
28
- console.log(`\nNow you can make adjustments to ${chalk.cyan('CHANGELOG.md')} . Then press enter to continue.`);
29
-
30
- process.stdin.setRawMode(true);
31
- process.stdin.resume();
32
- process.stdin.on('data', process.exit.bind(process, 0));
33
- }
34
-
35
- main();
@@ -1 +0,0 @@
1
- 0.1.0