@solazah/solazah-cli 0.2.20 → 0.2.23

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.2.23](https://cnb.cool/seayona/solazah/solazah-cli/compare/v0.2.22...v0.2.23) (2026-06-20)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **npmrc:** update auth token for npm registry ([c706539](https://cnb.cool/seayona/solazah/solazah-cli/commit/c706539a5616d64b956ed5de3d6ad974d878f842))
11
+ * **template:** 修复脚手架类型门禁与 dotfile 发布剥离 ([9b041d1](https://cnb.cool/seayona/solazah/solazah-cli/commit/9b041d1221ea57e5804df21985e39e6f6991f36d))
12
+ * **vite.config:** change output directory from target to dist ([b7613bd](https://cnb.cool/seayona/solazah/solazah-cli/commit/b7613bde787280b8ff2190058ad8cfb406f5c31b))
13
+
14
+ ### [0.2.22](https://cnb.cool/seayona/solazah/solazah-cli/compare/v0.2.21...v0.2.22) (2026-06-13)
15
+
16
+
17
+ ### Features
18
+
19
+ * **config:** update files to copy and adjust template directory path ([02d46a3](https://cnb.cool/seayona/solazah/solazah-cli/commit/02d46a3f91b8274ef9b735ad7fae06fedc7cce50))
20
+ * **plugin:** add command to list available plugin templates ([bb708c1](https://cnb.cool/seayona/solazah/solazah-cli/commit/bb708c10766f8d68bfa8f47e45cc0c5b695cb908))
21
+ * **plugin:** package 时复制 public 目录,支持命令图标等静态资产随包发布 ([bce1e75](https://cnb.cool/seayona/solazah/solazah-cli/commit/bce1e75ae491114b6b795c68ce843df711ba3000))
22
+
23
+ ### [0.2.21](https://cnb.cool/seayona/solazah/solazah-cli/compare/v0.2.20...v0.2.21) (2026-05-03)
24
+
5
25
  ### [0.2.20](https://cnb.cool/seayona/solazah/solazah-cli/compare/v0.2.19...v0.2.20) (2026-05-03)
6
26
 
7
27
  ### [0.2.19](https://cnb.cool/seayona/solazah/solazah-cli/compare/v0.2.18...v0.2.19) (2026-05-03)
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/commands/plugin/create.ts"],"names":[],"mappings":"AAuBA,UAAU,mBAAmB;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiPrF"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/commands/plugin/create.ts"],"names":[],"mappings":"AAuBA,UAAU,mBAAmB;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuQrF"}
@@ -1 +1 @@
1
- {"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../../../src/commands/plugin/package.ts"],"names":[],"mappings":"AASA,UAAU,oBAAoB;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAsID,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgEvF"}
1
+ {"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../../../src/commands/plugin/package.ts"],"names":[],"mappings":"AASA,UAAU,oBAAoB;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA4ID,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgEvF"}
@@ -0,0 +1,6 @@
1
+ interface TemplatesOptions {
2
+ json?: boolean;
3
+ }
4
+ export declare function listTemplatesCommand(options: TemplatesOptions): Promise<void>;
5
+ export {};
6
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/commands/plugin/templates.ts"],"names":[],"mappings":"AAGA,UAAU,gBAAgB;IACxB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBnF"}
package/index.js CHANGED
@@ -57,7 +57,7 @@ async function ensureDirectory(dirPath) {
57
57
  await fs.ensureDir(dirPath);
58
58
  }
59
59
  function getTemplatesRootDir() {
60
- return path.resolve(__dirname$1, "../templates");
60
+ return path.resolve(__dirname$1, "templates");
61
61
  }
62
62
  function getTemplateDir(templateName) {
63
63
  return path.join(getTemplatesRootDir(), templateName);
@@ -272,6 +272,17 @@ async function createPluginCommand(options) {
272
272
  manifest.commands = [answers.displayName];
273
273
  manifest.development.main = `http://localhost:${answers.port}`;
274
274
  await fs.writeJson(manifestPath, manifest, { spaces: 2 });
275
+ const dotfileRenames = [
276
+ ["gitignore", ".gitignore"],
277
+ ["npmrc", ".npmrc"]
278
+ ];
279
+ for (const [from, to] of dotfileRenames) {
280
+ const fromPath = path.join(targetDir, from);
281
+ const toPath = path.join(targetDir, to);
282
+ if (await fs.pathExists(fromPath)) {
283
+ await fs.move(fromPath, toPath, { overwrite: true });
284
+ }
285
+ }
275
286
  const filesToRemove = [
276
287
  "template.json"
277
288
  ];
@@ -282,6 +293,13 @@ async function createPluginCommand(options) {
282
293
  }
283
294
  }
284
295
  spinner.succeed("Files updated");
296
+ spinner.start("Initializing git repository...");
297
+ try {
298
+ await execAsync$2("git init -b master", { cwd: targetDir });
299
+ spinner.succeed("Git repository initialized");
300
+ } catch {
301
+ spinner.info("Skipped git init (git not available)");
302
+ }
285
303
  if (!options.skipInstall) {
286
304
  spinner.start("Installing dependencies...");
287
305
  try {
@@ -340,6 +358,10 @@ function resolveFilesToCopy(projectDir, manifest) {
340
358
  if (fs.existsSync(srcScreenshots)) {
341
359
  entries.push({ from: "src/screenshots", to: "screenshots" });
342
360
  }
361
+ const srcPublic = path.join(projectDir, "public");
362
+ if (fs.existsSync(srcPublic)) {
363
+ entries.push({ from: "public", to: "public" });
364
+ }
343
365
  return entries;
344
366
  }
345
367
  function cleanReleaseDir(releaseDir) {
@@ -919,6 +941,27 @@ async function releasePluginCommand(options) {
919
941
  await packagePluginCommand({ dir: projectDir });
920
942
  await publishPluginCommand({ dir: projectDir });
921
943
  }
944
+ async function listTemplatesCommand(options) {
945
+ const templates = await getAvailableTemplates();
946
+ if (options.json) {
947
+ console.log(JSON.stringify(templates, null, 2));
948
+ return;
949
+ }
950
+ if (templates.length === 0) {
951
+ console.log(chalk.yellow("No templates available."));
952
+ return;
953
+ }
954
+ console.log(chalk.bold("\nAvailable templates:\n"));
955
+ for (const tpl of templates) {
956
+ console.log(` ${chalk.cyan(tpl.name)} ${chalk.gray("v" + tpl.version)}`);
957
+ console.log(` ${tpl.displayName}`);
958
+ console.log(` ${chalk.gray(tpl.description)}`);
959
+ if (tpl.features?.length) {
960
+ console.log(chalk.gray(` Features: ${tpl.features.join(", ")}`));
961
+ }
962
+ console.log();
963
+ }
964
+ }
922
965
  function openBrowser(url) {
923
966
  try {
924
967
  const platform = process.platform;
@@ -1107,6 +1150,7 @@ plugin.command("package").description("Build and package a Solazah plugin for di
1107
1150
  plugin.command("publish").description("Publish a plugin package to Solazah marketplace").option("-f, --file <file>", "Path to the .tgz plugin package file").option("-d, --dir <directory>", "Plugin project directory", ".").action(publishPluginCommand);
1108
1151
  plugin.command("version").description("Bump plugin version, update CHANGELOG and create git tag").option("-d, --dir <directory>", "Plugin project directory", ".").option("-r, --release-as <type>", "Specify release type: major, minor, patch").action(versionPluginCommand);
1109
1152
  plugin.command("release").description("Bump version, package and publish a plugin in one step").option("-d, --dir <directory>", "Plugin project directory", ".").action(releasePluginCommand);
1153
+ plugin.command("templates").description("List available plugin templates").option("--json", "Output as JSON").action(listTemplatesCommand);
1110
1154
  const account = program.command("account").description("Manage Solazah account authentication");
1111
1155
  const withIssuer = (cmd) => cmd.option("--issuer <issuer>", "OAuth2 issuer URL");
1112
1156
  withIssuer(account.command("login")).description("Login via OAuth2 device code flow").action(loginCommand);
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/utils/validate.ts","../src/utils/file.ts","../src/commands/plugin/create.ts","../src/commands/plugin/package.ts","../src/utils/auth-storage.ts","../src/utils/oauth-device.ts","../src/commands/plugin/publish.ts","../src/commands/plugin/version.ts","../src/commands/plugin/release.ts","../src/utils/open-browser.ts","../src/commands/account/shared.ts","../src/commands/account/login.ts","../src/commands/account/logout.ts","../src/commands/account/userinfo.ts","../src/index.ts"],"sourcesContent":["import validatePackageName from 'validate-npm-package-name';\r\n\r\nexport interface ValidationResult {\r\n valid: boolean;\r\n errors: string[];\r\n}\r\n\r\n/**\r\n * 验证插件名称\r\n * 接受任何合法的 npm 包名\r\n */\r\nexport function validatePluginName(name: string): ValidationResult {\r\n const errors: string[] = [];\r\n\r\n // 基本格式检查\r\n if (!name) {\r\n errors.push('Plugin name is required');\r\n return { valid: false, errors };\r\n }\r\n\r\n // 使用 npm 包名验证\r\n const validation = validatePackageName(name);\r\n if (!validation.validForNewPackages) {\r\n if (validation.errors) {\r\n errors.push(...validation.errors);\r\n }\r\n if (validation.warnings) {\r\n errors.push(...validation.warnings);\r\n }\r\n }\r\n\r\n return {\r\n valid: errors.length === 0,\r\n errors,\r\n };\r\n}\r\n\r\n/**\r\n * 从包名提取插件名称\r\n * @example extractPluginName('@solazah/solazah-plugin-example') => 'solazah-plugin-example'\r\n */\r\nexport function extractPluginName(packageName: string): string {\r\n // 移除作用域前缀(如 @solazah/)\r\n return packageName.replace(/^@[^/]+\\//, '');\r\n}\r\n\r\nexport function extractPluginSimpleName(packageName: string){\r\n // 移除作用域前缀(如 @solazah/)\r\n let name = packageName.replace(/^@[^/]+\\//, '');\r\n\r\n // 移除常见的插件前缀\r\n name = name.replace(/^plugin-/, '').replace(/^solazah-plugin-/, '');\r\n\r\n return name;\r\n}\r\n\r\n/**\r\n * 生成显示名称\r\n * @example generateDisplayName('example') => 'Example'\r\n */\r\nexport function generateDisplayName(name: string): string {\r\n return name\r\n .split('-')\r\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\r\n .join(' ');\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport { fileURLToPath } from 'url';\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\nexport interface TemplateInfo {\r\n name: string;\r\n displayName: string;\r\n description: string;\r\n version: string;\r\n features?: string[];\r\n}\r\n\r\n/**\r\n * 检查目录是否为空\r\n */\r\nexport async function isDirectoryEmpty(dirPath: string): Promise<boolean> {\r\n try {\r\n const files = await fs.readdir(dirPath);\r\n return files.length === 0;\r\n } catch {\r\n return true; // 目录不存在,视为空\r\n }\r\n}\r\n\r\n/**\r\n * 确保目录存在\r\n */\r\nexport async function ensureDirectory(dirPath: string): Promise<void> {\r\n await fs.ensureDir(dirPath);\r\n}\r\n\r\n/**\r\n * 获取模板根目录路径\r\n */\r\nexport function getTemplatesRootDir(): string {\r\n return path.resolve(__dirname, '../templates');\r\n}\r\n\r\n/**\r\n * 获取指定模板的目录路径\r\n */\r\nexport function getTemplateDir(templateName: string): string {\r\n return path.join(getTemplatesRootDir(), templateName);\r\n}\r\n\r\n/**\r\n * 获取所有可用模板\r\n */\r\nexport async function getAvailableTemplates(): Promise<TemplateInfo[]> {\r\n const templatesRoot = getTemplatesRootDir();\r\n const templates: TemplateInfo[] = [];\r\n\r\n try {\r\n const entries = await fs.readdir(templatesRoot, { withFileTypes: true });\r\n\r\n for (const entry of entries) {\r\n if (entry.isDirectory()) {\r\n const templateJsonPath = path.join(templatesRoot, entry.name, 'template.json');\r\n\r\n if (await fs.pathExists(templateJsonPath)) {\r\n const templateInfo = await fs.readJson(templateJsonPath);\r\n templates.push(templateInfo);\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n console.error('Failed to read templates:', error);\r\n }\r\n\r\n return templates;\r\n}\r\n\r\n/**\r\n * 复制模板文件\r\n */\r\nexport async function copyTemplate(templateDir: string, targetDir: string): Promise<void> {\r\n await fs.copy(templateDir, targetDir, {\r\n filter: (src) => {\r\n // 排除 node_modules 和其他不需要的文件\r\n const basename = path.basename(src);\r\n return !['node_modules', '.git', 'dist', 'target', 'release'].includes(basename);\r\n },\r\n });\r\n}\r\n\r\n/**\r\n * 替换文件内容中的占位符\r\n * 支持 {{PLACEHOLDER}} 格式\r\n */\r\nexport async function replacePlaceholders(\r\n filePath: string,\r\n replacements: Record<string, string>\r\n): Promise<void> {\r\n let content = await fs.readFile(filePath, 'utf-8');\r\n\r\n for (const [key, value] of Object.entries(replacements)) {\r\n // 支持 {{KEY}} 和直接字符串替换\r\n const placeholder = `{{${key}}}`;\r\n content = content.replaceAll(placeholder, value);\r\n // 兼容旧的直接替换方式\r\n content = content.replaceAll(key, value);\r\n }\r\n\r\n await fs.writeFile(filePath, content, 'utf-8');\r\n}\r\n\r\n/**\r\n * 批量替换目录中所有文件的占位符\r\n */\r\nexport async function replaceInDirectory(\r\n dirPath: string,\r\n replacements: Record<string, string>,\r\n extensions: string[] = ['.ts', '.tsx', '.js', '.jsx', '.json', '.html', '.md']\r\n): Promise<void> {\r\n const files = await fs.readdir(dirPath, { withFileTypes: true });\r\n\r\n for (const file of files) {\r\n const filePath = path.join(dirPath, file.name);\r\n\r\n if (file.isDirectory()) {\r\n // 递归处理子目录\r\n await replaceInDirectory(filePath, replacements, extensions);\r\n } else if (file.isFile()) {\r\n // 检查文件扩展名\r\n const ext = path.extname(file.name);\r\n if (extensions.includes(ext) || file.name === 'manifest.json') {\r\n await replacePlaceholders(filePath, replacements);\r\n }\r\n }\r\n }\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport inquirer from 'inquirer';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport { exec } from 'child_process';\r\nimport { promisify } from 'util';\r\nimport {\r\n validatePluginName,\r\n extractPluginName,\r\n generateDisplayName, extractPluginSimpleName,\r\n} from '../../utils/validate.js';\r\nimport {\r\n isDirectoryEmpty,\r\n ensureDirectory,\r\n getTemplateDir,\r\n getAvailableTemplates,\r\n copyTemplate,\r\n replaceInDirectory,\r\n} from '../../utils/file.js';\r\n\r\nconst execAsync = promisify(exec);\r\n\r\ninterface CreatePluginOptions {\r\n name?: string;\r\n dir?: string;\r\n targetDir?: string;\r\n template?: string;\r\n displayName?: string;\r\n description?: string;\r\n author?: string;\r\n port?: number;\r\n yes?: boolean;\r\n skipInstall?: boolean;\r\n}\r\n\r\nexport async function createPluginCommand(options: CreatePluginOptions): Promise<void> {\r\n console.log(chalk.cyan.bold('\\n🚀 Solazah Plugin Creator\\n'));\r\n\r\n try {\r\n // 1. 获取可用模板\r\n const availableTemplates = await getAvailableTemplates();\r\n\r\n if (availableTemplates.length === 0) {\r\n console.error(chalk.red('❌ No templates found'));\r\n process.exit(1);\r\n }\r\n\r\n // 2. 选择模板\r\n let selectedTemplate = options.template || 'react';\r\n\r\n if (!options.template && availableTemplates.length > 1) {\r\n const { template } = await inquirer.prompt([\r\n {\r\n type: 'list',\r\n name: 'template',\r\n message: 'Select a template:',\r\n choices: availableTemplates.map((t) => ({\r\n name: `${t.displayName} - ${t.description}`,\r\n value: t.name,\r\n })),\r\n default: 'react',\r\n },\r\n ]);\r\n selectedTemplate = template;\r\n }\r\n\r\n const templateInfo = availableTemplates.find((t) => t.name === selectedTemplate);\r\n if (!templateInfo) {\r\n console.error(chalk.red(`❌ Template \"${selectedTemplate}\" not found`));\r\n process.exit(1);\r\n }\r\n\r\n console.log(chalk.gray(`Using template: ${templateInfo.displayName}\\n`));\r\n\r\n // 3. 获取或询问插件名称\r\n let pluginName = options.name;\r\n\r\n if (!pluginName) {\r\n const answers = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'name',\r\n message: 'Plugin name (e.g., my-plugin or @scope/plugin-name):',\r\n validate: (input: string) => {\r\n const result = validatePluginName(input);\r\n return result.valid || result.errors.join(', ');\r\n },\r\n },\r\n ]);\r\n pluginName = answers.name;\r\n }\r\n\r\n // 验证插件名称\r\n const validation = validatePluginName(pluginName!);\r\n if (!validation.valid) {\r\n console.error(chalk.red('❌ Invalid plugin name:'));\r\n validation.errors.forEach((error) => console.error(chalk.red(` - ${error}`)));\r\n process.exit(1);\r\n }\r\n\r\n // 2. 提取信息\r\n const packageName = extractPluginName(pluginName!)\r\n const simpleName = extractPluginSimpleName(pluginName!);\r\n const defaultDisplayName = generateDisplayName(simpleName);\r\n\r\n // 3. 确定目标目录\r\n const defaultTargetDir = options.targetDir\r\n ? path.resolve(options.targetDir)\r\n : (options.dir ? path.resolve(options.dir, packageName) : path.resolve('.'));\r\n\r\n let targetDir: string;\r\n if (options.targetDir || options.yes) {\r\n targetDir = defaultTargetDir;\r\n } else {\r\n const { targetDir: confirmedTargetDir } = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'targetDir',\r\n message: 'Target directory:',\r\n default: defaultTargetDir,\r\n },\r\n ]);\r\n targetDir = path.resolve(confirmedTargetDir);\r\n }\r\n\r\n // 4. 询问额外信息(命令行已提供则跳过)\r\n const defaults = {\r\n displayName: defaultDisplayName,\r\n description: `A Solazah plugin - ${defaultDisplayName}`,\r\n author: '',\r\n port: 5200,\r\n };\r\n const provided = {\r\n displayName: options.displayName,\r\n description: options.description,\r\n author: options.author,\r\n port: options.port,\r\n };\r\n const remainingPrompts: any[] = [];\r\n if (provided.displayName === undefined && !options.yes) {\r\n remainingPrompts.push({ type: 'input', name: 'displayName', message: 'Display name:', default: defaults.displayName });\r\n }\r\n if (provided.description === undefined && !options.yes) {\r\n remainingPrompts.push({ type: 'input', name: 'description', message: 'Description:', default: defaults.description });\r\n }\r\n if (provided.author === undefined && !options.yes) {\r\n remainingPrompts.push({ type: 'input', name: 'author', message: 'Author:', default: defaults.author });\r\n }\r\n if (provided.port === undefined && !options.yes) {\r\n remainingPrompts.push({ type: 'number', name: 'port', message: 'Development server port:', default: defaults.port });\r\n }\r\n const promptAnswers = remainingPrompts.length > 0 ? await inquirer.prompt(remainingPrompts) : {};\r\n const answers = {\r\n displayName: provided.displayName ?? promptAnswers.displayName ?? defaults.displayName,\r\n description: provided.description ?? promptAnswers.description ?? defaults.description,\r\n author: provided.author ?? promptAnswers.author ?? defaults.author,\r\n port: provided.port ?? promptAnswers.port ?? defaults.port,\r\n };\r\n\r\n // 检查目录是否存在且非空\r\n const dirExists = await fs.pathExists(targetDir);\r\n if (dirExists) {\r\n const isEmpty = await isDirectoryEmpty(targetDir);\r\n if (!isEmpty) {\r\n const { overwrite } = await inquirer.prompt([\r\n {\r\n type: 'confirm',\r\n name: 'overwrite',\r\n message: `Directory ${chalk.cyan(targetDir)} is not empty. Overwrite?`,\r\n default: false,\r\n },\r\n ]);\r\n\r\n if (!overwrite) {\r\n console.log(chalk.yellow('❌ Cancelled'));\r\n process.exit(0);\r\n }\r\n\r\n await fs.remove(targetDir);\r\n }\r\n }\r\n\r\n // 5. 创建目录\r\n await ensureDirectory(targetDir);\r\n\r\n // 6. 复制模板\r\n const spinner = ora('Copying template files...').start();\r\n const templateDir = getTemplateDir(selectedTemplate);\r\n\r\n if (!(await fs.pathExists(templateDir))) {\r\n spinner.fail(chalk.red(`Template directory not found: ${templateDir}`));\r\n process.exit(1);\r\n }\r\n\r\n await copyTemplate(templateDir, targetDir);\r\n spinner.succeed('Template files copied');\r\n\r\n // 7. 更新文件内容\r\n spinner.start('Updating files...');\r\n\r\n // 定义占位符替换映射\r\n const replacements: Record<string, string> = {\r\n PLUGIN_NAME: pluginName!,\r\n PLUGIN_DISPLAY_NAME: answers.displayName,\r\n PLUGIN_DESCRIPTION: answers.description,\r\n PLUGIN_AUTHOR: answers.author || '',\r\n PLUGIN_VERSION: '0.0.1',\r\n DEV_PORT: answers.port.toString(),\r\n PLUGIN_SIMPLE_NAME: simpleName,\r\n PLUGIN_CLASS_NAME: `solazah-${simpleName}`,\r\n };\r\n\r\n // 批量替换目录中的占位符\r\n await replaceInDirectory(targetDir, replacements);\r\n\r\n // 额外处理特定文件(JSON 文件需要特殊处理)\r\n const packageJsonPath = path.join(targetDir, 'package.json');\r\n const packageJson = await fs.readJson(packageJsonPath);\r\n packageJson.name = pluginName;\r\n packageJson.version = '0.0.1';\r\n packageJson.description = answers.description;\r\n if (answers.author) {\r\n packageJson.author = answers.author;\r\n }\r\n packageJson.scripts.dev = `vite --port ${answers.port}`;\r\n await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });\r\n\r\n const manifestPath = path.join(targetDir, 'manifest.json');\r\n const manifest = await fs.readJson(manifestPath);\r\n manifest.name = pluginName;\r\n manifest.displayName = answers.displayName;\r\n manifest.commands = [answers.displayName];\r\n manifest.development.main = `http://localhost:${answers.port}`;\r\n await fs.writeJson(manifestPath, manifest, { spaces: 2 });\r\n\r\n // 删除不需要的文件\r\n const filesToRemove = [\r\n 'template.json',\r\n ];\r\n for (const file of filesToRemove) {\r\n const filePath = path.join(targetDir, file);\r\n if (await fs.pathExists(filePath)) {\r\n await fs.remove(filePath);\r\n }\r\n }\r\n\r\n spinner.succeed('Files updated');\r\n\r\n // 8. 安装依赖\r\n if (!options.skipInstall) {\r\n spinner.start('Installing dependencies...');\r\n try {\r\n await execAsync('npm install', { cwd: targetDir });\r\n spinner.succeed('Dependencies installed');\r\n } catch (error) {\r\n spinner.fail('Failed to install dependencies');\r\n console.log(chalk.yellow('You can install them manually by running: npm install'));\r\n }\r\n }\r\n\r\n // 9. 完成\r\n console.log(chalk.green.bold('\\n✅ Plugin created successfully!\\n'));\r\n console.log(chalk.cyan('Next steps:'));\r\n const relativePath = path.relative(process.cwd(), targetDir);\r\n if (relativePath && relativePath !== '.') {\r\n console.log(chalk.gray(` cd ${relativePath}`));\r\n }\r\n if (options.skipInstall) {\r\n console.log(chalk.gray(' npm install'));\r\n }\r\n console.log(chalk.gray(' npm run dev'));\r\n console.log();\r\n } catch (error) {\r\n console.error(chalk.red('❌ Error:'), error);\r\n process.exit(1);\r\n }\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport { exec } from 'child_process';\r\nimport { promisify } from 'util';\r\n\r\nconst execAsync = promisify(exec);\r\n\r\ninterface PackagePluginOptions {\r\n dir?: string;\r\n}\r\n\r\ntype FileCopyEntry = string | { from: string; to: string };\r\n\r\n/**\r\n * 根据 manifest.json 推断需要复制到 release/ 的文件列表\r\n */\r\nfunction resolveFilesToCopy(projectDir: string, manifest: Record<string, unknown>): FileCopyEntry[] {\r\n const entries: FileCopyEntry[] = [];\r\n\r\n // manifest.json 始终需要\r\n entries.push('manifest.json');\r\n\r\n // 可选文档文件\r\n for (const doc of ['CHANGELOG.md', 'README.md','icon.png']) {\r\n if (fs.existsSync(path.join(projectDir, doc))) {\r\n entries.push(doc);\r\n }\r\n }\r\n\r\n // 根据 manifest.main 推断构建产物目标目录\r\n // e.g. \"renderer/index.html\" → dist -> renderer\r\n // e.g. \"index.html\" → dist -> .\r\n const main = manifest.main as string | undefined;\r\n if (main) {\r\n const mainDir = path.dirname(main);\r\n entries.push({ from: 'dist', to: mainDir === '.' ? '.' : mainDir });\r\n } else {\r\n entries.push({ from: 'dist', to: '.' });\r\n }\r\n\r\n // preload 目录\r\n const preload = manifest.preload as string | undefined;\r\n if (preload) {\r\n const preloadDir = path.dirname(preload); // e.g. \"preload/preload.mjs\" -> \"preload\"\r\n const srcPreload = path.join(projectDir, 'src', 'preload');\r\n if (fs.existsSync(srcPreload)) {\r\n entries.push({ from: 'src/preload', to: preloadDir });\r\n }\r\n }\r\n\r\n // skills 目录\r\n const srcSkills = path.join(projectDir, 'src', 'skills');\r\n if (fs.existsSync(srcSkills)) {\r\n entries.push({ from: 'src/skills', to: 'skills' });\r\n }\r\n\r\n // screenshots 目录\r\n const srcScreenshots = path.join(projectDir, 'src', 'screenshots');\r\n if (fs.existsSync(srcScreenshots)) {\r\n entries.push({ from: 'src/screenshots', to: 'screenshots' });\r\n }\r\n\r\n return entries;\r\n}\r\n\r\n/**\r\n * 清理并初始化 release 目录\r\n */\r\nfunction cleanReleaseDir(releaseDir: string): void {\r\n if (fs.existsSync(releaseDir)) {\r\n fs.rmSync(releaseDir, { recursive: true, force: true });\r\n }\r\n fs.mkdirSync(releaseDir, { recursive: true });\r\n}\r\n\r\n/**\r\n * 按照文件列表复制到 release 目录\r\n */\r\nfunction copyFiles(projectDir: string, releaseDir: string, files: FileCopyEntry[]): void {\r\n for (const file of files) {\r\n if (typeof file === 'string') {\r\n const src = path.resolve(projectDir, file);\r\n const dest = path.resolve(releaseDir, file);\r\n fs.cpSync(src, dest, { recursive: true });\r\n } else {\r\n const src = path.resolve(projectDir, file.from);\r\n const dest = path.resolve(releaseDir, file.to);\r\n fs.cpSync(src, dest, { recursive: true });\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 精简 package.json,只保留发布需要的字段\r\n */\r\nfunction processPackageJson(projectDir: string, releaseDir: string): void {\r\n const raw = fs.readFileSync(path.resolve(projectDir, 'package.json'), 'utf-8');\r\n const pkg = JSON.parse(raw);\r\n\r\n const cleaned: Record<string, unknown> = {\r\n name: pkg.name,\r\n main: pkg.main,\r\n type: pkg.type,\r\n version: pkg.version,\r\n description: pkg.description,\r\n author: pkg.author,\r\n license: pkg.license,\r\n homepage: pkg.homepage,\r\n repository: pkg.repository,\r\n keywords: pkg.keywords,\r\n peerDependencies: pkg.peerDependencies,\r\n };\r\n\r\n fs.writeFileSync(\r\n path.resolve(releaseDir, 'package.json'),\r\n JSON.stringify(cleaned, null, 2),\r\n );\r\n}\r\n\r\n/**\r\n * 清理构建产物临时目录 (dist/)\r\n * Windows 上 cpSync 之后文件句柄可能尚未完全释放,rmSync 会报 ENOTEMPTY。\r\n * 加重试逻辑,等待系统释放句柄后再删除。\r\n */\r\nfunction cleanDistDir(projectDir: string): void {\r\n const distDir = path.join(projectDir, 'dist');\r\n if (!fs.existsSync(distDir)) return;\r\n\r\n const maxRetries = 5;\r\n const retryDelayMs = 200;\r\n\r\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\r\n try {\r\n fs.rmSync(distDir, { recursive: true, force: true });\r\n return;\r\n } catch (err: any) {\r\n if (attempt === maxRetries) throw err;\r\n // 同步等待后重试(仅用于磁盘 I/O 竞态,不阻塞事件循环中的业务逻辑)\r\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, retryDelayMs);\r\n }\r\n }\r\n}\r\n\r\nexport async function packagePluginCommand(options: PackagePluginOptions): Promise<void> {\r\n const projectDir = path.resolve(options.dir || '.');\r\n\r\n console.log(chalk.cyan.bold('\\n📦 Solazah Plugin Packager\\n'));\r\n\r\n // 1. 验证项目目录\r\n const packageJsonPath = path.join(projectDir, 'package.json');\r\n if (!(await fs.pathExists(packageJsonPath))) {\r\n console.error(chalk.red('❌ No package.json found in the target directory.'));\r\n process.exit(1);\r\n }\r\n\r\n const manifestPath = path.join(projectDir, 'manifest.json');\r\n if (!(await fs.pathExists(manifestPath))) {\r\n console.error(chalk.red('❌ No manifest.json found. Is this a Solazah plugin project?'));\r\n process.exit(1);\r\n }\r\n\r\n const packageData = await fs.readJson(packageJsonPath);\r\n const manifest = await fs.readJson(manifestPath);\r\n console.log(chalk.gray(`Plugin: ${packageData.name || 'unknown'} v${packageData.version || '0.0.0'}\\n`));\r\n\r\n // 2. 构建项目\r\n const buildSpinner = ora('Building plugin...').start();\r\n try {\r\n await execAsync('npm run build', { cwd: projectDir });\r\n buildSpinner.succeed('Build completed');\r\n } catch (error: any) {\r\n buildSpinner.fail('Build failed');\r\n console.error(chalk.red(error.stderr || error.message));\r\n process.exit(1);\r\n }\r\n\r\n // 3. 打包到 release/\r\n const packageSpinner = ora('Packaging plugin...').start();\r\n try {\r\n const releaseDir = path.join(projectDir, 'release');\r\n const filesToCopy = resolveFilesToCopy(projectDir, manifest);\r\n\r\n cleanReleaseDir(releaseDir);\r\n copyFiles(projectDir, releaseDir, filesToCopy);\r\n processPackageJson(projectDir, releaseDir);\r\n cleanDistDir(projectDir);\r\n\r\n packageSpinner.succeed('Package created in release/');\r\n } catch (error: any) {\r\n packageSpinner.fail('Packaging failed');\r\n console.error(chalk.red(error.message));\r\n process.exit(1);\r\n }\r\n\r\n // 4. 生成 .tgz 文件到 release/\r\n const tgzSpinner = ora('Creating .tgz archive...').start();\r\n try {\r\n const { stdout } = await execAsync('npm pack ./release --pack-destination ./release', { cwd: projectDir });\r\n const tgzFile = stdout.trim();\r\n tgzSpinner.succeed(`Archive created: ${chalk.cyan('release/' + tgzFile)}`);\r\n } catch (error: any) {\r\n tgzSpinner.fail('Failed to create .tgz archive');\r\n console.error(chalk.red(error.stderr || error.message));\r\n process.exit(1);\r\n }\r\n\r\n console.log(chalk.green.bold('\\n✅ Plugin packaged successfully!\\n'));\r\n}\r\n","import path from 'path';\r\nimport os from 'os';\r\nimport fs from 'fs-extra';\r\n\r\nexport interface StoredTokens {\r\n access_token: string;\r\n refresh_token?: string;\r\n id_token?: string;\r\n token_type: string;\r\n scope?: string;\r\n expires_at?: number; // epoch seconds\r\n issuer?: string;\r\n}\r\n\r\nexport interface TokenResponse {\r\n access_token: string;\r\n refresh_token?: string;\r\n id_token?: string;\r\n token_type: string;\r\n expires_in?: number;\r\n scope?: string;\r\n}\r\n\r\nconst AUTH_DIR = path.join(os.homedir(), '.solazah');\r\nconst AUTH_FILE = path.join(AUTH_DIR, 'auth.json');\r\nconst EXPIRY_SKEW_SECONDS = 30;\r\n\r\nexport async function saveTokens(tokens: StoredTokens): Promise<void> {\r\n await fs.ensureDir(AUTH_DIR);\r\n await fs.writeJson(AUTH_FILE, tokens, { spaces: 2 });\r\n try {\r\n await fs.chmod(AUTH_FILE, 0o600);\r\n } catch {\r\n // ignore chmod errors on Windows\r\n }\r\n}\r\n\r\nexport async function loadTokens(): Promise<StoredTokens | null> {\r\n try {\r\n return (await fs.readJson(AUTH_FILE)) as StoredTokens;\r\n } catch (err) {\r\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\r\n return null;\r\n }\r\n throw err;\r\n }\r\n}\r\n\r\nexport async function clearTokens(): Promise<void> {\r\n try {\r\n await fs.remove(AUTH_FILE);\r\n } catch {\r\n // ignore\r\n }\r\n}\r\n\r\nexport function isExpired(tokens: StoredTokens): boolean {\r\n if (!tokens.expires_at) return false;\r\n return Math.floor(Date.now() / 1000) >= tokens.expires_at - EXPIRY_SKEW_SECONDS;\r\n}\r\n\r\nexport function toStoredTokens(tokens: TokenResponse, issuer: string): StoredTokens {\r\n const now = Math.floor(Date.now() / 1000);\r\n return {\r\n access_token: tokens.access_token,\r\n refresh_token: tokens.refresh_token,\r\n id_token: tokens.id_token,\r\n token_type: tokens.token_type,\r\n scope: tokens.scope,\r\n expires_at: tokens.expires_in ? now + tokens.expires_in : undefined,\r\n issuer,\r\n };\r\n}\r\n\r\n/**\r\n * 合并刷新后的令牌:若服务器未返回新的 refresh_token 则保留原值。\r\n */\r\nexport function mergeRefreshedTokens(\r\n previous: StoredTokens,\r\n refreshed: TokenResponse,\r\n issuer: string,\r\n): StoredTokens {\r\n const next = toStoredTokens(refreshed, issuer);\r\n if (!next.refresh_token) {\r\n next.refresh_token = previous.refresh_token;\r\n }\r\n return next;\r\n}\r\n","import { TokenResponse } from './auth-storage.js';\r\n\r\nexport const DEFAULT_ISSUER = 'https://solazah.seayona.com/account';\r\nexport const DEFAULT_CLIENT_ID = 'solazah-cli';\r\nexport const DEFAULT_CLIENT_SECRET = 'solazah-cli';\r\nexport const DEFAULT_SCOPE = 'openid profile offline_access';\r\nexport const DEFAULT_TOKEN_TYPE = 'Bearer';\r\n\r\n/**\r\n * 授权服务器默认把 verification_uri 指向后端的表单端点\r\n * (/account/oauth2/device_verification),但我们的前端是 SPA,\r\n * 直接让用户访问后端表单会看到空白页。这里统一重写为 SPA 的\r\n * /#/device hash 路由,由前端引导用户登录并提交 user_code。\r\n */\r\nexport const DEFAULT_VERIFICATION_URI = 'https://solazah.seayona.com/account/#/device';\r\n\r\nexport const GRANT_TYPE = {\r\n DEVICE_CODE: 'urn:ietf:params:oauth:grant-type:device_code',\r\n REFRESH_TOKEN: 'refresh_token',\r\n} as const;\r\n\r\nexport const TOKEN_HINT = {\r\n ACCESS: 'access_token',\r\n REFRESH: 'refresh_token',\r\n} as const;\r\nexport type TokenTypeHint = typeof TOKEN_HINT[keyof typeof TOKEN_HINT];\r\n\r\nexport const DEVICE_ERROR = {\r\n AUTH_PENDING: 'authorization_pending',\r\n SLOW_DOWN: 'slow_down',\r\n ACCESS_DENIED: 'access_denied',\r\n EXPIRED_TOKEN: 'expired_token',\r\n} as const;\r\n\r\nexport interface OidcDiscovery {\r\n issuer: string;\r\n device_authorization_endpoint?: string;\r\n token_endpoint: string;\r\n userinfo_endpoint?: string;\r\n end_session_endpoint?: string;\r\n revocation_endpoint?: string;\r\n}\r\n\r\nexport interface DeviceAuthorizationResponse {\r\n device_code: string;\r\n user_code: string;\r\n verification_uri: string;\r\n verification_uri_complete?: string;\r\n expires_in: number;\r\n interval?: number;\r\n}\r\n\r\nexport interface UserInfo {\r\n sub: string;\r\n name?: string;\r\n username?: string;\r\n preferred_username?: string;\r\n email?: string;\r\n [key: string]: unknown;\r\n}\r\n\r\nexport async function discover(issuer: string = DEFAULT_ISSUER): Promise<OidcDiscovery> {\r\n const url = `${issuer.replace(/\\/+$/, '')}/.well-known/openid-configuration`;\r\n const res = await fetch(url);\r\n if (!res.ok) {\r\n throw new Error(`Failed to load OIDC discovery (${res.status}): ${url}`);\r\n }\r\n return (await res.json()) as OidcDiscovery;\r\n}\r\n\r\n/**\r\n * OAuth 端点 POST:application/x-www-form-urlencoded;\r\n * 使用 client_secret_post 方式认证,client_id 和 client_secret 放在表单中。\r\n */\r\nasync function postForm(\r\n url: string,\r\n params: Record<string, string>,\r\n): Promise<{ ok: boolean; status: number; data: Record<string, unknown> }> {\r\n const body = new URLSearchParams(params);\r\n const res = await fetch(url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded',\r\n Accept: 'application/json',\r\n },\r\n body: body.toString(),\r\n redirect: 'manual',\r\n });\r\n\r\n // 服务器返回 3xx 重定向通常意味着客户端认证失败或端点配置错误\r\n if (res.status >= 300 && res.status < 400) {\r\n const location = res.headers.get('location') ?? '(unknown)';\r\n throw new Error(\r\n `服务器返回了重定向 (${res.status}) → ${location},` +\r\n `请确认客户端 client_id 已在授权服务器注册为公共客户端`,\r\n );\r\n }\r\n\r\n const text = await res.text();\r\n let data: Record<string, unknown>;\r\n try {\r\n data = JSON.parse(text) as Record<string, unknown>;\r\n } catch {\r\n throw new Error(\r\n `服务器返回了非 JSON 响应 (${res.status}): ${text.slice(0, 200)}`,\r\n );\r\n }\r\n return { ok: res.ok, status: res.status, data };\r\n}\r\n\r\n/** 安全提取 string */\r\nfunction str(v: unknown): string | undefined {\r\n return typeof v === 'string' ? v : undefined;\r\n}\r\n/** 安全提取 number */\r\nfunction num(v: unknown): number | undefined {\r\n return typeof v === 'number' ? v : undefined;\r\n}\r\n\r\nexport async function requestDeviceCode(options: {\r\n discovery: OidcDiscovery;\r\n clientId?: string;\r\n scope?: string;\r\n}): Promise<DeviceAuthorizationResponse> {\r\n const { discovery } = options;\r\n if (!discovery.device_authorization_endpoint) {\r\n throw new Error('授权服务器未提供 device_authorization_endpoint');\r\n }\r\n const { ok, status, data } = await postForm(discovery.device_authorization_endpoint, {\r\n client_id: options.clientId ?? DEFAULT_CLIENT_ID,\r\n client_secret: DEFAULT_CLIENT_SECRET,\r\n scope: options.scope ?? DEFAULT_SCOPE,\r\n });\r\n if (!ok) {\r\n throw new Error(`设备授权请求失败 (${status}): ${JSON.stringify(data)}`);\r\n }\r\n\r\n // 兼容 snake_case(RFC 8628 标准)和 camelCase 两种响应格式\r\n const deviceCode = str(data.device_code) ?? str(data.deviceCode);\r\n const userCode = str(data.user_code) ?? str(data.userCode);\r\n const expiresIn = num(data.expires_in) ?? num(data.expiresIn);\r\n const interval = num(data.interval);\r\n\r\n if (!deviceCode || !userCode || expiresIn == null) {\r\n throw new Error(\r\n `设备授权响应缺少必要字段(device_code/user_code/expires_in),` +\r\n `实际响应:${JSON.stringify(data)}`,\r\n );\r\n }\r\n\r\n // 授权服务器默认返回后端表单端点作为 verification_uri,\r\n // 这里覆写为 SPA 地址,保证用户打开后能看到前端页面。\r\n const verificationUri = DEFAULT_VERIFICATION_URI;\r\n const verificationUriComplete = `${verificationUri}?user_code=${encodeURIComponent(userCode)}`;\r\n\r\n return {\r\n device_code: deviceCode,\r\n user_code: userCode,\r\n verification_uri: verificationUri,\r\n verification_uri_complete: verificationUriComplete,\r\n expires_in: expiresIn,\r\n interval,\r\n };\r\n}\r\n\r\nexport type PollStatus = 'success' | 'pending' | 'slow_down' | 'denied' | 'expired' | 'error';\r\n\r\nexport interface PollResult {\r\n status: PollStatus;\r\n tokens?: TokenResponse;\r\n error?: string;\r\n errorDescription?: string;\r\n}\r\n\r\nexport async function pollToken(options: {\r\n discovery: OidcDiscovery;\r\n deviceCode: string;\r\n clientId?: string;\r\n}): Promise<PollResult> {\r\n const { ok, data } = await postForm(options.discovery.token_endpoint, {\r\n grant_type: GRANT_TYPE.DEVICE_CODE,\r\n device_code: options.deviceCode,\r\n client_id: options.clientId ?? DEFAULT_CLIENT_ID,\r\n client_secret: DEFAULT_CLIENT_SECRET,\r\n });\r\n\r\n if (ok && typeof data.access_token === 'string') {\r\n return { status: 'success', tokens: tokenResponseFrom(data) };\r\n }\r\n\r\n const err = typeof data.error === 'string' ? data.error : 'error';\r\n const errorDescription =\r\n typeof data.error_description === 'string' ? data.error_description : undefined;\r\n const status: PollStatus =\r\n err === DEVICE_ERROR.AUTH_PENDING ? 'pending'\r\n : err === DEVICE_ERROR.SLOW_DOWN ? 'slow_down'\r\n : err === DEVICE_ERROR.ACCESS_DENIED ? 'denied'\r\n : err === DEVICE_ERROR.EXPIRED_TOKEN ? 'expired'\r\n : 'error';\r\n return { status, error: err, errorDescription };\r\n}\r\n\r\nexport async function fetchUserInfo(options: {\r\n discovery: OidcDiscovery;\r\n accessToken: string;\r\n}): Promise<UserInfo> {\r\n if (!options.discovery.userinfo_endpoint) {\r\n throw new Error('授权服务器未提供 userinfo_endpoint');\r\n }\r\n const res = await fetch(options.discovery.userinfo_endpoint, {\r\n headers: {\r\n Authorization: `Bearer ${options.accessToken}`,\r\n Accept: 'application/json',\r\n },\r\n });\r\n if (!res.ok) {\r\n const text = await res.text();\r\n throw new Error(`获取用户信息失败 (${res.status}): ${text}`);\r\n }\r\n return (await res.json()) as UserInfo;\r\n}\r\n\r\nexport async function refreshTokens(options: {\r\n discovery: OidcDiscovery;\r\n refreshToken: string;\r\n clientId?: string;\r\n}): Promise<TokenResponse> {\r\n const { ok, status, data } = await postForm(options.discovery.token_endpoint, {\r\n grant_type: GRANT_TYPE.REFRESH_TOKEN,\r\n refresh_token: options.refreshToken,\r\n client_id: options.clientId ?? DEFAULT_CLIENT_ID,\r\n client_secret: DEFAULT_CLIENT_SECRET,\r\n });\r\n if (!ok) {\r\n throw new Error(`刷新令牌失败 (${status}): ${JSON.stringify(data)}`);\r\n }\r\n return tokenResponseFrom(data);\r\n}\r\n\r\n/**\r\n * 撤销指定类型的令牌;best-effort,出错不抛出。\r\n */\r\nexport async function revokeToken(options: {\r\n discovery: OidcDiscovery;\r\n token: string;\r\n tokenTypeHint?: TokenTypeHint;\r\n clientId?: string;\r\n}): Promise<void> {\r\n if (!options.discovery.revocation_endpoint) {\r\n return;\r\n }\r\n const params: Record<string, string> = {\r\n token: options.token,\r\n client_id: options.clientId ?? DEFAULT_CLIENT_ID,\r\n client_secret: DEFAULT_CLIENT_SECRET,\r\n };\r\n if (options.tokenTypeHint) {\r\n params.token_type_hint = options.tokenTypeHint;\r\n }\r\n await postForm(options.discovery.revocation_endpoint, params).catch(() => {\r\n // best-effort\r\n });\r\n}\r\n\r\n/**\r\n * 并发撤销 access_token 与 refresh_token。\r\n */\r\nexport async function revokeAll(options: {\r\n discovery: OidcDiscovery;\r\n accessToken?: string;\r\n refreshToken?: string;\r\n clientId?: string;\r\n}): Promise<void> {\r\n if (!options.discovery.revocation_endpoint) return;\r\n const jobs: Promise<void>[] = [];\r\n if (options.refreshToken) {\r\n jobs.push(revokeToken({\r\n discovery: options.discovery,\r\n token: options.refreshToken,\r\n tokenTypeHint: TOKEN_HINT.REFRESH,\r\n clientId: options.clientId,\r\n }));\r\n }\r\n if (options.accessToken) {\r\n jobs.push(revokeToken({\r\n discovery: options.discovery,\r\n token: options.accessToken,\r\n tokenTypeHint: TOKEN_HINT.ACCESS,\r\n clientId: options.clientId,\r\n }));\r\n }\r\n await Promise.all(jobs);\r\n}\r\n\r\nfunction tokenResponseFrom(data: Record<string, unknown>): TokenResponse {\r\n return {\r\n access_token: String(data.access_token ?? ''),\r\n refresh_token: typeof data.refresh_token === 'string' ? data.refresh_token : undefined,\r\n id_token: typeof data.id_token === 'string' ? data.id_token : undefined,\r\n token_type: typeof data.token_type === 'string' ? data.token_type : DEFAULT_TOKEN_TYPE,\r\n expires_in: typeof data.expires_in === 'number' ? data.expires_in : undefined,\r\n scope: typeof data.scope === 'string' ? data.scope : undefined,\r\n };\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport {\r\n loadTokens,\r\n isExpired,\r\n mergeRefreshedTokens,\r\n saveTokens,\r\n StoredTokens,\r\n} from '../../utils/auth-storage.js';\r\nimport { discover, refreshTokens } from '../../utils/oauth-device.js';\r\n\r\nconst MARKETPLACE_BASE_URL = 'https://solazah.seayona.com/marketplace';\r\n\r\ninterface PublishOptions {\r\n file?: string;\r\n dir?: string;\r\n}\r\n\r\nasync function ensureAuth(): Promise<StoredTokens> {\r\n const tokens = await loadTokens();\r\n if (!tokens) {\r\n console.log(chalk.red('当前未登录,请先执行:solazah-cli account login'));\r\n process.exit(1);\r\n }\r\n\r\n if (isExpired(tokens) && tokens.refresh_token) {\r\n const spinner = ora('令牌已过期,正在刷新...').start();\r\n try {\r\n const discovery = await discover(tokens.issuer);\r\n const refreshed = await refreshTokens({ discovery, refreshToken: tokens.refresh_token });\r\n const updated = mergeRefreshedTokens(tokens, refreshed, discovery.issuer);\r\n await saveTokens(updated);\r\n spinner.succeed('令牌刷新成功');\r\n return updated;\r\n } catch {\r\n spinner.fail('令牌刷新失败,请重新登录:solazah-cli account login');\r\n process.exit(1);\r\n }\r\n }\r\n\r\n return tokens;\r\n}\r\n\r\n/**\r\n * 根据 manifest.json 的 name 和 version 推算 release/ 中的 .tgz 文件路径\r\n * npm pack 生成的文件名规则:@scope/pkg-name -> scope-pkg-name-version.tgz\r\n */\r\nfunction findTgzByManifest(projectDir: string): string {\r\n const manifestPath = path.join(projectDir, 'manifest.json');\r\n if (!fs.existsSync(manifestPath)) {\r\n console.log(chalk.red('❌ 未找到 manifest.json,无法确定插件包文件名'));\r\n process.exit(1);\r\n }\r\n\r\n const manifest = fs.readJsonSync(manifestPath);\r\n const name: string = manifest.name;\r\n const version: string = manifest.version;\r\n\r\n if (!name || !version) {\r\n console.log(chalk.red('❌ manifest.json 中缺少 name 或 version 字段'));\r\n process.exit(1);\r\n }\r\n\r\n // @scope/pkg-name -> scope-pkg-name, pkg-name -> pkg-name\r\n const tgzName = name.replace(/^@/, '').replace(/\\//, '-');\r\n const tgzFile = `${tgzName}-${version}.tgz`;\r\n\r\n return path.join(projectDir, 'release', tgzFile);\r\n}\r\n\r\nexport async function publishPluginCommand(options: PublishOptions): Promise<void> {\r\n let filePath: string;\r\n\r\n if (options.file) {\r\n filePath = path.resolve(options.file);\r\n } else {\r\n const projectDir = path.resolve(options.dir || '.');\r\n filePath = findTgzByManifest(projectDir);\r\n }\r\n\r\n // 检查文件是否存在\r\n if (!await fs.pathExists(filePath)) {\r\n console.log(chalk.red(`文件不存在:${filePath}`));\r\n process.exit(1);\r\n }\r\n\r\n // 检查文件扩展名\r\n if (!filePath.endsWith('.tgz')) {\r\n console.log(chalk.red('仅支持 .tgz 格式的插件包'));\r\n process.exit(1);\r\n }\r\n\r\n // 检查登录状态 & 刷新令牌\r\n const tokens = await ensureAuth();\r\n\r\n const fileName = path.basename(filePath);\r\n const spinner = ora(`正在发布插件包:${fileName}`).start();\r\n\r\n try {\r\n const fileBuffer = await fs.readFile(filePath);\r\n const blob = new Blob([fileBuffer], { type: 'application/gzip' });\r\n\r\n const formData = new FormData();\r\n formData.append('package', blob, fileName);\r\n\r\n const res = await fetch(`${MARKETPLACE_BASE_URL}/api/v1/publish-requests`, {\r\n method: 'POST',\r\n headers: {\r\n Authorization: `Bearer ${tokens.access_token}`,\r\n },\r\n body: formData,\r\n });\r\n\r\n const data = await res.json().catch(() => ({}));\r\n\r\n if (!res.ok) {\r\n const code = (data as Record<string, unknown>).code ?? '';\r\n const message = (data as Record<string, unknown>).message ?? res.statusText;\r\n if (code === 'DUPLICATE_SUBMISSION') {\r\n spinner.fail('发布失败:该版本已提交过发布申请,请勿重复提交');\r\n } else {\r\n spinner.fail(`发布失败 (${res.status}): ${message}`);\r\n }\r\n process.exit(1);\r\n }\r\n\r\n const result = data as Record<string, unknown>;\r\n spinner.succeed('插件发布申请已提交');\r\n console.log('');\r\n console.log(chalk.green(' 申请详情:'));\r\n console.log(` 申请ID: ${result.pluginPublishRequestId}`);\r\n console.log(` 插件名称: ${result.pluginName}`);\r\n console.log(` 状态: ${result.status}`);\r\n console.log('');\r\n console.log(chalk.gray(' 发布申请已提交,等待管理员审核。'));\r\n } catch (err) {\r\n spinner.fail(`发布失败:${(err as Error).message}`);\r\n process.exit(1);\r\n }\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport semver from 'semver';\r\nimport { promisify } from 'util';\r\nimport { exec } from 'child_process';\r\nimport conventionalChangelog from 'conventional-changelog-core';\r\n\r\nconst execAsync = promisify(exec);\r\n\r\ninterface VersionOptions {\r\n dir?: string;\r\n releaseAs?: string;\r\n}\r\n\r\n// 读取 .versionrc(如果存在),提取自定义 commit type → section 映射\r\nfunction loadVersionrc(projectDir: string): Record<string, unknown> {\r\n for (const name of ['.versionrc', '.versionrc.json']) {\r\n const p = path.join(projectDir, name);\r\n if (fs.existsSync(p)) {\r\n try {\r\n return fs.readJsonSync(p);\r\n } catch {\r\n // ignore malformed versionrc\r\n }\r\n }\r\n }\r\n return {};\r\n}\r\n\r\n// 把 .versionrc 中的 types 数组转成 conventional-changelog-core 需要的 parserOpts / writerOpts\r\nfunction buildChangelogConfig(versionrc: Record<string, unknown>) {\r\n const types = versionrc.types as Array<{ type: string; section?: string; hidden?: boolean }> | undefined;\r\n if (!types) return {};\r\n\r\n const parserOpts = {\r\n noteKeywords: ['BREAKING CHANGE', 'BREAKING-CHANGE'],\r\n };\r\n\r\n const writerOpts = {\r\n transform: (commit: Record<string, unknown>) => {\r\n const match = types.find((t) => t.type === commit.type);\r\n if (!match) return false;\r\n if (match.hidden) return false;\r\n commit.type = match.section ?? commit.type;\r\n return commit;\r\n },\r\n groupBy: 'type',\r\n commitGroupsSort: 'title',\r\n commitsSort: ['scope', 'subject'],\r\n };\r\n\r\n return { parserOpts, writerOpts };\r\n}\r\n\r\n// 生成本次发布的 changelog 片段\r\nasync function generateChangelogEntry(projectDir: string, newVersion: string, versionrc: Record<string, unknown>): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n const { parserOpts, writerOpts } = buildChangelogConfig(versionrc);\r\n const header = (versionrc.header as string | undefined) ?? '# CHANGELOG\\n\\n';\r\n\r\n const stream = conventionalChangelog(\r\n { preset: 'angular', parserOpts, writerOpts } as Parameters<typeof conventionalChangelog>[0],\r\n { version: newVersion } as Parameters<typeof conventionalChangelog>[1],\r\n undefined,\r\n undefined,\r\n undefined,\r\n );\r\n\r\n let content = '';\r\n stream.on('data', (chunk: Buffer | string) => { content += chunk.toString(); });\r\n stream.on('end', () => resolve(content));\r\n stream.on('error', reject);\r\n\r\n // cwd 必须在插件项目目录,conventional-changelog 默认从当前目录读 git log\r\n (stream as any).cwd = projectDir;\r\n });\r\n}\r\n\r\n// 更新文件中的 version 字段(package.json / manifest.json)\r\nfunction bumpVersionInFile(filePath: string, newVersion: string): void {\r\n if (!fs.existsSync(filePath)) return;\r\n const content = fs.readFileSync(filePath, 'utf-8');\r\n // 只替换顶层 \"version\" 字段,避免误改嵌套结构\r\n const updated = content.replace(\r\n /^(\\s*\"version\"\\s*:\\s*)\"[^\"]*\"/m,\r\n `$1\"${newVersion}\"`,\r\n );\r\n fs.writeFileSync(filePath, updated, 'utf-8');\r\n}\r\n\r\n// 追加 changelog 片段到 CHANGELOG.md(在现有内容之前)\r\nfunction prependChangelog(projectDir: string, entry: string, header: string): void {\r\n const changelogPath = path.join(projectDir, 'CHANGELOG.md');\r\n const existing = fs.existsSync(changelogPath) ? fs.readFileSync(changelogPath, 'utf-8') : '';\r\n // 去掉已有的 header 行,再重新写\r\n const body = existing.startsWith(header.trim())\r\n ? existing.slice(header.trim().length).trimStart()\r\n : existing;\r\n fs.writeFileSync(changelogPath, header.trim() + '\\n\\n' + entry + (body ? '\\n' + body : ''), 'utf-8');\r\n}\r\n\r\nexport async function versionPluginCommand(options: VersionOptions): Promise<void> {\r\n const projectDir = path.resolve(options.dir || '.');\r\n\r\n const packageJsonPath = path.join(projectDir, 'package.json');\r\n const manifestPath = path.join(projectDir, 'manifest.json');\r\n\r\n if (!(await fs.pathExists(packageJsonPath))) {\r\n console.error(chalk.red('❌ No package.json found.'));\r\n process.exit(1);\r\n }\r\n\r\n const pkg = await fs.readJson(packageJsonPath);\r\n const currentVersion: string = pkg.version ?? '0.0.0';\r\n\r\n // 1. 确定 release type\r\n let releaseType = options.releaseAs || 'patch';\r\n\r\n // 2. 计算新版本号\r\n const newVersion = semver.inc(currentVersion, releaseType as semver.ReleaseType);\r\n if (!newVersion) {\r\n console.error(chalk.red(`❌ Invalid version: ${currentVersion} + ${releaseType}`));\r\n process.exit(1);\r\n }\r\n\r\n const versionrc = loadVersionrc(projectDir);\r\n const header = (versionrc.header as string | undefined) ?? '# CHANGELOG\\n\\n';\r\n\r\n console.log(chalk.gray(`\\n ${currentVersion} → ${chalk.green(newVersion)}\\n`));\r\n\r\n // 3. 生成 changelog 片段\r\n const changelogSpinner = ora('Generating changelog...').start();\r\n let changelogEntry = '';\r\n try {\r\n const prevCwd = process.cwd();\r\n process.chdir(projectDir);\r\n changelogEntry = await generateChangelogEntry(projectDir, newVersion, versionrc);\r\n process.chdir(prevCwd);\r\n changelogSpinner.succeed('Changelog generated');\r\n } catch (err: any) {\r\n changelogSpinner.warn(`Changelog generation skipped: ${err.message}`);\r\n }\r\n\r\n // 4. 更新版本号\r\n const bumpSpinner = ora('Bumping version files...').start();\r\n bumpVersionInFile(packageJsonPath, newVersion);\r\n bumpVersionInFile(path.join(projectDir, 'package-lock.json'), newVersion);\r\n bumpVersionInFile(manifestPath, newVersion);\r\n bumpSpinner.succeed('Version files updated');\r\n\r\n // 5. 写入 CHANGELOG.md\r\n if (changelogEntry.trim()) {\r\n prependChangelog(projectDir, changelogEntry.trim(), header);\r\n ora('').succeed('CHANGELOG.md updated');\r\n }\r\n\r\n // 6. git commit + tag\r\n const gitSpinner = ora('Creating git commit and tag...').start();\r\n try {\r\n const filesToStage = ['package.json', 'manifest.json', 'CHANGELOG.md', 'package-lock.json']\r\n .filter((f) => fs.existsSync(path.join(projectDir, f)));\r\n\r\n await execAsync(`git add ${filesToStage.join(' ')}`, { cwd: projectDir });\r\n await execAsync(`git commit -m \"chore(release): ${newVersion}\"`, { cwd: projectDir });\r\n\r\n // 尝试打 tag,已存在则跳过\r\n try {\r\n await execAsync(`git tag -a v${newVersion} -m \"chore(release): ${newVersion}\"`, { cwd: projectDir });\r\n } catch (tagErr: any) {\r\n if (!tagErr.message?.includes('already exists')) throw tagErr;\r\n gitSpinner.warn(`Tag v${newVersion} already exists, skipping`);\r\n return;\r\n }\r\n\r\n gitSpinner.succeed(`Tagged release v${newVersion}`);\r\n } catch (err: any) {\r\n gitSpinner.fail(`Git operations failed: ${err.stderr || err.message}`);\r\n process.exit(1);\r\n }\r\n\r\n console.log(chalk.green.bold(`\\n✅ Version bumped to ${newVersion}\\n`));\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport chalk from 'chalk';\r\nimport { versionPluginCommand } from './version.js';\r\nimport { packagePluginCommand } from './package.js';\r\nimport { publishPluginCommand } from './publish.js';\r\n\r\ninterface ReleaseOptions {\r\n dir?: string;\r\n}\r\n\r\nexport async function releasePluginCommand(options: ReleaseOptions): Promise<void> {\r\n const projectDir = path.resolve(options.dir || '.');\r\n\r\n if (!(await fs.pathExists(path.join(projectDir, 'package.json')))) {\r\n console.error(chalk.red('❌ No package.json found in the target directory.'));\r\n process.exit(1);\r\n }\r\n\r\n // 1. 版本升级\r\n await versionPluginCommand({ dir: projectDir });\r\n\r\n // 2. 打包\r\n await packagePluginCommand({ dir: projectDir });\r\n\r\n // 3. 发布\r\n await publishPluginCommand({ dir: projectDir });\r\n}\r\n","import { spawn } from 'child_process';\r\n\r\n/**\r\n * 跨平台打开浏览器,best-effort:失败时静默返回 false,由调用方提示用户手动打开。\r\n */\r\nexport function openBrowser(url: string): boolean {\r\n try {\r\n const platform = process.platform;\r\n let command: string;\r\n let args: string[];\r\n\r\n if (platform === 'win32') {\r\n command = 'cmd';\r\n args = ['/c', 'start', '\"\"', url.replace(/&/g, '^&')];\r\n } else if (platform === 'darwin') {\r\n command = 'open';\r\n args = [url];\r\n } else {\r\n command = 'xdg-open';\r\n args = [url];\r\n }\r\n\r\n const child = spawn(command, args, {\r\n detached: true,\r\n stdio: 'ignore',\r\n shell: false,\r\n });\r\n child.on('error', () => {\r\n // ignore; caller already printed the URL\r\n });\r\n child.unref();\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n","import {\r\n isExpired,\r\n mergeRefreshedTokens,\r\n saveTokens,\r\n StoredTokens,\r\n} from '../../utils/auth-storage.js';\r\nimport {\r\n DEFAULT_ISSUER,\r\n discover,\r\n OidcDiscovery,\r\n refreshTokens,\r\n} from '../../utils/oauth-device.js';\r\n\r\nexport interface CommonOptions {\r\n issuer?: string;\r\n}\r\n\r\nexport const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\r\n\r\nexport function loadDiscovery(issuer?: string): Promise<OidcDiscovery> {\r\n return discover(issuer || DEFAULT_ISSUER);\r\n}\r\n\r\nexport async function ensureValidTokens(\r\n discovery: OidcDiscovery,\r\n tokens: StoredTokens,\r\n): Promise<StoredTokens> {\r\n if (!isExpired(tokens)) {\r\n return tokens;\r\n }\r\n if (!tokens.refresh_token) {\r\n throw new Error('访问令牌已过期且无 refresh_token,请重新登录');\r\n }\r\n const refreshed = await refreshTokens({\r\n discovery,\r\n refreshToken: tokens.refresh_token,\r\n });\r\n const stored = mergeRefreshedTokens(tokens, refreshed, discovery.issuer);\r\n await saveTokens(stored);\r\n return stored;\r\n}\r\n","import chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport {\r\n saveTokens,\r\n toStoredTokens,\r\n} from '../../utils/auth-storage.js';\r\nimport {\r\n fetchUserInfo,\r\n pollToken,\r\n requestDeviceCode,\r\n} from '../../utils/oauth-device.js';\r\nimport { openBrowser } from '../../utils/open-browser.js';\r\nimport { CommonOptions, delay, loadDiscovery } from './shared.js';\r\n\r\nexport async function loginCommand(options: CommonOptions): Promise<void> {\r\n console.log(chalk.cyan.bold('\\nSolazah 账户登录\\n'));\r\n\r\n const spinner = ora('加载授权服务器元数据...').start();\r\n let discovery;\r\n try {\r\n discovery = await loadDiscovery(options.issuer);\r\n spinner.succeed('已加载授权服务器元数据');\r\n } catch (err) {\r\n spinner.fail('加载授权服务器元数据失败');\r\n console.error(chalk.red((err as Error).message));\r\n process.exit(1);\r\n }\r\n\r\n spinner.start('申请设备授权码...');\r\n let device;\r\n try {\r\n device = await requestDeviceCode({ discovery });\r\n spinner.succeed('已获取设备授权码');\r\n } catch (err) {\r\n spinner.fail('申请设备授权码失败');\r\n console.error(chalk.red((err as Error).message));\r\n process.exit(1);\r\n }\r\n\r\n const verificationUri = device.verification_uri_complete || device.verification_uri;\r\n\r\n console.log();\r\n console.log(chalk.bold('请在浏览器中完成授权:'));\r\n if (device.verification_uri_complete) {\r\n console.log(` 一键链接:${chalk.cyan(device.verification_uri_complete)}`);\r\n }\r\n console.log(` 用户码 :${chalk.yellow.bold(device.user_code)}`);\r\n console.log(` 有效期 :${device.expires_in} 秒`);\r\n console.log();\r\n\r\n const opened = openBrowser(verificationUri);\r\n console.log(chalk.gray(\r\n opened\r\n ? '已尝试为你打开浏览器,若未自动打开请手动访问上面的一键链接。'\r\n : '请手动在浏览器中打开上面的一键链接。',\r\n ));\r\n console.log();\r\n\r\n // RFC 8628 建议的最小轮询间隔为 5 秒\r\n let interval = Math.max(device.interval ?? 5, 5);\r\n const deadline = Date.now() + device.expires_in * 1000;\r\n\r\n const poll = ora('等待用户授权...').start();\r\n while (Date.now() < deadline) {\r\n await delay(interval * 1000);\r\n let result;\r\n try {\r\n result = await pollToken({ discovery, deviceCode: device.device_code });\r\n } catch (err) {\r\n // 网络抖动等瞬时异常:继续轮询直到截止时间\r\n poll.text = `轮询出错,将重试:${(err as Error).message}`;\r\n continue;\r\n }\r\n\r\n if (result.status === 'success' && result.tokens) {\r\n const stored = toStoredTokens(result.tokens, discovery.issuer);\r\n await saveTokens(stored);\r\n poll.succeed('授权成功');\r\n\r\n if (discovery.userinfo_endpoint) {\r\n try {\r\n const info = await fetchUserInfo({\r\n discovery,\r\n accessToken: stored.access_token,\r\n });\r\n console.log();\r\n console.log(chalk.green('已登录:'));\r\n console.log(` sub :${info.sub}`);\r\n const username = info.username ?? info.preferred_username;\r\n if (username) console.log(` username:${username}`);\r\n if (info.name) console.log(` name :${info.name}`);\r\n if (info.email) console.log(` email :${info.email}`);\r\n } catch {\r\n // 打印用户信息失败不影响登录结果\r\n }\r\n }\r\n console.log();\r\n return;\r\n }\r\n\r\n if (result.status === 'pending') continue;\r\n\r\n if (result.status === 'slow_down') {\r\n interval += 5;\r\n poll.text = `服务器要求降低轮询频率(${interval}s)...`;\r\n continue;\r\n }\r\n\r\n if (result.status === 'denied') {\r\n poll.fail('用户拒绝了授权');\r\n process.exit(1);\r\n }\r\n\r\n if (result.status === 'expired') {\r\n poll.fail('设备码已过期,请重新执行登录命令');\r\n process.exit(1);\r\n }\r\n\r\n // 其他错误视为瞬时错误,继续轮询直到截止时间\r\n poll.text = `授权失败(将重试):${result.error}${result.errorDescription ? ' - ' + result.errorDescription : ''}`;\r\n }\r\n poll.fail('等待授权超时,请重新执行登录命令');\r\n process.exit(1);\r\n}\r\n","import chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport {\r\n clearTokens,\r\n loadTokens,\r\n} from '../../utils/auth-storage.js';\r\nimport { revokeAll } from '../../utils/oauth-device.js';\r\nimport { CommonOptions, loadDiscovery } from './shared.js';\r\n\r\nexport async function logoutCommand(options: CommonOptions): Promise<void> {\r\n const tokens = await loadTokens();\r\n if (!tokens) {\r\n console.log(chalk.yellow('当前未登录。'));\r\n return;\r\n }\r\n\r\n const spinner = ora('正在登出...').start();\r\n try {\r\n const discovery = await loadDiscovery(tokens.issuer || options.issuer).catch(() => null);\r\n if (discovery) {\r\n await revokeAll({\r\n discovery,\r\n accessToken: tokens.access_token,\r\n refreshToken: tokens.refresh_token,\r\n });\r\n }\r\n await clearTokens();\r\n spinner.succeed('已登出');\r\n } catch (err) {\r\n await clearTokens();\r\n spinner.warn('本地凭证已清除,但撤销令牌时发生错误');\r\n console.error(chalk.gray((err as Error).message));\r\n }\r\n}\r\n","import chalk from 'chalk';\r\nimport { loadTokens } from '../../utils/auth-storage.js';\r\nimport { fetchUserInfo } from '../../utils/oauth-device.js';\r\nimport { CommonOptions, ensureValidTokens, loadDiscovery } from './shared.js';\r\n\r\nexport async function userinfoCommand(options: CommonOptions): Promise<void> {\r\n const tokens = await loadTokens();\r\n if (!tokens) {\r\n console.log(chalk.yellow('当前未登录,请先执行:solazah-cli account login'));\r\n process.exit(1);\r\n }\r\n\r\n try {\r\n const discovery = await loadDiscovery(tokens.issuer || options.issuer);\r\n const valid = await ensureValidTokens(discovery, tokens);\r\n const info = await fetchUserInfo({\r\n discovery,\r\n accessToken: valid.access_token,\r\n });\r\n console.log(JSON.stringify(info, null, 2));\r\n } catch (err) {\r\n console.error(chalk.red('获取用户信息失败:'), (err as Error).message);\r\n process.exit(1);\r\n }\r\n}\r\n","#!/usr/bin/env node\r\n\r\nimport { Command } from 'commander';\r\nimport { createPluginCommand } from './commands/plugin/create.js';\r\nimport { packagePluginCommand } from './commands/plugin/package.js';\r\nimport { publishPluginCommand } from './commands/plugin/publish.js';\r\nimport { versionPluginCommand } from './commands/plugin/version.js';\r\nimport { releasePluginCommand } from './commands/plugin/release.js';\r\nimport { loginCommand } from './commands/account/login.js';\r\nimport { logoutCommand } from './commands/account/logout.js';\r\nimport { userinfoCommand } from './commands/account/userinfo.js';\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name('solazah-cli')\r\n .description('Solazah CLI - Command line tool for Solazah plugin development')\r\n .version(\"0.0.1\");\r\n\r\n// Plugin commands\r\nconst plugin = program\r\n .command('plugin')\r\n .description('Manage Solazah plugins');\r\n\r\nplugin\r\n .command('create')\r\n .description('Create a new Solazah plugin')\r\n .option('-n, --name <name>', 'Plugin name (e.g., my-plugin or @scope/plugin-name)')\r\n .option('-d, --dir <directory>', 'Parent directory; final target is <dir>/<plugin-name>', '.')\r\n .option('--target-dir <path>', 'Full target directory; overrides --dir')\r\n .option('-t, --template <template>', 'Template to use (e.g., react)', 'react')\r\n .option('--display-name <name>', 'Display name (skips prompt if provided)')\r\n .option('--description <desc>', 'Plugin description (skips prompt if provided)')\r\n .option('--author <author>', 'Author (skips prompt if provided)')\r\n .option('--port <port>', 'Dev server port (skips prompt if provided)', (v) => parseInt(v, 10))\r\n .option('-y, --yes', 'Skip all prompts and accept defaults for any missing answers')\r\n .option('--skip-install', 'Skip npm install')\r\n .action(createPluginCommand);\r\n\r\nplugin\r\n .command('package')\r\n .description('Build and package a Solazah plugin for distribution')\r\n .option('-d, --dir <directory>', 'Plugin project directory', '.')\r\n .action(packagePluginCommand);\r\n\r\nplugin\r\n .command('publish')\r\n .description('Publish a plugin package to Solazah marketplace')\r\n .option('-f, --file <file>', 'Path to the .tgz plugin package file')\r\n .option('-d, --dir <directory>', 'Plugin project directory', '.')\r\n .action(publishPluginCommand);\r\n\r\nplugin\r\n .command('version')\r\n .description('Bump plugin version, update CHANGELOG and create git tag')\r\n .option('-d, --dir <directory>', 'Plugin project directory', '.')\r\n .option('-r, --release-as <type>', 'Specify release type: major, minor, patch')\r\n .action(versionPluginCommand);\r\n\r\nplugin\r\n .command('release')\r\n .description('Bump version, package and publish a plugin in one step')\r\n .option('-d, --dir <directory>', 'Plugin project directory', '.')\r\n .action(releasePluginCommand);\r\n\r\n// Account commands (OAuth2 Device Authorization Grant)\r\nconst account = program\r\n .command('account')\r\n .description('Manage Solazah account authentication');\r\n\r\nconst withIssuer = (cmd: Command) =>\r\n cmd.option('--issuer <issuer>', 'OAuth2 issuer URL');\r\n\r\nwithIssuer(account.command('login'))\r\n .description('Login via OAuth2 device code flow')\r\n .action(loginCommand);\r\n\r\nwithIssuer(account.command('logout'))\r\n .description('Logout and revoke stored tokens')\r\n .action(logoutCommand);\r\n\r\nwithIssuer(account.command('userinfo'))\r\n .description('Show current user info')\r\n .action(userinfoCommand);\r\n\r\nprogram.parseAsync().catch((err) => {\r\n console.error(err);\r\n process.exit(1);\r\n});\r\n"],"names":["__filename","__dirname","execAsync","answers"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/utils/validate.ts","../src/utils/file.ts","../src/commands/plugin/create.ts","../src/commands/plugin/package.ts","../src/utils/auth-storage.ts","../src/utils/oauth-device.ts","../src/commands/plugin/publish.ts","../src/commands/plugin/version.ts","../src/commands/plugin/release.ts","../src/commands/plugin/templates.ts","../src/utils/open-browser.ts","../src/commands/account/shared.ts","../src/commands/account/login.ts","../src/commands/account/logout.ts","../src/commands/account/userinfo.ts","../src/index.ts"],"sourcesContent":["import validatePackageName from 'validate-npm-package-name';\r\n\r\nexport interface ValidationResult {\r\n valid: boolean;\r\n errors: string[];\r\n}\r\n\r\n/**\r\n * 验证插件名称\r\n * 接受任何合法的 npm 包名\r\n */\r\nexport function validatePluginName(name: string): ValidationResult {\r\n const errors: string[] = [];\r\n\r\n // 基本格式检查\r\n if (!name) {\r\n errors.push('Plugin name is required');\r\n return { valid: false, errors };\r\n }\r\n\r\n // 使用 npm 包名验证\r\n const validation = validatePackageName(name);\r\n if (!validation.validForNewPackages) {\r\n if (validation.errors) {\r\n errors.push(...validation.errors);\r\n }\r\n if (validation.warnings) {\r\n errors.push(...validation.warnings);\r\n }\r\n }\r\n\r\n return {\r\n valid: errors.length === 0,\r\n errors,\r\n };\r\n}\r\n\r\n/**\r\n * 从包名提取插件名称\r\n * @example extractPluginName('@solazah/solazah-plugin-example') => 'solazah-plugin-example'\r\n */\r\nexport function extractPluginName(packageName: string): string {\r\n // 移除作用域前缀(如 @solazah/)\r\n return packageName.replace(/^@[^/]+\\//, '');\r\n}\r\n\r\nexport function extractPluginSimpleName(packageName: string){\r\n // 移除作用域前缀(如 @solazah/)\r\n let name = packageName.replace(/^@[^/]+\\//, '');\r\n\r\n // 移除常见的插件前缀\r\n name = name.replace(/^plugin-/, '').replace(/^solazah-plugin-/, '');\r\n\r\n return name;\r\n}\r\n\r\n/**\r\n * 生成显示名称\r\n * @example generateDisplayName('example') => 'Example'\r\n */\r\nexport function generateDisplayName(name: string): string {\r\n return name\r\n .split('-')\r\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\r\n .join(' ');\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport { fileURLToPath } from 'url';\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\nexport interface TemplateInfo {\r\n name: string;\r\n displayName: string;\r\n description: string;\r\n version: string;\r\n features?: string[];\r\n}\r\n\r\n/**\r\n * 检查目录是否为空\r\n */\r\nexport async function isDirectoryEmpty(dirPath: string): Promise<boolean> {\r\n try {\r\n const files = await fs.readdir(dirPath);\r\n return files.length === 0;\r\n } catch {\r\n return true; // 目录不存在,视为空\r\n }\r\n}\r\n\r\n/**\r\n * 确保目录存在\r\n */\r\nexport async function ensureDirectory(dirPath: string): Promise<void> {\r\n await fs.ensureDir(dirPath);\r\n}\r\n\r\n/**\r\n * 获取模板根目录路径\r\n * 发布产物中 index.js 与 templates/ 同级(见 package.config.js 的 filesToCopy),\r\n * 所以 __dirname 即包根目录。\r\n */\r\nexport function getTemplatesRootDir(): string {\r\n return path.resolve(__dirname, 'templates');\r\n}\r\n\r\n/**\r\n * 获取指定模板的目录路径\r\n */\r\nexport function getTemplateDir(templateName: string): string {\r\n return path.join(getTemplatesRootDir(), templateName);\r\n}\r\n\r\n/**\r\n * 获取所有可用模板\r\n */\r\nexport async function getAvailableTemplates(): Promise<TemplateInfo[]> {\r\n const templatesRoot = getTemplatesRootDir();\r\n const templates: TemplateInfo[] = [];\r\n\r\n try {\r\n const entries = await fs.readdir(templatesRoot, { withFileTypes: true });\r\n\r\n for (const entry of entries) {\r\n if (entry.isDirectory()) {\r\n const templateJsonPath = path.join(templatesRoot, entry.name, 'template.json');\r\n\r\n if (await fs.pathExists(templateJsonPath)) {\r\n const templateInfo = await fs.readJson(templateJsonPath);\r\n templates.push(templateInfo);\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n console.error('Failed to read templates:', error);\r\n }\r\n\r\n return templates;\r\n}\r\n\r\n/**\r\n * 复制模板文件\r\n */\r\nexport async function copyTemplate(templateDir: string, targetDir: string): Promise<void> {\r\n await fs.copy(templateDir, targetDir, {\r\n filter: (src) => {\r\n // 排除 node_modules 和其他不需要的文件\r\n const basename = path.basename(src);\r\n return !['node_modules', '.git', 'dist', 'target', 'release'].includes(basename);\r\n },\r\n });\r\n}\r\n\r\n/**\r\n * 替换文件内容中的占位符\r\n * 支持 {{PLACEHOLDER}} 格式\r\n */\r\nexport async function replacePlaceholders(\r\n filePath: string,\r\n replacements: Record<string, string>\r\n): Promise<void> {\r\n let content = await fs.readFile(filePath, 'utf-8');\r\n\r\n for (const [key, value] of Object.entries(replacements)) {\r\n // 支持 {{KEY}} 和直接字符串替换\r\n const placeholder = `{{${key}}}`;\r\n content = content.replaceAll(placeholder, value);\r\n // 兼容旧的直接替换方式\r\n content = content.replaceAll(key, value);\r\n }\r\n\r\n await fs.writeFile(filePath, content, 'utf-8');\r\n}\r\n\r\n/**\r\n * 批量替换目录中所有文件的占位符\r\n */\r\nexport async function replaceInDirectory(\r\n dirPath: string,\r\n replacements: Record<string, string>,\r\n extensions: string[] = ['.ts', '.tsx', '.js', '.jsx', '.json', '.html', '.md']\r\n): Promise<void> {\r\n const files = await fs.readdir(dirPath, { withFileTypes: true });\r\n\r\n for (const file of files) {\r\n const filePath = path.join(dirPath, file.name);\r\n\r\n if (file.isDirectory()) {\r\n // 递归处理子目录\r\n await replaceInDirectory(filePath, replacements, extensions);\r\n } else if (file.isFile()) {\r\n // 检查文件扩展名\r\n const ext = path.extname(file.name);\r\n if (extensions.includes(ext) || file.name === 'manifest.json') {\r\n await replacePlaceholders(filePath, replacements);\r\n }\r\n }\r\n }\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport inquirer from 'inquirer';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport { exec } from 'child_process';\r\nimport { promisify } from 'util';\r\nimport {\r\n validatePluginName,\r\n extractPluginName,\r\n generateDisplayName, extractPluginSimpleName,\r\n} from '../../utils/validate.js';\r\nimport {\r\n isDirectoryEmpty,\r\n ensureDirectory,\r\n getTemplateDir,\r\n getAvailableTemplates,\r\n copyTemplate,\r\n replaceInDirectory,\r\n} from '../../utils/file.js';\r\n\r\nconst execAsync = promisify(exec);\r\n\r\ninterface CreatePluginOptions {\r\n name?: string;\r\n dir?: string;\r\n targetDir?: string;\r\n template?: string;\r\n displayName?: string;\r\n description?: string;\r\n author?: string;\r\n port?: number;\r\n yes?: boolean;\r\n skipInstall?: boolean;\r\n}\r\n\r\nexport async function createPluginCommand(options: CreatePluginOptions): Promise<void> {\r\n console.log(chalk.cyan.bold('\\n🚀 Solazah Plugin Creator\\n'));\r\n\r\n try {\r\n // 1. 获取可用模板\r\n const availableTemplates = await getAvailableTemplates();\r\n\r\n if (availableTemplates.length === 0) {\r\n console.error(chalk.red('❌ No templates found'));\r\n process.exit(1);\r\n }\r\n\r\n // 2. 选择模板\r\n let selectedTemplate = options.template || 'react';\r\n\r\n if (!options.template && availableTemplates.length > 1) {\r\n const { template } = await inquirer.prompt([\r\n {\r\n type: 'list',\r\n name: 'template',\r\n message: 'Select a template:',\r\n choices: availableTemplates.map((t) => ({\r\n name: `${t.displayName} - ${t.description}`,\r\n value: t.name,\r\n })),\r\n default: 'react',\r\n },\r\n ]);\r\n selectedTemplate = template;\r\n }\r\n\r\n const templateInfo = availableTemplates.find((t) => t.name === selectedTemplate);\r\n if (!templateInfo) {\r\n console.error(chalk.red(`❌ Template \"${selectedTemplate}\" not found`));\r\n process.exit(1);\r\n }\r\n\r\n console.log(chalk.gray(`Using template: ${templateInfo.displayName}\\n`));\r\n\r\n // 3. 获取或询问插件名称\r\n let pluginName = options.name;\r\n\r\n if (!pluginName) {\r\n const answers = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'name',\r\n message: 'Plugin name (e.g., my-plugin or @scope/plugin-name):',\r\n validate: (input: string) => {\r\n const result = validatePluginName(input);\r\n return result.valid || result.errors.join(', ');\r\n },\r\n },\r\n ]);\r\n pluginName = answers.name;\r\n }\r\n\r\n // 验证插件名称\r\n const validation = validatePluginName(pluginName!);\r\n if (!validation.valid) {\r\n console.error(chalk.red('❌ Invalid plugin name:'));\r\n validation.errors.forEach((error) => console.error(chalk.red(` - ${error}`)));\r\n process.exit(1);\r\n }\r\n\r\n // 2. 提取信息\r\n const packageName = extractPluginName(pluginName!)\r\n const simpleName = extractPluginSimpleName(pluginName!);\r\n const defaultDisplayName = generateDisplayName(simpleName);\r\n\r\n // 3. 确定目标目录\r\n const defaultTargetDir = options.targetDir\r\n ? path.resolve(options.targetDir)\r\n : (options.dir ? path.resolve(options.dir, packageName) : path.resolve('.'));\r\n\r\n let targetDir: string;\r\n if (options.targetDir || options.yes) {\r\n targetDir = defaultTargetDir;\r\n } else {\r\n const { targetDir: confirmedTargetDir } = await inquirer.prompt([\r\n {\r\n type: 'input',\r\n name: 'targetDir',\r\n message: 'Target directory:',\r\n default: defaultTargetDir,\r\n },\r\n ]);\r\n targetDir = path.resolve(confirmedTargetDir);\r\n }\r\n\r\n // 4. 询问额外信息(命令行已提供则跳过)\r\n const defaults = {\r\n displayName: defaultDisplayName,\r\n description: `A Solazah plugin - ${defaultDisplayName}`,\r\n author: '',\r\n port: 5200,\r\n };\r\n const provided = {\r\n displayName: options.displayName,\r\n description: options.description,\r\n author: options.author,\r\n port: options.port,\r\n };\r\n const remainingPrompts: any[] = [];\r\n if (provided.displayName === undefined && !options.yes) {\r\n remainingPrompts.push({ type: 'input', name: 'displayName', message: 'Display name:', default: defaults.displayName });\r\n }\r\n if (provided.description === undefined && !options.yes) {\r\n remainingPrompts.push({ type: 'input', name: 'description', message: 'Description:', default: defaults.description });\r\n }\r\n if (provided.author === undefined && !options.yes) {\r\n remainingPrompts.push({ type: 'input', name: 'author', message: 'Author:', default: defaults.author });\r\n }\r\n if (provided.port === undefined && !options.yes) {\r\n remainingPrompts.push({ type: 'number', name: 'port', message: 'Development server port:', default: defaults.port });\r\n }\r\n const promptAnswers = remainingPrompts.length > 0 ? await inquirer.prompt(remainingPrompts) : {};\r\n const answers = {\r\n displayName: provided.displayName ?? promptAnswers.displayName ?? defaults.displayName,\r\n description: provided.description ?? promptAnswers.description ?? defaults.description,\r\n author: provided.author ?? promptAnswers.author ?? defaults.author,\r\n port: provided.port ?? promptAnswers.port ?? defaults.port,\r\n };\r\n\r\n // 检查目录是否存在且非空\r\n const dirExists = await fs.pathExists(targetDir);\r\n if (dirExists) {\r\n const isEmpty = await isDirectoryEmpty(targetDir);\r\n if (!isEmpty) {\r\n const { overwrite } = await inquirer.prompt([\r\n {\r\n type: 'confirm',\r\n name: 'overwrite',\r\n message: `Directory ${chalk.cyan(targetDir)} is not empty. Overwrite?`,\r\n default: false,\r\n },\r\n ]);\r\n\r\n if (!overwrite) {\r\n console.log(chalk.yellow('❌ Cancelled'));\r\n process.exit(0);\r\n }\r\n\r\n await fs.remove(targetDir);\r\n }\r\n }\r\n\r\n // 5. 创建目录\r\n await ensureDirectory(targetDir);\r\n\r\n // 6. 复制模板\r\n const spinner = ora('Copying template files...').start();\r\n const templateDir = getTemplateDir(selectedTemplate);\r\n\r\n if (!(await fs.pathExists(templateDir))) {\r\n spinner.fail(chalk.red(`Template directory not found: ${templateDir}`));\r\n process.exit(1);\r\n }\r\n\r\n await copyTemplate(templateDir, targetDir);\r\n spinner.succeed('Template files copied');\r\n\r\n // 7. 更新文件内容\r\n spinner.start('Updating files...');\r\n\r\n // 定义占位符替换映射\r\n const replacements: Record<string, string> = {\r\n PLUGIN_NAME: pluginName!,\r\n PLUGIN_DISPLAY_NAME: answers.displayName,\r\n PLUGIN_DESCRIPTION: answers.description,\r\n PLUGIN_AUTHOR: answers.author || '',\r\n PLUGIN_VERSION: '0.0.1',\r\n DEV_PORT: answers.port.toString(),\r\n PLUGIN_SIMPLE_NAME: simpleName,\r\n PLUGIN_CLASS_NAME: `solazah-${simpleName}`,\r\n };\r\n\r\n // 批量替换目录中的占位符\r\n await replaceInDirectory(targetDir, replacements);\r\n\r\n // 额外处理特定文件(JSON 文件需要特殊处理)\r\n const packageJsonPath = path.join(targetDir, 'package.json');\r\n const packageJson = await fs.readJson(packageJsonPath);\r\n packageJson.name = pluginName;\r\n packageJson.version = '0.0.1';\r\n packageJson.description = answers.description;\r\n if (answers.author) {\r\n packageJson.author = answers.author;\r\n }\r\n packageJson.scripts.dev = `vite --port ${answers.port}`;\r\n await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });\r\n\r\n const manifestPath = path.join(targetDir, 'manifest.json');\r\n const manifest = await fs.readJson(manifestPath);\r\n manifest.name = pluginName;\r\n manifest.displayName = answers.displayName;\r\n manifest.commands = [answers.displayName];\r\n manifest.development.main = `http://localhost:${answers.port}`;\r\n await fs.writeJson(manifestPath, manifest, { spaces: 2 });\r\n\r\n // 还原 dotfiles:npm publish 会剥离 .gitignore/.npmrc,模板里以无点名保存,生成后重命名回来\r\n const dotfileRenames: [string, string][] = [\r\n ['gitignore', '.gitignore'],\r\n ['npmrc', '.npmrc'],\r\n ];\r\n for (const [from, to] of dotfileRenames) {\r\n const fromPath = path.join(targetDir, from);\r\n const toPath = path.join(targetDir, to);\r\n if (await fs.pathExists(fromPath)) {\r\n await fs.move(fromPath, toPath, { overwrite: true });\r\n }\r\n }\r\n\r\n // 删除不需要的文件\r\n const filesToRemove = [\r\n 'template.json',\r\n ];\r\n for (const file of filesToRemove) {\r\n const filePath = path.join(targetDir, file);\r\n if (await fs.pathExists(filePath)) {\r\n await fs.remove(filePath);\r\n }\r\n }\r\n\r\n spinner.succeed('Files updated');\r\n\r\n // 初始化 git 仓库(best-effort,git 不可用则跳过)\r\n spinner.start('Initializing git repository...');\r\n try {\r\n await execAsync('git init -b master', { cwd: targetDir });\r\n spinner.succeed('Git repository initialized');\r\n } catch {\r\n spinner.info('Skipped git init (git not available)');\r\n }\r\n\r\n // 8. 安装依赖\r\n if (!options.skipInstall) {\r\n spinner.start('Installing dependencies...');\r\n try {\r\n await execAsync('npm install', { cwd: targetDir });\r\n spinner.succeed('Dependencies installed');\r\n } catch (error) {\r\n spinner.fail('Failed to install dependencies');\r\n console.log(chalk.yellow('You can install them manually by running: npm install'));\r\n }\r\n }\r\n\r\n // 9. 完成\r\n console.log(chalk.green.bold('\\n✅ Plugin created successfully!\\n'));\r\n console.log(chalk.cyan('Next steps:'));\r\n const relativePath = path.relative(process.cwd(), targetDir);\r\n if (relativePath && relativePath !== '.') {\r\n console.log(chalk.gray(` cd ${relativePath}`));\r\n }\r\n if (options.skipInstall) {\r\n console.log(chalk.gray(' npm install'));\r\n }\r\n console.log(chalk.gray(' npm run dev'));\r\n console.log();\r\n } catch (error) {\r\n console.error(chalk.red('❌ Error:'), error);\r\n process.exit(1);\r\n }\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport { exec } from 'child_process';\r\nimport { promisify } from 'util';\r\n\r\nconst execAsync = promisify(exec);\r\n\r\ninterface PackagePluginOptions {\r\n dir?: string;\r\n}\r\n\r\ntype FileCopyEntry = string | { from: string; to: string };\r\n\r\n/**\r\n * 根据 manifest.json 推断需要复制到 release/ 的文件列表\r\n */\r\nfunction resolveFilesToCopy(projectDir: string, manifest: Record<string, unknown>): FileCopyEntry[] {\r\n const entries: FileCopyEntry[] = [];\r\n\r\n // manifest.json 始终需要\r\n entries.push('manifest.json');\r\n\r\n // 可选文档文件\r\n for (const doc of ['CHANGELOG.md', 'README.md','icon.png']) {\r\n if (fs.existsSync(path.join(projectDir, doc))) {\r\n entries.push(doc);\r\n }\r\n }\r\n\r\n // 根据 manifest.main 推断构建产物目标目录\r\n // e.g. \"renderer/index.html\" → dist -> renderer\r\n // e.g. \"index.html\" → dist -> .\r\n const main = manifest.main as string | undefined;\r\n if (main) {\r\n const mainDir = path.dirname(main);\r\n entries.push({ from: 'dist', to: mainDir === '.' ? '.' : mainDir });\r\n } else {\r\n entries.push({ from: 'dist', to: '.' });\r\n }\r\n\r\n // preload 目录\r\n const preload = manifest.preload as string | undefined;\r\n if (preload) {\r\n const preloadDir = path.dirname(preload); // e.g. \"preload/preload.mjs\" -> \"preload\"\r\n const srcPreload = path.join(projectDir, 'src', 'preload');\r\n if (fs.existsSync(srcPreload)) {\r\n entries.push({ from: 'src/preload', to: preloadDir });\r\n }\r\n }\r\n\r\n // skills 目录\r\n const srcSkills = path.join(projectDir, 'src', 'skills');\r\n if (fs.existsSync(srcSkills)) {\r\n entries.push({ from: 'src/skills', to: 'skills' });\r\n }\r\n\r\n // screenshots 目录\r\n const srcScreenshots = path.join(projectDir, 'src', 'screenshots');\r\n if (fs.existsSync(srcScreenshots)) {\r\n entries.push({ from: 'src/screenshots', to: 'screenshots' });\r\n }\r\n\r\n // public 目录:命令图标等静态资产,原样进包(manifest 里以 public/xxx.png 引用)\r\n const srcPublic = path.join(projectDir, 'public');\r\n if (fs.existsSync(srcPublic)) {\r\n entries.push({ from: 'public', to: 'public' });\r\n }\r\n\r\n return entries;\r\n}\r\n\r\n/**\r\n * 清理并初始化 release 目录\r\n */\r\nfunction cleanReleaseDir(releaseDir: string): void {\r\n if (fs.existsSync(releaseDir)) {\r\n fs.rmSync(releaseDir, { recursive: true, force: true });\r\n }\r\n fs.mkdirSync(releaseDir, { recursive: true });\r\n}\r\n\r\n/**\r\n * 按照文件列表复制到 release 目录\r\n */\r\nfunction copyFiles(projectDir: string, releaseDir: string, files: FileCopyEntry[]): void {\r\n for (const file of files) {\r\n if (typeof file === 'string') {\r\n const src = path.resolve(projectDir, file);\r\n const dest = path.resolve(releaseDir, file);\r\n fs.cpSync(src, dest, { recursive: true });\r\n } else {\r\n const src = path.resolve(projectDir, file.from);\r\n const dest = path.resolve(releaseDir, file.to);\r\n fs.cpSync(src, dest, { recursive: true });\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 精简 package.json,只保留发布需要的字段\r\n */\r\nfunction processPackageJson(projectDir: string, releaseDir: string): void {\r\n const raw = fs.readFileSync(path.resolve(projectDir, 'package.json'), 'utf-8');\r\n const pkg = JSON.parse(raw);\r\n\r\n const cleaned: Record<string, unknown> = {\r\n name: pkg.name,\r\n main: pkg.main,\r\n type: pkg.type,\r\n version: pkg.version,\r\n description: pkg.description,\r\n author: pkg.author,\r\n license: pkg.license,\r\n homepage: pkg.homepage,\r\n repository: pkg.repository,\r\n keywords: pkg.keywords,\r\n peerDependencies: pkg.peerDependencies,\r\n };\r\n\r\n fs.writeFileSync(\r\n path.resolve(releaseDir, 'package.json'),\r\n JSON.stringify(cleaned, null, 2),\r\n );\r\n}\r\n\r\n/**\r\n * 清理构建产物临时目录 (dist/)\r\n * Windows 上 cpSync 之后文件句柄可能尚未完全释放,rmSync 会报 ENOTEMPTY。\r\n * 加重试逻辑,等待系统释放句柄后再删除。\r\n */\r\nfunction cleanDistDir(projectDir: string): void {\r\n const distDir = path.join(projectDir, 'dist');\r\n if (!fs.existsSync(distDir)) return;\r\n\r\n const maxRetries = 5;\r\n const retryDelayMs = 200;\r\n\r\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\r\n try {\r\n fs.rmSync(distDir, { recursive: true, force: true });\r\n return;\r\n } catch (err: any) {\r\n if (attempt === maxRetries) throw err;\r\n // 同步等待后重试(仅用于磁盘 I/O 竞态,不阻塞事件循环中的业务逻辑)\r\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, retryDelayMs);\r\n }\r\n }\r\n}\r\n\r\nexport async function packagePluginCommand(options: PackagePluginOptions): Promise<void> {\r\n const projectDir = path.resolve(options.dir || '.');\r\n\r\n console.log(chalk.cyan.bold('\\n📦 Solazah Plugin Packager\\n'));\r\n\r\n // 1. 验证项目目录\r\n const packageJsonPath = path.join(projectDir, 'package.json');\r\n if (!(await fs.pathExists(packageJsonPath))) {\r\n console.error(chalk.red('❌ No package.json found in the target directory.'));\r\n process.exit(1);\r\n }\r\n\r\n const manifestPath = path.join(projectDir, 'manifest.json');\r\n if (!(await fs.pathExists(manifestPath))) {\r\n console.error(chalk.red('❌ No manifest.json found. Is this a Solazah plugin project?'));\r\n process.exit(1);\r\n }\r\n\r\n const packageData = await fs.readJson(packageJsonPath);\r\n const manifest = await fs.readJson(manifestPath);\r\n console.log(chalk.gray(`Plugin: ${packageData.name || 'unknown'} v${packageData.version || '0.0.0'}\\n`));\r\n\r\n // 2. 构建项目\r\n const buildSpinner = ora('Building plugin...').start();\r\n try {\r\n await execAsync('npm run build', { cwd: projectDir });\r\n buildSpinner.succeed('Build completed');\r\n } catch (error: any) {\r\n buildSpinner.fail('Build failed');\r\n console.error(chalk.red(error.stderr || error.message));\r\n process.exit(1);\r\n }\r\n\r\n // 3. 打包到 release/\r\n const packageSpinner = ora('Packaging plugin...').start();\r\n try {\r\n const releaseDir = path.join(projectDir, 'release');\r\n const filesToCopy = resolveFilesToCopy(projectDir, manifest);\r\n\r\n cleanReleaseDir(releaseDir);\r\n copyFiles(projectDir, releaseDir, filesToCopy);\r\n processPackageJson(projectDir, releaseDir);\r\n cleanDistDir(projectDir);\r\n\r\n packageSpinner.succeed('Package created in release/');\r\n } catch (error: any) {\r\n packageSpinner.fail('Packaging failed');\r\n console.error(chalk.red(error.message));\r\n process.exit(1);\r\n }\r\n\r\n // 4. 生成 .tgz 文件到 release/\r\n const tgzSpinner = ora('Creating .tgz archive...').start();\r\n try {\r\n const { stdout } = await execAsync('npm pack ./release --pack-destination ./release', { cwd: projectDir });\r\n const tgzFile = stdout.trim();\r\n tgzSpinner.succeed(`Archive created: ${chalk.cyan('release/' + tgzFile)}`);\r\n } catch (error: any) {\r\n tgzSpinner.fail('Failed to create .tgz archive');\r\n console.error(chalk.red(error.stderr || error.message));\r\n process.exit(1);\r\n }\r\n\r\n console.log(chalk.green.bold('\\n✅ Plugin packaged successfully!\\n'));\r\n}\r\n","import path from 'path';\r\nimport os from 'os';\r\nimport fs from 'fs-extra';\r\n\r\nexport interface StoredTokens {\r\n access_token: string;\r\n refresh_token?: string;\r\n id_token?: string;\r\n token_type: string;\r\n scope?: string;\r\n expires_at?: number; // epoch seconds\r\n issuer?: string;\r\n}\r\n\r\nexport interface TokenResponse {\r\n access_token: string;\r\n refresh_token?: string;\r\n id_token?: string;\r\n token_type: string;\r\n expires_in?: number;\r\n scope?: string;\r\n}\r\n\r\nconst AUTH_DIR = path.join(os.homedir(), '.solazah');\r\nconst AUTH_FILE = path.join(AUTH_DIR, 'auth.json');\r\nconst EXPIRY_SKEW_SECONDS = 30;\r\n\r\nexport async function saveTokens(tokens: StoredTokens): Promise<void> {\r\n await fs.ensureDir(AUTH_DIR);\r\n await fs.writeJson(AUTH_FILE, tokens, { spaces: 2 });\r\n try {\r\n await fs.chmod(AUTH_FILE, 0o600);\r\n } catch {\r\n // ignore chmod errors on Windows\r\n }\r\n}\r\n\r\nexport async function loadTokens(): Promise<StoredTokens | null> {\r\n try {\r\n return (await fs.readJson(AUTH_FILE)) as StoredTokens;\r\n } catch (err) {\r\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\r\n return null;\r\n }\r\n throw err;\r\n }\r\n}\r\n\r\nexport async function clearTokens(): Promise<void> {\r\n try {\r\n await fs.remove(AUTH_FILE);\r\n } catch {\r\n // ignore\r\n }\r\n}\r\n\r\nexport function isExpired(tokens: StoredTokens): boolean {\r\n if (!tokens.expires_at) return false;\r\n return Math.floor(Date.now() / 1000) >= tokens.expires_at - EXPIRY_SKEW_SECONDS;\r\n}\r\n\r\nexport function toStoredTokens(tokens: TokenResponse, issuer: string): StoredTokens {\r\n const now = Math.floor(Date.now() / 1000);\r\n return {\r\n access_token: tokens.access_token,\r\n refresh_token: tokens.refresh_token,\r\n id_token: tokens.id_token,\r\n token_type: tokens.token_type,\r\n scope: tokens.scope,\r\n expires_at: tokens.expires_in ? now + tokens.expires_in : undefined,\r\n issuer,\r\n };\r\n}\r\n\r\n/**\r\n * 合并刷新后的令牌:若服务器未返回新的 refresh_token 则保留原值。\r\n */\r\nexport function mergeRefreshedTokens(\r\n previous: StoredTokens,\r\n refreshed: TokenResponse,\r\n issuer: string,\r\n): StoredTokens {\r\n const next = toStoredTokens(refreshed, issuer);\r\n if (!next.refresh_token) {\r\n next.refresh_token = previous.refresh_token;\r\n }\r\n return next;\r\n}\r\n","import { TokenResponse } from './auth-storage.js';\r\n\r\nexport const DEFAULT_ISSUER = 'https://solazah.seayona.com/account';\r\nexport const DEFAULT_CLIENT_ID = 'solazah-cli';\r\nexport const DEFAULT_CLIENT_SECRET = 'solazah-cli';\r\nexport const DEFAULT_SCOPE = 'openid profile offline_access';\r\nexport const DEFAULT_TOKEN_TYPE = 'Bearer';\r\n\r\n/**\r\n * 授权服务器默认把 verification_uri 指向后端的表单端点\r\n * (/account/oauth2/device_verification),但我们的前端是 SPA,\r\n * 直接让用户访问后端表单会看到空白页。这里统一重写为 SPA 的\r\n * /#/device hash 路由,由前端引导用户登录并提交 user_code。\r\n */\r\nexport const DEFAULT_VERIFICATION_URI = 'https://solazah.seayona.com/account/#/device';\r\n\r\nexport const GRANT_TYPE = {\r\n DEVICE_CODE: 'urn:ietf:params:oauth:grant-type:device_code',\r\n REFRESH_TOKEN: 'refresh_token',\r\n} as const;\r\n\r\nexport const TOKEN_HINT = {\r\n ACCESS: 'access_token',\r\n REFRESH: 'refresh_token',\r\n} as const;\r\nexport type TokenTypeHint = typeof TOKEN_HINT[keyof typeof TOKEN_HINT];\r\n\r\nexport const DEVICE_ERROR = {\r\n AUTH_PENDING: 'authorization_pending',\r\n SLOW_DOWN: 'slow_down',\r\n ACCESS_DENIED: 'access_denied',\r\n EXPIRED_TOKEN: 'expired_token',\r\n} as const;\r\n\r\nexport interface OidcDiscovery {\r\n issuer: string;\r\n device_authorization_endpoint?: string;\r\n token_endpoint: string;\r\n userinfo_endpoint?: string;\r\n end_session_endpoint?: string;\r\n revocation_endpoint?: string;\r\n}\r\n\r\nexport interface DeviceAuthorizationResponse {\r\n device_code: string;\r\n user_code: string;\r\n verification_uri: string;\r\n verification_uri_complete?: string;\r\n expires_in: number;\r\n interval?: number;\r\n}\r\n\r\nexport interface UserInfo {\r\n sub: string;\r\n name?: string;\r\n username?: string;\r\n preferred_username?: string;\r\n email?: string;\r\n [key: string]: unknown;\r\n}\r\n\r\nexport async function discover(issuer: string = DEFAULT_ISSUER): Promise<OidcDiscovery> {\r\n const url = `${issuer.replace(/\\/+$/, '')}/.well-known/openid-configuration`;\r\n const res = await fetch(url);\r\n if (!res.ok) {\r\n throw new Error(`Failed to load OIDC discovery (${res.status}): ${url}`);\r\n }\r\n return (await res.json()) as OidcDiscovery;\r\n}\r\n\r\n/**\r\n * OAuth 端点 POST:application/x-www-form-urlencoded;\r\n * 使用 client_secret_post 方式认证,client_id 和 client_secret 放在表单中。\r\n */\r\nasync function postForm(\r\n url: string,\r\n params: Record<string, string>,\r\n): Promise<{ ok: boolean; status: number; data: Record<string, unknown> }> {\r\n const body = new URLSearchParams(params);\r\n const res = await fetch(url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded',\r\n Accept: 'application/json',\r\n },\r\n body: body.toString(),\r\n redirect: 'manual',\r\n });\r\n\r\n // 服务器返回 3xx 重定向通常意味着客户端认证失败或端点配置错误\r\n if (res.status >= 300 && res.status < 400) {\r\n const location = res.headers.get('location') ?? '(unknown)';\r\n throw new Error(\r\n `服务器返回了重定向 (${res.status}) → ${location},` +\r\n `请确认客户端 client_id 已在授权服务器注册为公共客户端`,\r\n );\r\n }\r\n\r\n const text = await res.text();\r\n let data: Record<string, unknown>;\r\n try {\r\n data = JSON.parse(text) as Record<string, unknown>;\r\n } catch {\r\n throw new Error(\r\n `服务器返回了非 JSON 响应 (${res.status}): ${text.slice(0, 200)}`,\r\n );\r\n }\r\n return { ok: res.ok, status: res.status, data };\r\n}\r\n\r\n/** 安全提取 string */\r\nfunction str(v: unknown): string | undefined {\r\n return typeof v === 'string' ? v : undefined;\r\n}\r\n/** 安全提取 number */\r\nfunction num(v: unknown): number | undefined {\r\n return typeof v === 'number' ? v : undefined;\r\n}\r\n\r\nexport async function requestDeviceCode(options: {\r\n discovery: OidcDiscovery;\r\n clientId?: string;\r\n scope?: string;\r\n}): Promise<DeviceAuthorizationResponse> {\r\n const { discovery } = options;\r\n if (!discovery.device_authorization_endpoint) {\r\n throw new Error('授权服务器未提供 device_authorization_endpoint');\r\n }\r\n const { ok, status, data } = await postForm(discovery.device_authorization_endpoint, {\r\n client_id: options.clientId ?? DEFAULT_CLIENT_ID,\r\n client_secret: DEFAULT_CLIENT_SECRET,\r\n scope: options.scope ?? DEFAULT_SCOPE,\r\n });\r\n if (!ok) {\r\n throw new Error(`设备授权请求失败 (${status}): ${JSON.stringify(data)}`);\r\n }\r\n\r\n // 兼容 snake_case(RFC 8628 标准)和 camelCase 两种响应格式\r\n const deviceCode = str(data.device_code) ?? str(data.deviceCode);\r\n const userCode = str(data.user_code) ?? str(data.userCode);\r\n const expiresIn = num(data.expires_in) ?? num(data.expiresIn);\r\n const interval = num(data.interval);\r\n\r\n if (!deviceCode || !userCode || expiresIn == null) {\r\n throw new Error(\r\n `设备授权响应缺少必要字段(device_code/user_code/expires_in),` +\r\n `实际响应:${JSON.stringify(data)}`,\r\n );\r\n }\r\n\r\n // 授权服务器默认返回后端表单端点作为 verification_uri,\r\n // 这里覆写为 SPA 地址,保证用户打开后能看到前端页面。\r\n const verificationUri = DEFAULT_VERIFICATION_URI;\r\n const verificationUriComplete = `${verificationUri}?user_code=${encodeURIComponent(userCode)}`;\r\n\r\n return {\r\n device_code: deviceCode,\r\n user_code: userCode,\r\n verification_uri: verificationUri,\r\n verification_uri_complete: verificationUriComplete,\r\n expires_in: expiresIn,\r\n interval,\r\n };\r\n}\r\n\r\nexport type PollStatus = 'success' | 'pending' | 'slow_down' | 'denied' | 'expired' | 'error';\r\n\r\nexport interface PollResult {\r\n status: PollStatus;\r\n tokens?: TokenResponse;\r\n error?: string;\r\n errorDescription?: string;\r\n}\r\n\r\nexport async function pollToken(options: {\r\n discovery: OidcDiscovery;\r\n deviceCode: string;\r\n clientId?: string;\r\n}): Promise<PollResult> {\r\n const { ok, data } = await postForm(options.discovery.token_endpoint, {\r\n grant_type: GRANT_TYPE.DEVICE_CODE,\r\n device_code: options.deviceCode,\r\n client_id: options.clientId ?? DEFAULT_CLIENT_ID,\r\n client_secret: DEFAULT_CLIENT_SECRET,\r\n });\r\n\r\n if (ok && typeof data.access_token === 'string') {\r\n return { status: 'success', tokens: tokenResponseFrom(data) };\r\n }\r\n\r\n const err = typeof data.error === 'string' ? data.error : 'error';\r\n const errorDescription =\r\n typeof data.error_description === 'string' ? data.error_description : undefined;\r\n const status: PollStatus =\r\n err === DEVICE_ERROR.AUTH_PENDING ? 'pending'\r\n : err === DEVICE_ERROR.SLOW_DOWN ? 'slow_down'\r\n : err === DEVICE_ERROR.ACCESS_DENIED ? 'denied'\r\n : err === DEVICE_ERROR.EXPIRED_TOKEN ? 'expired'\r\n : 'error';\r\n return { status, error: err, errorDescription };\r\n}\r\n\r\nexport async function fetchUserInfo(options: {\r\n discovery: OidcDiscovery;\r\n accessToken: string;\r\n}): Promise<UserInfo> {\r\n if (!options.discovery.userinfo_endpoint) {\r\n throw new Error('授权服务器未提供 userinfo_endpoint');\r\n }\r\n const res = await fetch(options.discovery.userinfo_endpoint, {\r\n headers: {\r\n Authorization: `Bearer ${options.accessToken}`,\r\n Accept: 'application/json',\r\n },\r\n });\r\n if (!res.ok) {\r\n const text = await res.text();\r\n throw new Error(`获取用户信息失败 (${res.status}): ${text}`);\r\n }\r\n return (await res.json()) as UserInfo;\r\n}\r\n\r\nexport async function refreshTokens(options: {\r\n discovery: OidcDiscovery;\r\n refreshToken: string;\r\n clientId?: string;\r\n}): Promise<TokenResponse> {\r\n const { ok, status, data } = await postForm(options.discovery.token_endpoint, {\r\n grant_type: GRANT_TYPE.REFRESH_TOKEN,\r\n refresh_token: options.refreshToken,\r\n client_id: options.clientId ?? DEFAULT_CLIENT_ID,\r\n client_secret: DEFAULT_CLIENT_SECRET,\r\n });\r\n if (!ok) {\r\n throw new Error(`刷新令牌失败 (${status}): ${JSON.stringify(data)}`);\r\n }\r\n return tokenResponseFrom(data);\r\n}\r\n\r\n/**\r\n * 撤销指定类型的令牌;best-effort,出错不抛出。\r\n */\r\nexport async function revokeToken(options: {\r\n discovery: OidcDiscovery;\r\n token: string;\r\n tokenTypeHint?: TokenTypeHint;\r\n clientId?: string;\r\n}): Promise<void> {\r\n if (!options.discovery.revocation_endpoint) {\r\n return;\r\n }\r\n const params: Record<string, string> = {\r\n token: options.token,\r\n client_id: options.clientId ?? DEFAULT_CLIENT_ID,\r\n client_secret: DEFAULT_CLIENT_SECRET,\r\n };\r\n if (options.tokenTypeHint) {\r\n params.token_type_hint = options.tokenTypeHint;\r\n }\r\n await postForm(options.discovery.revocation_endpoint, params).catch(() => {\r\n // best-effort\r\n });\r\n}\r\n\r\n/**\r\n * 并发撤销 access_token 与 refresh_token。\r\n */\r\nexport async function revokeAll(options: {\r\n discovery: OidcDiscovery;\r\n accessToken?: string;\r\n refreshToken?: string;\r\n clientId?: string;\r\n}): Promise<void> {\r\n if (!options.discovery.revocation_endpoint) return;\r\n const jobs: Promise<void>[] = [];\r\n if (options.refreshToken) {\r\n jobs.push(revokeToken({\r\n discovery: options.discovery,\r\n token: options.refreshToken,\r\n tokenTypeHint: TOKEN_HINT.REFRESH,\r\n clientId: options.clientId,\r\n }));\r\n }\r\n if (options.accessToken) {\r\n jobs.push(revokeToken({\r\n discovery: options.discovery,\r\n token: options.accessToken,\r\n tokenTypeHint: TOKEN_HINT.ACCESS,\r\n clientId: options.clientId,\r\n }));\r\n }\r\n await Promise.all(jobs);\r\n}\r\n\r\nfunction tokenResponseFrom(data: Record<string, unknown>): TokenResponse {\r\n return {\r\n access_token: String(data.access_token ?? ''),\r\n refresh_token: typeof data.refresh_token === 'string' ? data.refresh_token : undefined,\r\n id_token: typeof data.id_token === 'string' ? data.id_token : undefined,\r\n token_type: typeof data.token_type === 'string' ? data.token_type : DEFAULT_TOKEN_TYPE,\r\n expires_in: typeof data.expires_in === 'number' ? data.expires_in : undefined,\r\n scope: typeof data.scope === 'string' ? data.scope : undefined,\r\n };\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport {\r\n loadTokens,\r\n isExpired,\r\n mergeRefreshedTokens,\r\n saveTokens,\r\n StoredTokens,\r\n} from '../../utils/auth-storage.js';\r\nimport { discover, refreshTokens } from '../../utils/oauth-device.js';\r\n\r\nconst MARKETPLACE_BASE_URL = 'https://solazah.seayona.com/marketplace';\r\n\r\ninterface PublishOptions {\r\n file?: string;\r\n dir?: string;\r\n}\r\n\r\nasync function ensureAuth(): Promise<StoredTokens> {\r\n const tokens = await loadTokens();\r\n if (!tokens) {\r\n console.log(chalk.red('当前未登录,请先执行:solazah-cli account login'));\r\n process.exit(1);\r\n }\r\n\r\n if (isExpired(tokens) && tokens.refresh_token) {\r\n const spinner = ora('令牌已过期,正在刷新...').start();\r\n try {\r\n const discovery = await discover(tokens.issuer);\r\n const refreshed = await refreshTokens({ discovery, refreshToken: tokens.refresh_token });\r\n const updated = mergeRefreshedTokens(tokens, refreshed, discovery.issuer);\r\n await saveTokens(updated);\r\n spinner.succeed('令牌刷新成功');\r\n return updated;\r\n } catch {\r\n spinner.fail('令牌刷新失败,请重新登录:solazah-cli account login');\r\n process.exit(1);\r\n }\r\n }\r\n\r\n return tokens;\r\n}\r\n\r\n/**\r\n * 根据 manifest.json 的 name 和 version 推算 release/ 中的 .tgz 文件路径\r\n * npm pack 生成的文件名规则:@scope/pkg-name -> scope-pkg-name-version.tgz\r\n */\r\nfunction findTgzByManifest(projectDir: string): string {\r\n const manifestPath = path.join(projectDir, 'manifest.json');\r\n if (!fs.existsSync(manifestPath)) {\r\n console.log(chalk.red('❌ 未找到 manifest.json,无法确定插件包文件名'));\r\n process.exit(1);\r\n }\r\n\r\n const manifest = fs.readJsonSync(manifestPath);\r\n const name: string = manifest.name;\r\n const version: string = manifest.version;\r\n\r\n if (!name || !version) {\r\n console.log(chalk.red('❌ manifest.json 中缺少 name 或 version 字段'));\r\n process.exit(1);\r\n }\r\n\r\n // @scope/pkg-name -> scope-pkg-name, pkg-name -> pkg-name\r\n const tgzName = name.replace(/^@/, '').replace(/\\//, '-');\r\n const tgzFile = `${tgzName}-${version}.tgz`;\r\n\r\n return path.join(projectDir, 'release', tgzFile);\r\n}\r\n\r\nexport async function publishPluginCommand(options: PublishOptions): Promise<void> {\r\n let filePath: string;\r\n\r\n if (options.file) {\r\n filePath = path.resolve(options.file);\r\n } else {\r\n const projectDir = path.resolve(options.dir || '.');\r\n filePath = findTgzByManifest(projectDir);\r\n }\r\n\r\n // 检查文件是否存在\r\n if (!await fs.pathExists(filePath)) {\r\n console.log(chalk.red(`文件不存在:${filePath}`));\r\n process.exit(1);\r\n }\r\n\r\n // 检查文件扩展名\r\n if (!filePath.endsWith('.tgz')) {\r\n console.log(chalk.red('仅支持 .tgz 格式的插件包'));\r\n process.exit(1);\r\n }\r\n\r\n // 检查登录状态 & 刷新令牌\r\n const tokens = await ensureAuth();\r\n\r\n const fileName = path.basename(filePath);\r\n const spinner = ora(`正在发布插件包:${fileName}`).start();\r\n\r\n try {\r\n const fileBuffer = await fs.readFile(filePath);\r\n const blob = new Blob([fileBuffer], { type: 'application/gzip' });\r\n\r\n const formData = new FormData();\r\n formData.append('package', blob, fileName);\r\n\r\n const res = await fetch(`${MARKETPLACE_BASE_URL}/api/v1/publish-requests`, {\r\n method: 'POST',\r\n headers: {\r\n Authorization: `Bearer ${tokens.access_token}`,\r\n },\r\n body: formData,\r\n });\r\n\r\n const data = await res.json().catch(() => ({}));\r\n\r\n if (!res.ok) {\r\n const code = (data as Record<string, unknown>).code ?? '';\r\n const message = (data as Record<string, unknown>).message ?? res.statusText;\r\n if (code === 'DUPLICATE_SUBMISSION') {\r\n spinner.fail('发布失败:该版本已提交过发布申请,请勿重复提交');\r\n } else {\r\n spinner.fail(`发布失败 (${res.status}): ${message}`);\r\n }\r\n process.exit(1);\r\n }\r\n\r\n const result = data as Record<string, unknown>;\r\n spinner.succeed('插件发布申请已提交');\r\n console.log('');\r\n console.log(chalk.green(' 申请详情:'));\r\n console.log(` 申请ID: ${result.pluginPublishRequestId}`);\r\n console.log(` 插件名称: ${result.pluginName}`);\r\n console.log(` 状态: ${result.status}`);\r\n console.log('');\r\n console.log(chalk.gray(' 发布申请已提交,等待管理员审核。'));\r\n } catch (err) {\r\n spinner.fail(`发布失败:${(err as Error).message}`);\r\n process.exit(1);\r\n }\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport semver from 'semver';\r\nimport { promisify } from 'util';\r\nimport { exec } from 'child_process';\r\nimport conventionalChangelog from 'conventional-changelog-core';\r\n\r\nconst execAsync = promisify(exec);\r\n\r\ninterface VersionOptions {\r\n dir?: string;\r\n releaseAs?: string;\r\n}\r\n\r\n// 读取 .versionrc(如果存在),提取自定义 commit type → section 映射\r\nfunction loadVersionrc(projectDir: string): Record<string, unknown> {\r\n for (const name of ['.versionrc', '.versionrc.json']) {\r\n const p = path.join(projectDir, name);\r\n if (fs.existsSync(p)) {\r\n try {\r\n return fs.readJsonSync(p);\r\n } catch {\r\n // ignore malformed versionrc\r\n }\r\n }\r\n }\r\n return {};\r\n}\r\n\r\n// 把 .versionrc 中的 types 数组转成 conventional-changelog-core 需要的 parserOpts / writerOpts\r\nfunction buildChangelogConfig(versionrc: Record<string, unknown>) {\r\n const types = versionrc.types as Array<{ type: string; section?: string; hidden?: boolean }> | undefined;\r\n if (!types) return {};\r\n\r\n const parserOpts = {\r\n noteKeywords: ['BREAKING CHANGE', 'BREAKING-CHANGE'],\r\n };\r\n\r\n const writerOpts = {\r\n transform: (commit: Record<string, unknown>) => {\r\n const match = types.find((t) => t.type === commit.type);\r\n if (!match) return false;\r\n if (match.hidden) return false;\r\n commit.type = match.section ?? commit.type;\r\n return commit;\r\n },\r\n groupBy: 'type',\r\n commitGroupsSort: 'title',\r\n commitsSort: ['scope', 'subject'],\r\n };\r\n\r\n return { parserOpts, writerOpts };\r\n}\r\n\r\n// 生成本次发布的 changelog 片段\r\nasync function generateChangelogEntry(projectDir: string, newVersion: string, versionrc: Record<string, unknown>): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n const { parserOpts, writerOpts } = buildChangelogConfig(versionrc);\r\n const header = (versionrc.header as string | undefined) ?? '# CHANGELOG\\n\\n';\r\n\r\n const stream = conventionalChangelog(\r\n { preset: 'angular', parserOpts, writerOpts } as Parameters<typeof conventionalChangelog>[0],\r\n { version: newVersion } as Parameters<typeof conventionalChangelog>[1],\r\n undefined,\r\n undefined,\r\n undefined,\r\n );\r\n\r\n let content = '';\r\n stream.on('data', (chunk: Buffer | string) => { content += chunk.toString(); });\r\n stream.on('end', () => resolve(content));\r\n stream.on('error', reject);\r\n\r\n // cwd 必须在插件项目目录,conventional-changelog 默认从当前目录读 git log\r\n (stream as any).cwd = projectDir;\r\n });\r\n}\r\n\r\n// 更新文件中的 version 字段(package.json / manifest.json)\r\nfunction bumpVersionInFile(filePath: string, newVersion: string): void {\r\n if (!fs.existsSync(filePath)) return;\r\n const content = fs.readFileSync(filePath, 'utf-8');\r\n // 只替换顶层 \"version\" 字段,避免误改嵌套结构\r\n const updated = content.replace(\r\n /^(\\s*\"version\"\\s*:\\s*)\"[^\"]*\"/m,\r\n `$1\"${newVersion}\"`,\r\n );\r\n fs.writeFileSync(filePath, updated, 'utf-8');\r\n}\r\n\r\n// 追加 changelog 片段到 CHANGELOG.md(在现有内容之前)\r\nfunction prependChangelog(projectDir: string, entry: string, header: string): void {\r\n const changelogPath = path.join(projectDir, 'CHANGELOG.md');\r\n const existing = fs.existsSync(changelogPath) ? fs.readFileSync(changelogPath, 'utf-8') : '';\r\n // 去掉已有的 header 行,再重新写\r\n const body = existing.startsWith(header.trim())\r\n ? existing.slice(header.trim().length).trimStart()\r\n : existing;\r\n fs.writeFileSync(changelogPath, header.trim() + '\\n\\n' + entry + (body ? '\\n' + body : ''), 'utf-8');\r\n}\r\n\r\nexport async function versionPluginCommand(options: VersionOptions): Promise<void> {\r\n const projectDir = path.resolve(options.dir || '.');\r\n\r\n const packageJsonPath = path.join(projectDir, 'package.json');\r\n const manifestPath = path.join(projectDir, 'manifest.json');\r\n\r\n if (!(await fs.pathExists(packageJsonPath))) {\r\n console.error(chalk.red('❌ No package.json found.'));\r\n process.exit(1);\r\n }\r\n\r\n const pkg = await fs.readJson(packageJsonPath);\r\n const currentVersion: string = pkg.version ?? '0.0.0';\r\n\r\n // 1. 确定 release type\r\n let releaseType = options.releaseAs || 'patch';\r\n\r\n // 2. 计算新版本号\r\n const newVersion = semver.inc(currentVersion, releaseType as semver.ReleaseType);\r\n if (!newVersion) {\r\n console.error(chalk.red(`❌ Invalid version: ${currentVersion} + ${releaseType}`));\r\n process.exit(1);\r\n }\r\n\r\n const versionrc = loadVersionrc(projectDir);\r\n const header = (versionrc.header as string | undefined) ?? '# CHANGELOG\\n\\n';\r\n\r\n console.log(chalk.gray(`\\n ${currentVersion} → ${chalk.green(newVersion)}\\n`));\r\n\r\n // 3. 生成 changelog 片段\r\n const changelogSpinner = ora('Generating changelog...').start();\r\n let changelogEntry = '';\r\n try {\r\n const prevCwd = process.cwd();\r\n process.chdir(projectDir);\r\n changelogEntry = await generateChangelogEntry(projectDir, newVersion, versionrc);\r\n process.chdir(prevCwd);\r\n changelogSpinner.succeed('Changelog generated');\r\n } catch (err: any) {\r\n changelogSpinner.warn(`Changelog generation skipped: ${err.message}`);\r\n }\r\n\r\n // 4. 更新版本号\r\n const bumpSpinner = ora('Bumping version files...').start();\r\n bumpVersionInFile(packageJsonPath, newVersion);\r\n bumpVersionInFile(path.join(projectDir, 'package-lock.json'), newVersion);\r\n bumpVersionInFile(manifestPath, newVersion);\r\n bumpSpinner.succeed('Version files updated');\r\n\r\n // 5. 写入 CHANGELOG.md\r\n if (changelogEntry.trim()) {\r\n prependChangelog(projectDir, changelogEntry.trim(), header);\r\n ora('').succeed('CHANGELOG.md updated');\r\n }\r\n\r\n // 6. git commit + tag\r\n const gitSpinner = ora('Creating git commit and tag...').start();\r\n try {\r\n const filesToStage = ['package.json', 'manifest.json', 'CHANGELOG.md', 'package-lock.json']\r\n .filter((f) => fs.existsSync(path.join(projectDir, f)));\r\n\r\n await execAsync(`git add ${filesToStage.join(' ')}`, { cwd: projectDir });\r\n await execAsync(`git commit -m \"chore(release): ${newVersion}\"`, { cwd: projectDir });\r\n\r\n // 尝试打 tag,已存在则跳过\r\n try {\r\n await execAsync(`git tag -a v${newVersion} -m \"chore(release): ${newVersion}\"`, { cwd: projectDir });\r\n } catch (tagErr: any) {\r\n if (!tagErr.message?.includes('already exists')) throw tagErr;\r\n gitSpinner.warn(`Tag v${newVersion} already exists, skipping`);\r\n return;\r\n }\r\n\r\n gitSpinner.succeed(`Tagged release v${newVersion}`);\r\n } catch (err: any) {\r\n gitSpinner.fail(`Git operations failed: ${err.stderr || err.message}`);\r\n process.exit(1);\r\n }\r\n\r\n console.log(chalk.green.bold(`\\n✅ Version bumped to ${newVersion}\\n`));\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport chalk from 'chalk';\r\nimport { versionPluginCommand } from './version.js';\r\nimport { packagePluginCommand } from './package.js';\r\nimport { publishPluginCommand } from './publish.js';\r\n\r\ninterface ReleaseOptions {\r\n dir?: string;\r\n}\r\n\r\nexport async function releasePluginCommand(options: ReleaseOptions): Promise<void> {\r\n const projectDir = path.resolve(options.dir || '.');\r\n\r\n if (!(await fs.pathExists(path.join(projectDir, 'package.json')))) {\r\n console.error(chalk.red('❌ No package.json found in the target directory.'));\r\n process.exit(1);\r\n }\r\n\r\n // 1. 版本升级\r\n await versionPluginCommand({ dir: projectDir });\r\n\r\n // 2. 打包\r\n await packagePluginCommand({ dir: projectDir });\r\n\r\n // 3. 发布\r\n await publishPluginCommand({ dir: projectDir });\r\n}\r\n","import chalk from 'chalk';\nimport { getAvailableTemplates } from '../../utils/file.js';\n\ninterface TemplatesOptions {\n json?: boolean;\n}\n\nexport async function listTemplatesCommand(options: TemplatesOptions): Promise<void> {\n const templates = await getAvailableTemplates();\n\n if (options.json) {\n console.log(JSON.stringify(templates, null, 2));\n return;\n }\n\n if (templates.length === 0) {\n console.log(chalk.yellow('No templates available.'));\n return;\n }\n\n console.log(chalk.bold('\\nAvailable templates:\\n'));\n for (const tpl of templates) {\n console.log(` ${chalk.cyan(tpl.name)} ${chalk.gray('v' + tpl.version)}`);\n console.log(` ${tpl.displayName}`);\n console.log(` ${chalk.gray(tpl.description)}`);\n if (tpl.features?.length) {\n console.log(chalk.gray(` Features: ${tpl.features.join(', ')}`));\n }\n console.log();\n }\n}\n","import { spawn } from 'child_process';\r\n\r\n/**\r\n * 跨平台打开浏览器,best-effort:失败时静默返回 false,由调用方提示用户手动打开。\r\n */\r\nexport function openBrowser(url: string): boolean {\r\n try {\r\n const platform = process.platform;\r\n let command: string;\r\n let args: string[];\r\n\r\n if (platform === 'win32') {\r\n command = 'cmd';\r\n args = ['/c', 'start', '\"\"', url.replace(/&/g, '^&')];\r\n } else if (platform === 'darwin') {\r\n command = 'open';\r\n args = [url];\r\n } else {\r\n command = 'xdg-open';\r\n args = [url];\r\n }\r\n\r\n const child = spawn(command, args, {\r\n detached: true,\r\n stdio: 'ignore',\r\n shell: false,\r\n });\r\n child.on('error', () => {\r\n // ignore; caller already printed the URL\r\n });\r\n child.unref();\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n","import {\r\n isExpired,\r\n mergeRefreshedTokens,\r\n saveTokens,\r\n StoredTokens,\r\n} from '../../utils/auth-storage.js';\r\nimport {\r\n DEFAULT_ISSUER,\r\n discover,\r\n OidcDiscovery,\r\n refreshTokens,\r\n} from '../../utils/oauth-device.js';\r\n\r\nexport interface CommonOptions {\r\n issuer?: string;\r\n}\r\n\r\nexport const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\r\n\r\nexport function loadDiscovery(issuer?: string): Promise<OidcDiscovery> {\r\n return discover(issuer || DEFAULT_ISSUER);\r\n}\r\n\r\nexport async function ensureValidTokens(\r\n discovery: OidcDiscovery,\r\n tokens: StoredTokens,\r\n): Promise<StoredTokens> {\r\n if (!isExpired(tokens)) {\r\n return tokens;\r\n }\r\n if (!tokens.refresh_token) {\r\n throw new Error('访问令牌已过期且无 refresh_token,请重新登录');\r\n }\r\n const refreshed = await refreshTokens({\r\n discovery,\r\n refreshToken: tokens.refresh_token,\r\n });\r\n const stored = mergeRefreshedTokens(tokens, refreshed, discovery.issuer);\r\n await saveTokens(stored);\r\n return stored;\r\n}\r\n","import chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport {\r\n saveTokens,\r\n toStoredTokens,\r\n} from '../../utils/auth-storage.js';\r\nimport {\r\n fetchUserInfo,\r\n pollToken,\r\n requestDeviceCode,\r\n} from '../../utils/oauth-device.js';\r\nimport { openBrowser } from '../../utils/open-browser.js';\r\nimport { CommonOptions, delay, loadDiscovery } from './shared.js';\r\n\r\nexport async function loginCommand(options: CommonOptions): Promise<void> {\r\n console.log(chalk.cyan.bold('\\nSolazah 账户登录\\n'));\r\n\r\n const spinner = ora('加载授权服务器元数据...').start();\r\n let discovery;\r\n try {\r\n discovery = await loadDiscovery(options.issuer);\r\n spinner.succeed('已加载授权服务器元数据');\r\n } catch (err) {\r\n spinner.fail('加载授权服务器元数据失败');\r\n console.error(chalk.red((err as Error).message));\r\n process.exit(1);\r\n }\r\n\r\n spinner.start('申请设备授权码...');\r\n let device;\r\n try {\r\n device = await requestDeviceCode({ discovery });\r\n spinner.succeed('已获取设备授权码');\r\n } catch (err) {\r\n spinner.fail('申请设备授权码失败');\r\n console.error(chalk.red((err as Error).message));\r\n process.exit(1);\r\n }\r\n\r\n const verificationUri = device.verification_uri_complete || device.verification_uri;\r\n\r\n console.log();\r\n console.log(chalk.bold('请在浏览器中完成授权:'));\r\n if (device.verification_uri_complete) {\r\n console.log(` 一键链接:${chalk.cyan(device.verification_uri_complete)}`);\r\n }\r\n console.log(` 用户码 :${chalk.yellow.bold(device.user_code)}`);\r\n console.log(` 有效期 :${device.expires_in} 秒`);\r\n console.log();\r\n\r\n const opened = openBrowser(verificationUri);\r\n console.log(chalk.gray(\r\n opened\r\n ? '已尝试为你打开浏览器,若未自动打开请手动访问上面的一键链接。'\r\n : '请手动在浏览器中打开上面的一键链接。',\r\n ));\r\n console.log();\r\n\r\n // RFC 8628 建议的最小轮询间隔为 5 秒\r\n let interval = Math.max(device.interval ?? 5, 5);\r\n const deadline = Date.now() + device.expires_in * 1000;\r\n\r\n const poll = ora('等待用户授权...').start();\r\n while (Date.now() < deadline) {\r\n await delay(interval * 1000);\r\n let result;\r\n try {\r\n result = await pollToken({ discovery, deviceCode: device.device_code });\r\n } catch (err) {\r\n // 网络抖动等瞬时异常:继续轮询直到截止时间\r\n poll.text = `轮询出错,将重试:${(err as Error).message}`;\r\n continue;\r\n }\r\n\r\n if (result.status === 'success' && result.tokens) {\r\n const stored = toStoredTokens(result.tokens, discovery.issuer);\r\n await saveTokens(stored);\r\n poll.succeed('授权成功');\r\n\r\n if (discovery.userinfo_endpoint) {\r\n try {\r\n const info = await fetchUserInfo({\r\n discovery,\r\n accessToken: stored.access_token,\r\n });\r\n console.log();\r\n console.log(chalk.green('已登录:'));\r\n console.log(` sub :${info.sub}`);\r\n const username = info.username ?? info.preferred_username;\r\n if (username) console.log(` username:${username}`);\r\n if (info.name) console.log(` name :${info.name}`);\r\n if (info.email) console.log(` email :${info.email}`);\r\n } catch {\r\n // 打印用户信息失败不影响登录结果\r\n }\r\n }\r\n console.log();\r\n return;\r\n }\r\n\r\n if (result.status === 'pending') continue;\r\n\r\n if (result.status === 'slow_down') {\r\n interval += 5;\r\n poll.text = `服务器要求降低轮询频率(${interval}s)...`;\r\n continue;\r\n }\r\n\r\n if (result.status === 'denied') {\r\n poll.fail('用户拒绝了授权');\r\n process.exit(1);\r\n }\r\n\r\n if (result.status === 'expired') {\r\n poll.fail('设备码已过期,请重新执行登录命令');\r\n process.exit(1);\r\n }\r\n\r\n // 其他错误视为瞬时错误,继续轮询直到截止时间\r\n poll.text = `授权失败(将重试):${result.error}${result.errorDescription ? ' - ' + result.errorDescription : ''}`;\r\n }\r\n poll.fail('等待授权超时,请重新执行登录命令');\r\n process.exit(1);\r\n}\r\n","import chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport {\r\n clearTokens,\r\n loadTokens,\r\n} from '../../utils/auth-storage.js';\r\nimport { revokeAll } from '../../utils/oauth-device.js';\r\nimport { CommonOptions, loadDiscovery } from './shared.js';\r\n\r\nexport async function logoutCommand(options: CommonOptions): Promise<void> {\r\n const tokens = await loadTokens();\r\n if (!tokens) {\r\n console.log(chalk.yellow('当前未登录。'));\r\n return;\r\n }\r\n\r\n const spinner = ora('正在登出...').start();\r\n try {\r\n const discovery = await loadDiscovery(tokens.issuer || options.issuer).catch(() => null);\r\n if (discovery) {\r\n await revokeAll({\r\n discovery,\r\n accessToken: tokens.access_token,\r\n refreshToken: tokens.refresh_token,\r\n });\r\n }\r\n await clearTokens();\r\n spinner.succeed('已登出');\r\n } catch (err) {\r\n await clearTokens();\r\n spinner.warn('本地凭证已清除,但撤销令牌时发生错误');\r\n console.error(chalk.gray((err as Error).message));\r\n }\r\n}\r\n","import chalk from 'chalk';\r\nimport { loadTokens } from '../../utils/auth-storage.js';\r\nimport { fetchUserInfo } from '../../utils/oauth-device.js';\r\nimport { CommonOptions, ensureValidTokens, loadDiscovery } from './shared.js';\r\n\r\nexport async function userinfoCommand(options: CommonOptions): Promise<void> {\r\n const tokens = await loadTokens();\r\n if (!tokens) {\r\n console.log(chalk.yellow('当前未登录,请先执行:solazah-cli account login'));\r\n process.exit(1);\r\n }\r\n\r\n try {\r\n const discovery = await loadDiscovery(tokens.issuer || options.issuer);\r\n const valid = await ensureValidTokens(discovery, tokens);\r\n const info = await fetchUserInfo({\r\n discovery,\r\n accessToken: valid.access_token,\r\n });\r\n console.log(JSON.stringify(info, null, 2));\r\n } catch (err) {\r\n console.error(chalk.red('获取用户信息失败:'), (err as Error).message);\r\n process.exit(1);\r\n }\r\n}\r\n","#!/usr/bin/env node\r\n\r\nimport { Command } from 'commander';\r\nimport { createPluginCommand } from './commands/plugin/create.js';\r\nimport { packagePluginCommand } from './commands/plugin/package.js';\r\nimport { publishPluginCommand } from './commands/plugin/publish.js';\r\nimport { versionPluginCommand } from './commands/plugin/version.js';\r\nimport { releasePluginCommand } from './commands/plugin/release.js';\r\nimport { listTemplatesCommand } from './commands/plugin/templates.js';\r\nimport { loginCommand } from './commands/account/login.js';\r\nimport { logoutCommand } from './commands/account/logout.js';\r\nimport { userinfoCommand } from './commands/account/userinfo.js';\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name('solazah-cli')\r\n .description('Solazah CLI - Command line tool for Solazah plugin development')\r\n .version(\"0.0.1\");\r\n\r\n// Plugin commands\r\nconst plugin = program\r\n .command('plugin')\r\n .description('Manage Solazah plugins');\r\n\r\nplugin\r\n .command('create')\r\n .description('Create a new Solazah plugin')\r\n .option('-n, --name <name>', 'Plugin name (e.g., my-plugin or @scope/plugin-name)')\r\n .option('-d, --dir <directory>', 'Parent directory; final target is <dir>/<plugin-name>', '.')\r\n .option('--target-dir <path>', 'Full target directory; overrides --dir')\r\n .option('-t, --template <template>', 'Template to use (e.g., react)', 'react')\r\n .option('--display-name <name>', 'Display name (skips prompt if provided)')\r\n .option('--description <desc>', 'Plugin description (skips prompt if provided)')\r\n .option('--author <author>', 'Author (skips prompt if provided)')\r\n .option('--port <port>', 'Dev server port (skips prompt if provided)', (v) => parseInt(v, 10))\r\n .option('-y, --yes', 'Skip all prompts and accept defaults for any missing answers')\r\n .option('--skip-install', 'Skip npm install')\r\n .action(createPluginCommand);\r\n\r\nplugin\r\n .command('package')\r\n .description('Build and package a Solazah plugin for distribution')\r\n .option('-d, --dir <directory>', 'Plugin project directory', '.')\r\n .action(packagePluginCommand);\r\n\r\nplugin\r\n .command('publish')\r\n .description('Publish a plugin package to Solazah marketplace')\r\n .option('-f, --file <file>', 'Path to the .tgz plugin package file')\r\n .option('-d, --dir <directory>', 'Plugin project directory', '.')\r\n .action(publishPluginCommand);\r\n\r\nplugin\r\n .command('version')\r\n .description('Bump plugin version, update CHANGELOG and create git tag')\r\n .option('-d, --dir <directory>', 'Plugin project directory', '.')\r\n .option('-r, --release-as <type>', 'Specify release type: major, minor, patch')\r\n .action(versionPluginCommand);\r\n\r\nplugin\r\n .command('release')\r\n .description('Bump version, package and publish a plugin in one step')\r\n .option('-d, --dir <directory>', 'Plugin project directory', '.')\r\n .action(releasePluginCommand);\r\n\r\nplugin\r\n .command('templates')\r\n .description('List available plugin templates')\r\n .option('--json', 'Output as JSON')\r\n .action(listTemplatesCommand);\r\n\r\n// Account commands (OAuth2 Device Authorization Grant)\r\nconst account = program\r\n .command('account')\r\n .description('Manage Solazah account authentication');\r\n\r\nconst withIssuer = (cmd: Command) =>\r\n cmd.option('--issuer <issuer>', 'OAuth2 issuer URL');\r\n\r\nwithIssuer(account.command('login'))\r\n .description('Login via OAuth2 device code flow')\r\n .action(loginCommand);\r\n\r\nwithIssuer(account.command('logout'))\r\n .description('Logout and revoke stored tokens')\r\n .action(logoutCommand);\r\n\r\nwithIssuer(account.command('userinfo'))\r\n .description('Show current user info')\r\n .action(userinfoCommand);\r\n\r\nprogram.parseAsync().catch((err) => {\r\n console.error(err);\r\n process.exit(1);\r\n});\r\n"],"names":["__filename","__dirname","execAsync","answers"],"mappings":"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"}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  },
5
5
  "name": "@solazah/solazah-cli",
6
6
  "type": "module",
7
- "version": "0.2.20",
7
+ "version": "0.2.23",
8
8
  "description": "Solazah CLI - Command line tool for Solazah plugin development",
9
9
  "author": "",
10
10
  "license": "MIT",
@@ -13,5 +13,15 @@
13
13
  "cli",
14
14
  "plugin",
15
15
  "scaffold"
16
- ]
16
+ ],
17
+ "dependencies": {
18
+ "chalk": "^5.4.1",
19
+ "commander": "^12.1.0",
20
+ "conventional-changelog-core": "^9.0.0",
21
+ "fs-extra": "^11.2.0",
22
+ "inquirer": "^10.0.0",
23
+ "ora": "^8.1.1",
24
+ "semver": "^7.7.4",
25
+ "validate-npm-package-name": "^6.0.0"
26
+ }
17
27
  }
@@ -1,224 +1,3 @@
1
1
  # Solazah Plugin Template
2
2
 
3
- Solazah 桌面应用的插件开发模板项目。
4
-
5
- ## 功能特点
6
-
7
- - 基于 React 19 + TypeScript + Vite
8
- - 集成 Tailwind CSS 4 样式系统
9
- - 支持 shadcn/ui 组件库
10
- - 内置状态管理(Valtio)
11
- - 完整的构建和发布流程
12
- - 支持 Conventional Commits 规范
13
-
14
- ## 快速开始
15
-
16
- ### 1. 安装依赖
17
-
18
- ```bash
19
- npm install
20
- ```
21
-
22
- ### 2. 修改插件信息
23
-
24
- 修改 `manifest.json` 文件中的插件配置:
25
-
26
- ```json
27
- {
28
- "name": "@solazah/your-plugin-name",
29
- "version": "0.0.1",
30
- "displayName": "你的插件名称",
31
- "commands": ["你的插件命令"],
32
- "development": {
33
- "main": "http://localhost:5200",
34
- "openDevTools": true
35
- }
36
- }
37
- ```
38
-
39
- 修改 `package.json` 中的项目信息:
40
-
41
- ```json
42
- {
43
- "name": "@solazah/your-plugin-name",
44
- "version": "0.0.1",
45
- "description": "你的插件描述",
46
- "keywords": ["solazah", "plugin"],
47
- "author": "Your Name",
48
- "license": "MIT"
49
- }
50
- ```
51
-
52
- ### 3. 开发
53
-
54
- ```bash
55
- npm run dev
56
- ```
57
-
58
- 开发服务器将在 `http://localhost:5200` 启动。
59
-
60
- ### 4. 实现插件逻辑
61
-
62
- 在 `src/renderer.js` 中实现插件的核心逻辑:
63
-
64
- ```javascript
65
- // 插件准备就绪
66
- window.ipcRenderer?.on("plugin.onReady", async (event, args) => {
67
- // 初始化插件
68
- // 注册命令
69
- });
70
-
71
- // 命令执行
72
- window.ipcRenderer?.on("plugin.onEnter", async (event, name, args) => {
73
- // 处理命令
74
- });
75
- ```
76
-
77
- ### 5. 创建页面组件
78
-
79
- 在 `src/pages/` 目录下创建你的页面组件,参考 `Home.tsx`。
80
-
81
- ## 项目结构
82
-
83
- ```
84
- .
85
- ├── build/ # 构建脚本
86
- │ └── package.config.js # 发布打包配置
87
- ├── src/
88
- │ ├── components/ # React 组件
89
- │ │ └── ui/ # UI 组件库
90
- │ ├── hooks/ # 自定义 Hooks
91
- │ ├── lib/ # 工具函数
92
- │ ├── pages/ # 页面组件
93
- │ ├── index.css # 全局样式
94
- │ ├── main.tsx # React 应用入口
95
- │ └── renderer.js # 插件系统入口
96
- ├── index.html # HTML 模板
97
- ├── manifest.json # 插件配置
98
- ├── package.json # 项目配置
99
- ├── vite.config.ts # Vite 配置
100
- └── tsconfig.json # TypeScript 配置
101
- ```
102
-
103
- ## 可用命令
104
-
105
- ```bash
106
- # 开发
107
- npm run dev # 启动开发服务器(端口 5200)
108
-
109
- # 构建
110
- npm run build # 生产构建
111
- npm run preview # 预览构建结果
112
-
113
- # 打包发布
114
- npm run package # 构建并打包到 release 目录
115
- npm run publish # 发布到 npm
116
- npm run package:publish # 构建并发布
117
-
118
- # 版本管理
119
- npm run version # 使用 standard-version 更新版本
120
-
121
- # 代码质量
122
- npm run lint # 运行 ESLint
123
- ```
124
-
125
- ## 插件系统 API
126
-
127
- ### IPC 事件
128
-
129
- - `plugin.onReady` - 插件准备就绪时触发
130
- - `plugin.onEnter` - 用户执行命令时触发
131
-
132
- ### IPC 方法
133
-
134
- - `window.ipcRenderer.invoke("plugin.getSetting", key)` - 获取插件设置
135
- - `window.ipcRenderer.invoke("plugin.setSetting", key, value)` - 保存插件设置
136
- - `window.ipcRenderer.invoke("plugin.setCommands", commands)` - 注册命令
137
- - `window.ipcRenderer.invoke("shell.openExternal", url)` - 在浏览器中打开链接
138
- - `window.ipcRenderer.invoke("window.hide")` - 隐藏窗口
139
- - `window.ipcRenderer.invoke("window.goHome")` - 返回主页
140
-
141
- ## 命令注册示例
142
-
143
- ```javascript
144
- await window.ipcRenderer.invoke("plugin.setCommands", [
145
- {
146
- name: "命令名称",
147
- alias: ["别名1", "别名2"],
148
- silence: false, // 是否静默执行
149
- shortcut: "Ctrl+Shift+K", // 快捷键
150
- requiredArgs: false, // 是否需要参数
151
- }
152
- ]);
153
- ```
154
-
155
- ## 使用 shadcn/ui 组件
156
-
157
- 本模板已配置 shadcn/ui,可以使用命令添加组件:
158
-
159
- ```bash
160
- npx shadcn@latest add button
161
- npx shadcn@latest add dialog
162
- npx shadcn@latest add input
163
- # ... 其他组件
164
- ```
165
-
166
- ## 状态管理
167
-
168
- 推荐使用 Valtio 进行状态管理:
169
-
170
- ```typescript
171
- import { proxy, useSnapshot } from 'valtio';
172
-
173
- // 创建状态
174
- const state = proxy({
175
- count: 0,
176
- increment: () => {
177
- state.count++;
178
- }
179
- });
180
-
181
- // 在组件中使用
182
- function Counter() {
183
- const snap = useSnapshot(state);
184
- return (
185
- <button onClick={state.increment}>
186
- Count: {snap.count}
187
- </button>
188
- );
189
- }
190
- ```
191
-
192
- ## Git 提交规范
193
-
194
- 本项目使用 Conventional Commits 规范:
195
-
196
- ```bash
197
- feat: 新功能
198
- fix: Bug 修复
199
- docs: 文档更新
200
- style: 代码格式
201
- refactor: 代码重构
202
- perf: 性能优化
203
- test: 测试
204
- build: 构建系统
205
- chore: 其他修改
206
- ci: CI/CD 配置
207
- ```
208
-
209
- ## 发布流程
210
-
211
- 1. 提交所有更改并确保代码通过 lint 检查
212
- 2. 运行 `npm run version` 生成版本号和 CHANGELOG
213
- 3. 运行 `npm run package:publish` 构建并发布到 npm
214
-
215
- ## 注意事项
216
-
217
- - 开发时确保 Solazah 主应用已启动
218
- - 插件开发端口默认为 5200,如需修改请同步更新 manifest.json
219
- - 使用 HashRouter 而非 BrowserRouter 以确保路由正常工作
220
- - 构建产物在 `release/` 目录,`target/` 是临时目录
221
-
222
- ## 许可证
223
-
224
- MIT
3
+ Solazah 桌面应用的插件开发模板项目。
@@ -0,0 +1,27 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
25
+
26
+ release
27
+ target
@@ -0,0 +1 @@
1
+ registry=https://npm.cnb.cool/seayona/solazah/npm/-/packages/
@@ -0,0 +1,180 @@
1
+ // 宿主(solazah-desktop)经 contextBridge 注入的 window.* API 的环境类型声明。
2
+ // 运行时由宿主 RESOURCE_PRELOAD 注入,插件不打包这些实现,只声明形状。
3
+ export {};
4
+
5
+ export type WorkspaceCategory = "document" | "image" | "audio" | "video" | "resource";
6
+
7
+ export interface FileEntry {
8
+ name: string;
9
+ path: string;
10
+ isDir: boolean;
11
+ size: number;
12
+ mtimeMs: number;
13
+ }
14
+
15
+ export interface FileStat {
16
+ isDir: boolean;
17
+ size: number;
18
+ mtimeMs: number;
19
+ ctimeMs: number;
20
+ }
21
+
22
+ export interface FileChange {
23
+ type: string;
24
+ path: string | null;
25
+ }
26
+
27
+ export interface FileListOptions {
28
+ recursive?: boolean;
29
+ workspaceId?: string | null;
30
+ }
31
+
32
+ export interface SolazahFile {
33
+ read(category: WorkspaceCategory, relPath: string, workspaceId?: string | null): Promise<string>;
34
+ write(category: WorkspaceCategory, relPath: string, content: string, workspaceId?: string | null): Promise<void>;
35
+ readBinary(category: WorkspaceCategory, relPath: string, workspaceId?: string | null): Promise<Uint8Array>;
36
+ writeBinary(category: WorkspaceCategory, relPath: string, data: Uint8Array, workspaceId?: string | null): Promise<void>;
37
+ list(category: WorkspaceCategory, relPath?: string, options?: FileListOptions): Promise<FileEntry[]>;
38
+ stat(category: WorkspaceCategory, relPath: string, workspaceId?: string | null): Promise<FileStat | null>;
39
+ exists(category: WorkspaceCategory, relPath: string, workspaceId?: string | null): Promise<boolean>;
40
+ mkdir(category: WorkspaceCategory, relPath: string, workspaceId?: string | null): Promise<void>;
41
+ remove(category: WorkspaceCategory, relPath: string, workspaceId?: string | null): Promise<void>;
42
+ rename(category: WorkspaceCategory, fromRel: string, toRel: string, workspaceId?: string | null): Promise<void>;
43
+ resolve(category: WorkspaceCategory, relPath?: string, workspaceId?: string | null): Promise<string>;
44
+ fileUrl(category: WorkspaceCategory, relPath?: string, workspaceId?: string | null): Promise<string>;
45
+ relativePath(fromCategory: WorkspaceCategory, fromRel: string, toCategory: WorkspaceCategory, toRel: string, workspaceId?: string | null): Promise<string>;
46
+ watch(category: WorkspaceCategory, onChange: (change: FileChange) => void, workspaceId?: string | null): () => void;
47
+ }
48
+
49
+ export interface HttpRequest {
50
+ method?: string;
51
+ headers?: Record<string, string>;
52
+ body?: string | null;
53
+ timeoutMs?: number;
54
+ maxBytes?: number;
55
+ }
56
+
57
+ export interface HttpResponse {
58
+ ok: boolean;
59
+ status: number;
60
+ statusText: string;
61
+ headers: Record<string, string>;
62
+ finalUrl: string;
63
+ body: string;
64
+ }
65
+
66
+ export interface SolazahHttp {
67
+ fetch(url: string, request?: HttpRequest): Promise<HttpResponse>;
68
+ }
69
+
70
+ export interface KbSearchHit {
71
+ path: string;
72
+ title: string;
73
+ chunk: string;
74
+ distance: number;
75
+ }
76
+
77
+ export interface SolazahKb {
78
+ search(query: string, k?: number): Promise<KbSearchHit[]>;
79
+ reindex(force?: boolean): Promise<{ok: boolean}>;
80
+ }
81
+
82
+ export interface WorkspaceDto {
83
+ id: string;
84
+ name: string;
85
+ path: string;
86
+ kind: "local" | "cloud";
87
+ state: "active" | "indexing" | "stale" | "disabled";
88
+ isDefault: boolean;
89
+ }
90
+
91
+ export interface SolazahWorkspace {
92
+ list(): Promise<WorkspaceDto[]>;
93
+ get(id: string): Promise<WorkspaceDto | null>;
94
+ touch(id: string): Promise<void>;
95
+ }
96
+
97
+ export interface SolazahIpcRenderer {
98
+ on(channel: string, listener: (event: unknown, ...args: any[]) => void): Promise<unknown>;
99
+ once(channel: string, listener: (event: unknown, ...args: any[]) => void): Promise<unknown>;
100
+ off(channel: string, listener: (...args: any[]) => void): Promise<unknown>;
101
+ send(channel: string, ...args: any[]): Promise<void>;
102
+ invoke<T = any>(channel: string, ...args: any[]): Promise<T>;
103
+ }
104
+
105
+ export interface PluginFloatingOptions {
106
+ width?: number;
107
+ height?: number;
108
+ minWidth?: number;
109
+ minHeight?: number;
110
+ x?: number;
111
+ y?: number;
112
+ alwaysOnTop?: boolean;
113
+ resizable?: boolean;
114
+ }
115
+
116
+ export interface SolazahPlugin {
117
+ /** 给 Otta(copilot)agent 注册可调用工具:schema 为 JSON Schema(或 null 表示无参)。 */
118
+ tool(name: string, description: string, schema: unknown | null, cb: (args: any) => any): any;
119
+ getTools(): Array<{name: string; description: string; schema: unknown}>;
120
+ onAction(command: string, action: string, handler: (args: any) => any): Promise<void>;
121
+ onSearch(command: string, handler: (args: any) => any): Promise<void>;
122
+ setCommands(items: any[]): Promise<any>;
123
+ registerCommands(items: any[]): Promise<any>;
124
+ unregisterCommands(commands: string[]): Promise<any>;
125
+ hide(): Promise<any>;
126
+ setHeight(height: number): Promise<any>;
127
+ floating(options?: PluginFloatingOptions): Promise<any>;
128
+ toggleAlwaysOnTop(): Promise<any>;
129
+ toggleDevTools(): Promise<any>;
130
+ info(): Promise<any>;
131
+ }
132
+
133
+ export interface SolazahClipboard {
134
+ read(): Promise<unknown>;
135
+ write(data: {type: "text" | "image" | "fileName"; content: string}): Promise<void>;
136
+ clear(): Promise<void>;
137
+ }
138
+
139
+ export interface SolazahPluginCommand {
140
+ open(commandName?: string, payload?: unknown): Promise<unknown>;
141
+ enter(commandName?: string, payload?: unknown): Promise<unknown>;
142
+ }
143
+
144
+ export interface FileFilter {
145
+ name: string;
146
+ extensions: string[];
147
+ }
148
+
149
+ export interface SolazahSystemWindow {
150
+ saveText(content: string, options?: {defaultName?: string; filters?: FileFilter[]}): Promise<string | null>;
151
+ toast(type: "info" | "success" | "warn" | "error", text: string): Promise<unknown>;
152
+ }
153
+
154
+ export interface SolazahShell {
155
+ openExternal(url: string): Promise<void>;
156
+ openPath(path: string): Promise<void>;
157
+ showItemInFolder(path: string): Promise<void>;
158
+ }
159
+
160
+ export interface SolazahEventBus {
161
+ on(event: string, handler: (data: unknown) => void): () => void;
162
+ off(event: string, handler: (data: unknown) => void): void;
163
+ emit(event: string, data: unknown): void;
164
+ }
165
+
166
+ declare global {
167
+ interface Window {
168
+ file: SolazahFile;
169
+ http: SolazahHttp;
170
+ kb: SolazahKb;
171
+ workspace: SolazahWorkspace;
172
+ ipcRenderer: SolazahIpcRenderer;
173
+ plugin: SolazahPlugin;
174
+ pluginCommand: SolazahPluginCommand;
175
+ systemShell: SolazahShell;
176
+ systemClipboard: SolazahClipboard;
177
+ systemWindow: SolazahSystemWindow;
178
+ eventBus: SolazahEventBus;
179
+ }
180
+ }
@@ -7,7 +7,7 @@ import Home from "@/renderer/pages/Home.tsx";
7
7
  console.log("[{{PLUGIN_NAME}}] plugin loaded!");
8
8
 
9
9
  // 插件准备就绪时触发
10
- window.ipcRenderer?.on("plugin.onReady", async (event, args) => {
10
+ window.ipcRenderer?.on("plugin.onReady", async (_event, args) => {
11
11
  console.log("[{{PLUGIN_NAME}}] onReady!", args);
12
12
 
13
13
  // 在这里初始化你的插件
@@ -25,7 +25,7 @@ window.ipcRenderer?.on("plugin.onReady", async (event, args) => {
25
25
  });
26
26
 
27
27
  // 用户执行命令时触发
28
- window.ipcRenderer?.on("plugin.onEnter", async (event, name, args) => {
28
+ window.ipcRenderer?.on("plugin.onEnter", async (_event, name, args) => {
29
29
  console.log("[{{PLUGIN_NAME}}] onEnter!", name, args);
30
30
 
31
31
  // 如果是主命令,显示插件界面
@@ -14,6 +14,12 @@
14
14
  "noEmit": true,
15
15
  "jsx": "react-jsx",
16
16
 
17
+ /* Path alias */
18
+ "baseUrl": ".",
19
+ "paths": {
20
+ "@/*": ["./src/*"]
21
+ },
22
+
17
23
  /* Linting */
18
24
  "strict": true,
19
25
  "noUnusedLocals": true,
@@ -13,7 +13,7 @@ export default defineConfig({
13
13
  },
14
14
  },
15
15
  build: {
16
- outDir: path.resolve(__dirname, "target"),
16
+ outDir: path.resolve(__dirname, "dist"),
17
17
  emptyOutDir: true
18
18
  },
19
19
  plugins: [
package/utils/file.d.ts CHANGED
@@ -15,6 +15,8 @@ export declare function isDirectoryEmpty(dirPath: string): Promise<boolean>;
15
15
  export declare function ensureDirectory(dirPath: string): Promise<void>;
16
16
  /**
17
17
  * 获取模板根目录路径
18
+ * 发布产物中 index.js 与 templates/ 同级(见 package.config.js 的 filesToCopy),
19
+ * 所以 __dirname 即包根目录。
18
20
  */
19
21
  export declare function getTemplatesRootDir(): string;
20
22
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/utils/file.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOxE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAsBrE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQxF;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,UAAU,GAAE,MAAM,EAA4D,GAC7E,OAAO,CAAC,IAAI,CAAC,CAiBf"}
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/utils/file.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOxE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAsBrE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQxF;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,UAAU,GAAE,MAAM,EAA4D,GAC7E,OAAO,CAAC,IAAI,CAAC,CAiBf"}
@@ -1,86 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { fileURLToPath } from "url";
4
-
5
- const filename = fileURLToPath(import.meta.url);
6
- const dirname = path.dirname(filename);
7
-
8
- const rootDir = path.join(dirname, "./");
9
- const distDir = path.join(dirname, "./release");
10
-
11
- const filesToCopy = [
12
- "CHANGELOG.md",
13
- ];
14
-
15
- function cleanDistDir() {
16
- if (fs.existsSync(distDir)) {
17
- fs.rmSync(distDir, { recursive: true, force: true });
18
- }
19
- fs.mkdirSync(distDir, { recursive: true });
20
- console.log("初始化发布目录");
21
- }
22
-
23
- function copyFiles() {
24
- filesToCopy.forEach((file) => {
25
- if (typeof file === "string") {
26
- const sourcePath = path.resolve(rootDir, file);
27
- const destPath = path.resolve(distDir, file);
28
-
29
- fs.cpSync(sourcePath, destPath, { recursive: true });
30
-
31
- console.log(`复制: ${file}`);
32
- }
33
- if (typeof file === "object") {
34
- const sourcePath = path.resolve(rootDir, file?.from);
35
- const destPath = path.resolve(distDir, file?.to);
36
-
37
- fs.cpSync(sourcePath, destPath, { recursive: true });
38
-
39
- console.log(`复制: ${file?.from} 到目录: ${file?.to}`);
40
- }
41
- });
42
- }
43
-
44
- function processPackageJson() {
45
- const packageJsonPath = path.resolve(rootDir, "package.json");
46
- const packageData = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
47
-
48
- const cleanedPackageJson = {
49
- name: packageData.name,
50
- main: packageData.main,
51
- type: packageData.type,
52
- version: packageData.version,
53
- description: packageData.description,
54
- author: packageData.author,
55
- license: packageData.license,
56
- homepage: packageData.homepage,
57
- repository: packageData.repository,
58
- keywords: packageData.keywords,
59
- peerDependencies: packageData.peerDependencies
60
- };
61
-
62
- const destPackageJsonPath = path.resolve(distDir, "package.json");
63
- fs.writeFileSync(destPackageJsonPath, JSON.stringify(cleanedPackageJson, null, 2));
64
- console.log("处理 package.json 完成");
65
- }
66
-
67
- function cleanTargetDir() {
68
- const targetDir = path.join(rootDir, "target");
69
- if (fs.existsSync(targetDir)) {
70
- fs.rmSync(targetDir, { recursive: true, force: true });
71
- console.log("清理 target 目录完成");
72
- }
73
- }
74
-
75
- try {
76
- console.log("开始执行任务...");
77
- cleanDistDir();
78
- copyFiles();
79
- processPackageJson();
80
- cleanTargetDir();
81
-
82
- console.log("🎉 所有任务执行完成!");
83
- } catch (err) {
84
- console.error("任务执行失败:", err.message);
85
- process.exit(1);
86
- }