From 697a5a5e02f46b0411d32050eb4c5d232eb1d5ec Mon Sep 17 00:00:00 2001 From: xiaou66 <2630316030@qq.com> Date: Thu, 4 Jun 2026 22:41:32 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20vue=20=E8=84=9A=E6=89=8B=E6=9E=B6?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ templates/vue-vite/package.json | 2 ++ .../vue-vite/{public => src-ztools}/logo.png | Bin .../{public => src-ztools}/plugin.json | 0 .../preload/package.json | 0 .../preload/services.js | 0 templates/vue-vite/tsconfig.app.json | 13 +++++++++ templates/vue-vite/tsconfig.json | 26 ++++++------------ templates/vue-vite/tsconfig.node.json | 20 ++++++++++++++ .../{vite.config.js => vite.config.ts} | 0 10 files changed, 46 insertions(+), 17 deletions(-) rename templates/vue-vite/{public => src-ztools}/logo.png (100%) rename templates/vue-vite/{public => src-ztools}/plugin.json (100%) rename templates/vue-vite/{public => src-ztools}/preload/package.json (100%) rename templates/vue-vite/{public => src-ztools}/preload/services.js (100%) create mode 100644 templates/vue-vite/tsconfig.app.json create mode 100644 templates/vue-vite/tsconfig.node.json rename templates/vue-vite/{vite.config.js => vite.config.ts} (100%) diff --git a/.gitignore b/.gitignore index e97095c..961e097 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ dist/ *.log .DS_Store *.tsbuildinfo +templates/**/*-lock.yaml +.idea diff --git a/templates/vue-vite/package.json b/templates/vue-vite/package.json index 336697c..d49f9f9 100644 --- a/templates/vue-vite/package.json +++ b/templates/vue-vite/package.json @@ -11,7 +11,9 @@ "vue": "^3.5.13" }, "devDependencies": { + "@tsconfig/node22": "^22.0.5", "@vitejs/plugin-vue": "^5.2.1", + "@vue/tsconfig": "^0.9.1", "@ztools-center/ztools-api-types": "^1.0.1", "typescript": "^5.3.0", "vite": "^6.0.11", diff --git a/templates/vue-vite/public/logo.png b/templates/vue-vite/src-ztools/logo.png similarity index 100% rename from templates/vue-vite/public/logo.png rename to templates/vue-vite/src-ztools/logo.png diff --git a/templates/vue-vite/public/plugin.json b/templates/vue-vite/src-ztools/plugin.json similarity index 100% rename from templates/vue-vite/public/plugin.json rename to templates/vue-vite/src-ztools/plugin.json diff --git a/templates/vue-vite/public/preload/package.json b/templates/vue-vite/src-ztools/preload/package.json similarity index 100% rename from templates/vue-vite/public/preload/package.json rename to templates/vue-vite/src-ztools/preload/package.json diff --git a/templates/vue-vite/public/preload/services.js b/templates/vue-vite/src-ztools/preload/services.js similarity index 100% rename from templates/vue-vite/public/preload/services.js rename to templates/vue-vite/src-ztools/preload/services.js diff --git a/templates/vue-vite/tsconfig.app.json b/templates/vue-vite/tsconfig.app.json new file mode 100644 index 0000000..474e71c --- /dev/null +++ b/templates/vue-vite/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*", "src-ztools/**"], + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "paths": { + "@/*": ["./src/*"] + }, + "types": ["@ztools-center/ztools-api-types"] + } +} diff --git a/templates/vue-vite/tsconfig.json b/templates/vue-vite/tsconfig.json index 39b73bc..66b5e57 100644 --- a/templates/vue-vite/tsconfig.json +++ b/templates/vue-vite/tsconfig.json @@ -1,19 +1,11 @@ { - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "preserve", - "strict": false, - "noImplicitAny": false, - "types": ["@ztools-center/ztools-api-types"] - }, - "include": ["src"] + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] } diff --git a/templates/vue-vite/tsconfig.node.json b/templates/vue-vite/tsconfig.node.json new file mode 100644 index 0000000..83f4bcb --- /dev/null +++ b/templates/vue-vite/tsconfig.node.json @@ -0,0 +1,20 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*", + "eslint.config.*", + "uno.config.ts", + ], + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node", "@ztools-center/ztools-api-types"], + } +} diff --git a/templates/vue-vite/vite.config.js b/templates/vue-vite/vite.config.ts similarity index 100% rename from templates/vue-vite/vite.config.js rename to templates/vue-vite/vite.config.ts From 4d79298c2a0bd07f628b757c6e82e712eecb99d8 Mon Sep 17 00:00:00 2001 From: xiaou66 <2630316030@qq.com> Date: Thu, 4 Jun 2026 22:42:52 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20vue=20=E8=84=9A=E6=89=8B=E6=9E=B6?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/vue-vite/src-ztools/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/vue-vite/src-ztools/plugin.json b/templates/vue-vite/src-ztools/plugin.json index 217f10c..98ced45 100644 --- a/templates/vue-vite/src-ztools/plugin.json +++ b/templates/vue-vite/src-ztools/plugin.json @@ -1,5 +1,5 @@ { - "$schema": "node_modules/@ztools-center/ztools-api-types/resource/ztools.schema.json", + "$schema": "../node_modules/@ztools-center/ztools-api-types/resource/ztools.schema.json", "name": "{{PLUGIN_NAME}}", "title": "{{PLUGIN_TITLE}}", "description": "{{DESCRIPTION}}", From d05cd9a0d09a7746560fee9c8533e03c9e74cfc1 Mon Sep 17 00:00:00 2001 From: xiaou66 <2630316030@qq.com> Date: Thu, 4 Jun 2026 23:04:00 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E6=A0=B9=E7=9B=AE=E5=BD=95=E8=A7=A3=E6=9E=90=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugin-project.ts | 106 ++++++++++++++++++++++++++++++++++++++++++ src/types.ts | 11 +++++ 2 files changed, 117 insertions(+) create mode 100644 src/plugin-project.ts diff --git a/src/plugin-project.ts b/src/plugin-project.ts new file mode 100644 index 0000000..0165e7b --- /dev/null +++ b/src/plugin-project.ts @@ -0,0 +1,106 @@ +import fs from 'node:fs' +import path from 'node:path' +import type { DiscoveredPluginProject, PluginConfig, PluginLayout } from './types.js' + +interface PluginPathCandidate { + layout: PluginLayout + pluginRoot: string + pluginJsonPath: string +} + +/** + * 插件项目发现模块。 + * + * 该模块集中维护 CLI 对插件根目录的判断规则,避免 publish、pull 等入口各自 + * 手写路径候选列表后产生结构兼容差异。最新的 src-ztools 结构优先,旧结构继续 + * 兼容以避免破坏已有插件项目。 + */ +export function discoverPluginProject(cwd: string = process.cwd()): DiscoveredPluginProject { + const projectRoot = path.resolve(cwd) + const candidates = buildPluginPathCandidates(projectRoot) + const existing = candidates.filter((candidate) => fs.existsSync(candidate.pluginJsonPath)) + + if (existing.length === 0) { + throw new Error( + '未找到 plugin.json,请确保在插件项目根目录下执行此命令\n' + + '支持的路径:./src-ztools/plugin.json, ./plugin.json, ./public/plugin.json' + ) + } + + const selected = existing[0] + const config = readPluginConfig(selected.pluginJsonPath) + const warnings = buildMultipleConfigWarnings(existing) + + return { + projectRoot, + pluginRoot: selected.pluginRoot, + pluginJsonPath: selected.pluginJsonPath, + layout: selected.layout, + config, + warnings + } +} + +/** + * 按优先级构造候选路径。 + * + * src-ztools 是新模板结构,必须优先于根目录和旧 public 结构;否则同一项目中残留 + * 旧配置时,CLI 会错误读取过期配置。 + */ +function buildPluginPathCandidates(projectRoot: string): PluginPathCandidate[] { + return [ + { + layout: 'src-ztools', + pluginRoot: path.join(projectRoot, 'src-ztools'), + pluginJsonPath: path.join(projectRoot, 'src-ztools', 'plugin.json') + }, + { + layout: 'root', + pluginRoot: projectRoot, + pluginJsonPath: path.join(projectRoot, 'plugin.json') + }, + { + layout: 'public', + pluginRoot: path.join(projectRoot, 'public'), + pluginJsonPath: path.join(projectRoot, 'public', 'plugin.json') + } + ] +} + +/** + * 读取插件配置。 + * + * 只负责 JSON 解析,不做业务字段校验;字段校验仍由 publish 等调用方根据命令场景 + * 执行,避免解析器承担过多职责。 + */ +function readPluginConfig(pluginJsonPath: string): PluginConfig { + try { + const content = fs.readFileSync(pluginJsonPath, 'utf-8') + return JSON.parse(content) as PluginConfig + } catch (error) { + throw new Error(`读取 plugin.json 失败: ${(error as Error).message}`) + } +} + +/** + * 构造多配置文件提示。 + * + * 多个 plugin.json 同时存在通常意味着迁移残留。这里不直接失败,是为了保留旧项目 + * 的渐进迁移能力;调用方负责把 warning 展示给用户。 + */ +function buildMultipleConfigWarnings(existing: PluginPathCandidate[]): string[] { + if (existing.length <= 1) { + return [] + } + + const selected = existing[0] + const ignored = existing.slice(1).map((candidate) => candidate.pluginJsonPath) + + return [ + [ + `检测到多个 plugin.json,已使用 ${selected.pluginJsonPath}`, + '被忽略的配置:', + ...ignored.map((item) => ` - ${item}`) + ].join('\n') + ] +} diff --git a/src/types.ts b/src/types.ts index d451d3a..8e08b97 100644 --- a/src/types.ts +++ b/src/types.ts @@ -61,3 +61,14 @@ export interface CommitInfo { date: string message: string } + +export type PluginLayout = 'src-ztools' | 'root' | 'public' + +export interface DiscoveredPluginProject { + projectRoot: string + pluginRoot: string + pluginJsonPath: string + layout: PluginLayout + config: PluginConfig + warnings: string[] +} From 987c0bdea579a1dfd3e9654f93bd50602afc9d99 Mon Sep 17 00:00:00 2001 From: xiaou66 <2630316030@qq.com> Date: Thu, 4 Jun 2026 23:06:02 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=E5=8F=91=E5=B8=83=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=8F=92=E4=BB=B6=E6=A0=B9=E7=9B=AE=E5=BD=95=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/git.ts | 39 +++++++++++++++++++++++++++++++++------ src/publish.ts | 45 +++++++++++++++------------------------------ 2 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/git.ts b/src/git.ts index 1c2655c..2efca9c 100644 --- a/src/git.ts +++ b/src/git.ts @@ -36,6 +36,14 @@ const COPY_IGNORE_FILE_PATTERNS: RegExp[] = [ /^yarn-error\.log.*$/ ] +interface CopyDirOptions { + allowDist: boolean +} + +interface CopyPluginFilesOptions { + allowDist?: boolean +} + /** * 执行Git命令 */ @@ -201,10 +209,21 @@ export function prepareBranch(pluginName: string): { existedRemotely: boolean; b return { existedRemotely, branchName } } -function shouldIgnoreEntry(name: string, isDir: boolean): boolean { +/** + * 判断复制时是否忽略目录或文件。 + * + * dist 在源码项目中通常是临时产物,但在 src-ztools 最新结构中是生产入口目录, + * 因此复制插件根目录时需要按上下文允许 dist。 + */ +function shouldIgnoreEntry(name: string, isDir: boolean, options: CopyDirOptions): boolean { + if (isDir && name === 'dist' && options.allowDist) { + return false + } + if (isDir) { return COPY_IGNORE_DIRS.has(name) } + return COPY_IGNORE_FILE_PATTERNS.some((re) => re.test(name)) } @@ -245,7 +264,11 @@ export function mirrorForkPluginToCwd(pluginName: string, destDir: string): void * 把用户工作目录的插件文件复制到 fork 仓库的 plugins// 下。 * 复制前先清空目标目录,确保用户本地删除的文件也会反映到 fork。 */ -export function copyPluginFiles(pluginName: string, sourceDir: string): void { +export function copyPluginFiles( + pluginName: string, + sourceDir: string, + options: CopyPluginFilesOptions = {} +): void { const destDir = path.join(FORK_REPO_DIR, 'plugins', pluginName) console.log(cyan(`\n同步插件文件到 plugins/${pluginName}/ ...`)) @@ -254,19 +277,23 @@ export function copyPluginFiles(pluginName: string, sourceDir: string): void { } fs.mkdirSync(destDir, { recursive: true }) - copyDirRecursive(sourceDir, destDir) + copyDirRecursive(sourceDir, destDir, { allowDist: options.allowDist ?? false }) console.log(green('✓ 文件同步完成')) } -function copyDirRecursive(src: string, dest: string): void { +function copyDirRecursive( + src: string, + dest: string, + options: CopyDirOptions = { allowDist: false } +): void { const entries = fs.readdirSync(src, { withFileTypes: true }) for (const entry of entries) { - if (shouldIgnoreEntry(entry.name, entry.isDirectory())) continue + if (shouldIgnoreEntry(entry.name, entry.isDirectory(), options)) continue const srcPath = path.join(src, entry.name) const destPath = path.join(dest, entry.name) if (entry.isDirectory()) { fs.mkdirSync(destPath, { recursive: true }) - copyDirRecursive(srcPath, destPath) + copyDirRecursive(srcPath, destPath, options) } else if (entry.isSymbolicLink()) { const link = fs.readlinkSync(srcPath) fs.symlinkSync(link, destPath) diff --git a/src/publish.ts b/src/publish.ts index a6cc1c7..537417b 100644 --- a/src/publish.ts +++ b/src/publish.ts @@ -1,6 +1,5 @@ import { execSync } from 'node:child_process' import { blue, cyan, green, red, yellow } from 'kolorist' -import fs from 'node:fs' import path from 'node:path' import prompts from 'prompts' import { ensureAuth } from './auth.js' @@ -28,7 +27,8 @@ import { pluginExistsUpstream, syncForkMain } from './github.js' -import type { PluginConfig } from './types.js' +import { discoverPluginProject } from './plugin-project.js' +import type { DiscoveredPluginProject, PluginConfig } from './types.js' /** * 验证插件名称格式 @@ -47,32 +47,16 @@ function validateVersion(version: string): boolean { } /** - * 验证插件项目 + * 验证插件项目。 + * + * 返回完整发现结果而不只返回配置,是为了让发布流程后续复制正确的插件根目录。 */ -function validatePluginProject(): PluginConfig { - const possiblePaths = [ - path.join(process.cwd(), 'plugin.json'), - path.join(process.cwd(), 'public', 'plugin.json') - ] - - let pluginJsonPath: string | null = null - for (const p of possiblePaths) { - if (fs.existsSync(p)) { - pluginJsonPath = p - break - } - } +function validatePluginProject(): DiscoveredPluginProject { + const pluginProject = discoverPluginProject() + const pluginConfig = pluginProject.config - if (!pluginJsonPath) { - throw new Error('未找到plugin.json,请确保在插件项目根目录下执行此命令\n支持的路径:./plugin.json, ./public/plugin.json') - } - - let pluginConfig: PluginConfig - try { - const content = fs.readFileSync(pluginJsonPath, 'utf-8') - pluginConfig = JSON.parse(content) - } catch (error) { - throw new Error(`读取plugin.json失败: ${(error as Error).message}`) + for (const warning of pluginProject.warnings) { + console.log(yellow(`⚠ ${warning}`)) } if (!pluginConfig.name) { @@ -121,7 +105,7 @@ function validatePluginProject(): PluginConfig { ) } - return pluginConfig + return pluginProject } /** @@ -313,7 +297,8 @@ export async function publish(): Promise { try { // 1. 验证插件项目 console.log(cyan('📋 验证插件项目...')) - const pluginConfig = validatePluginProject() + const pluginProject = validatePluginProject() + const pluginConfig = pluginProject.config const displayName = pluginConfig.title || pluginConfig.name console.log(green(`✓ 插件: ${displayName} (${pluginConfig.name})`)) console.log(green(`✓ 描述: ${pluginConfig.description || 'N/A'}`)) @@ -352,8 +337,8 @@ export async function publish(): Promise { // 7. 切换到 plugin/ 分支(仅用于决定从哪里出发追加 commit) prepareBranch(pluginConfig.name) - // 8. 把工作目录文件复制进 plugins// - copyPluginFiles(pluginConfig.name, process.cwd()) + // 8. 把插件根目录文件复制进 plugins// + copyPluginFiles(pluginConfig.name, pluginProject.pluginRoot, { allowDist: true }) // 9. 组装 commit 标题 / 正文 / PR 标题 const commitSubjects = getLocalCommitSubjectsSinceLastPublish() From 34b813b3584d9b1941883f47308e3913c95c059e Mon Sep 17 00:00:00 2001 From: xiaou66 <2630316030@qq.com> Date: Thu, 4 Jun 2026 23:06:57 +0800 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=E6=8B=89=E5=8F=96=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E6=94=AF=E6=8C=81=E6=8F=92=E4=BB=B6=E6=A0=B9=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pull.ts | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/src/pull.ts b/src/pull.ts index 27e6858..9017025 100644 --- a/src/pull.ts +++ b/src/pull.ts @@ -1,6 +1,4 @@ import { execSync } from 'node:child_process' -import fs from 'node:fs' -import path from 'node:path' import { blue, cyan, green, red, yellow } from 'kolorist' import { ensureAuth } from './auth.js' import { @@ -15,29 +13,7 @@ import { remotePluginBranchExists } from './git.js' import { ensureFork, getCurrentUser } from './github.js' -import type { PluginConfig } from './types.js' - -function readPluginConfig(): PluginConfig { - const candidates = [ - path.join(process.cwd(), 'plugin.json'), - path.join(process.cwd(), 'public', 'plugin.json') - ] - let pluginJsonPath: string | null = null - for (const p of candidates) { - if (fs.existsSync(p)) { - pluginJsonPath = p - break - } - } - if (!pluginJsonPath) { - throw new Error( - '未找到 plugin.json,请确保在插件项目根目录下执行此命令\n支持的路径:./plugin.json, ./public/plugin.json' - ) - } - const cfg = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf-8')) as PluginConfig - if (!cfg.name) throw new Error('plugin.json 中缺少 name 字段') - return cfg -} +import { discoverPluginProject } from './plugin-project.js' function runInCwd(cmd: string): void { execSync(cmd, { cwd: process.cwd(), stdio: ['pipe', 'inherit', 'inherit'] }) @@ -96,7 +72,14 @@ export async function pullContributions(): Promise { 'find no ztools-last-publish 标签——请先 ztools publish 成功一次后再使用 pull-contributions。' ) } - const pluginConfig = readPluginConfig() + const pluginProject = discoverPluginProject() + const pluginConfig = pluginProject.config + + for (const warning of pluginProject.warnings) { + console.log(yellow(`⚠ ${warning}`)) + } + + if (!pluginConfig.name) throw new Error('plugin.json 中缺少 name 字段') const displayName = pluginConfig.title || pluginConfig.name console.log(green(`✓ 插件: ${displayName} (${pluginConfig.name})`)) console.log(green(`✓ 当前分支: ${originalBranch}`)) @@ -126,7 +109,7 @@ export async function pullContributions(): Promise { runInCwd(`git checkout -b "${tempBranch}" "${baseSha}"`) // 7. 把 fork 当前 plugin 内容镜像到工作树并提交(这就是 theirs 的内容快照) - mirrorForkPluginToCwd(pluginConfig.name, cwd) + mirrorForkPluginToCwd(pluginConfig.name, pluginProject.pluginRoot) runInCwd('git add -A') const hasForkDiff = !tryRunInCwd('git diff --cached --quiet') From e2d8edd66fd3d7ffaf0957343b9835bff8269020 Mon Sep 17 00:00:00 2001 From: xiaou66 <2630316030@qq.com> Date: Thu, 4 Jun 2026 23:07:39 +0800 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=E6=8B=89=E5=8F=96=E8=B4=A1=E7=8C=AE?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E6=8F=92=E4=BB=B6=E6=9E=84=E5=BB=BA=E4=BA=A7?= =?UTF-8?q?=E7=89=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/git.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/git.ts b/src/git.ts index 2efca9c..965c161 100644 --- a/src/git.ts +++ b/src/git.ts @@ -257,7 +257,8 @@ export function mirrorForkPluginToCwd(pluginName: string, destDir: string): void if (!fs.existsSync(sourceDir)) { throw new Error(`fork 仓库里找不到 plugins/${pluginName}/,PR 分支可能已被删除或为空`) } - copyDirRecursive(sourceDir, destDir) + fs.mkdirSync(destDir, { recursive: true }) + copyDirRecursive(sourceDir, destDir, { allowDist: true }) } /**