From a5c03400d46f45fe157e903787e3ea71a4f84bbf Mon Sep 17 00:00:00 2001 From: Sakana <15715093608@163.com> Date: Wed, 16 Sep 2026 13:29:32 +0800 Subject: [PATCH 1/3] fix(create): align new library dependencies with the CLI version The remote library template can retain an older vite-plus range while project setup injects the current core override. Pin the newly scaffolded library to the creating CLI before integration, without changing migration rules for existing projects. --- .../create_library_npm/assert-version.mjs | 9 ++++ .../create_library_npm/snapshots.toml | 10 ++++ .../snapshots/create_library_npm.md | 12 +++++ .../cli/src/create/__tests__/builtin.spec.ts | 53 +++++++++++++++++++ packages/cli/src/create/templates/builtin.ts | 12 +++++ 5 files changed, 96 insertions(+) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/assert-version.mjs create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/snapshots/create_library_npm.md diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/assert-version.mjs b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/assert-version.mjs new file mode 100644 index 0000000000..63cbd7ca27 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/assert-version.mjs @@ -0,0 +1,9 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; + +const read = (file) => JSON.parse(fs.readFileSync(file, 'utf8')); +const pkg = read('library/package.json'); +const version = pkg.overrides.vite.replace('npm:@voidzero-dev/vite-plus-core@', ''); +assert.equal(pkg.devDependencies['vite-plus'], version); +assert.equal(read('library/node_modules/vite-plus/package.json').version, version); +console.log('Library dependency and installed Vite+ match the toolchain override.'); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/snapshots.toml new file mode 100644 index 0000000000..6b83380e03 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/snapshots.toml @@ -0,0 +1,10 @@ +[[case]] +name = "create_library_npm" +vp = "local" +local-registry = true +unset-env = ["VP_SKIP_INSTALL", "CI"] +comment = "An npm library uses the creating CLI's version for both vite-plus and the Vite override." +steps = [ + { argv = ["vp", "create", "vite:library", "--directory", "library", "--package-manager", "npm", "--no-interactive", "--no-git", "--no-hooks", "--no-agent", "--no-editor"], snapshot = false, timeout = 120000 }, + { argv = ["node", "assert-version.mjs"] }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/snapshots/create_library_npm.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/snapshots/create_library_npm.md new file mode 100644 index 0000000000..aacf62e9bc --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/snapshots/create_library_npm.md @@ -0,0 +1,12 @@ +# create_library_npm + +An npm library uses the creating CLI's version for both vite-plus and the Vite override. + +## `vp create vite:library --directory library --package-manager npm --no-interactive --no-git --no-hooks --no-agent --no-editor` + + +## `node assert-version.mjs` + +``` +Library dependency and installed Vite+ match the toolchain override. +``` diff --git a/packages/cli/src/create/__tests__/builtin.spec.ts b/packages/cli/src/create/__tests__/builtin.spec.ts index d63370f272..3d29867546 100644 --- a/packages/cli/src/create/__tests__/builtin.spec.ts +++ b/packages/cli/src/create/__tests__/builtin.spec.ts @@ -1,5 +1,11 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + import { describe, expect, it, vi } from 'vitest'; +import { VITE_PLUS_VERSION } from '../../utils/constants.ts'; +import { readJsonFile } from '../../utils/json.ts'; import { executeBuiltinTemplate } from '../templates/builtin.js'; const { mockLogError } = vi.hoisted(() => ({ mockLogError: vi.fn() })); @@ -64,3 +70,50 @@ describe('executeBuiltinTemplate', () => { expect(mockLogError).not.toHaveBeenCalled(); }); }); + +describe('builtin library toolchain version', () => { + it.each([false, true])( + 'aligns the downloaded template with the CLI (monorepo: %s)', + async (isMonorepo) => { + const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vp-library-template-')); + try { + const { runRemoteTemplateCommand } = await import('../templates/remote.js'); + vi.mocked(runRemoteTemplateCommand).mockImplementationOnce(async () => { + const dir = path.join(rootDir, baseTemplateInfo.targetDir); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync( + path.join(dir, 'package.json'), + JSON.stringify({ + name: 'template-name', + devDependencies: { 'vite-plus': '^0.2.4', typescript: '^7.0.2' }, + peerDependencies: { react: '^19' }, + }), + ); + return { exitCode: 0 }; + }); + const result = await executeBuiltinTemplate( + { + ...workspaceInfo, + rootDir, + isMonorepo, + parentDirs: [], + packages: [], + downloadPackageManager: { binPrefix: '' }, + }, + { ...baseTemplateInfo, command: 'vite:library' }, + { silent: true }, + ); + expect(result.exitCode).toBe(0); + expect( + readJsonFile(path.join(rootDir, baseTemplateInfo.targetDir, 'package.json')), + ).toEqual({ + name: baseTemplateInfo.packageName, + devDependencies: { 'vite-plus': VITE_PLUS_VERSION, typescript: '^7.0.2' }, + peerDependencies: { react: '^19' }, + }); + } finally { + fs.rmSync(rootDir, { recursive: true, force: true }); + } + }, + ); +}); diff --git a/packages/cli/src/create/templates/builtin.ts b/packages/cli/src/create/templates/builtin.ts index ee96165cd0..3574cbfb49 100644 --- a/packages/cli/src/create/templates/builtin.ts +++ b/packages/cli/src/create/templates/builtin.ts @@ -5,6 +5,8 @@ import * as prompts from '@voidzero-dev/vite-plus-prompts'; import colors from 'picocolors'; import type { WorkspaceInfo } from '../../types/index.ts'; +import { VITE_PLUS_NAME, VITE_PLUS_VERSION } from '../../utils/constants.ts'; +import { editJsonFile } from '../../utils/json.ts'; import type { ExecutionWithProjectDir } from '../command.ts'; import { discoverTemplate } from '../discovery.ts'; import { setPackageName } from '../utils.ts'; @@ -50,6 +52,16 @@ export async function executeBuiltinTemplate( } const fullPath = path.join(workspaceInfo.rootDir, templateInfo.targetDir); setPackageName(fullPath, templateInfo.packageName); + // The remote template can lag behind the CLI. Align a newly scaffolded + // library before project setup injects this CLI's toolchain overrides. + editJsonFile<{ devDependencies?: Record }>( + path.join(fullPath, 'package.json'), + (pkg) => { + pkg.devDependencies ??= {}; + pkg.devDependencies[VITE_PLUS_NAME] = VITE_PLUS_VERSION; + return pkg; + }, + ); if (workspaceInfo.isMonorepo) { removeNestedLibraryToolConfig(fullPath); } From e579a2d1a1e1068aa8c63286acafed354d799b83 Mon Sep 17 00:00:00 2001 From: Sakana <15715093608@163.com> Date: Sat, 3 Oct 2026 12:19:55 +0800 Subject: [PATCH 2/3] fix(create): align libraries generated inside monorepos Prepare both built-in library download paths with the creating CLI version before project integration. Keep existing migration rules and workspace configuration cleanup. Run built-in create CI cases without forced migration and assert the installed CLI and resolved core in each generated package. Cover npm monorepos and catalog-based package managers with regression tests. --- .github/workflows/test-vp-create.yml | 22 ++--- .../create_library_npm/assert-version.mjs | 27 ++++-- .../monorepo-library-version.spec.ts | 86 +++++++++++++++++++ packages/cli/src/create/templates/builtin.ts | 15 +--- packages/cli/src/create/templates/library.ts | 18 ++++ packages/cli/src/create/templates/monorepo.ts | 3 +- 6 files changed, 137 insertions(+), 34 deletions(-) create mode 100644 packages/cli/src/create/__tests__/monorepo-library-version.spec.ts create mode 100644 packages/cli/src/create/templates/library.ts diff --git a/.github/workflows/test-vp-create.yml b/.github/workflows/test-vp-create.yml index 2c1a5be5f3..140c3f8d52 100644 --- a/.github/workflows/test-vp-create.yml +++ b/.github/workflows/test-vp-create.yml @@ -175,9 +175,9 @@ jobs: # exact version like a real release. The vp binary was built before the # pack step pinned the package versions, so align the version explicitly. VP_VERSION: '0.0.0' - # Force full dependency rewriting so the library template's existing - # vite-plus dep gets overridden with the local build - VP_FORCE_MIGRATE: '1' + # Built-in templates must select the local version without forced migration. + # Keep the separate remote migration scenario in its existing force mode. + VP_FORCE_MIGRATE: ${{ matrix.template.verify-migration == 'true' && '1' || '' }} # yarn 4 quarantines packages published within `npmMinimalAgeGate` # (default 1440 min / 24h). When an oxlint bump landed <24h ago, the # migration step's `vp dlx @oxlint/migrate@` fails with @@ -350,15 +350,7 @@ jobs: - name: Verify local packages installed working-directory: ${{ runner.temp }}/test-project run: | - node -e " - const path = require('path'); - const pkg = require(path.resolve('node_modules/vite-plus/package.json')); - if (pkg.version !== '0.0.0') { - console.error('Expected vite-plus@0.0.0, got ' + pkg.version); - process.exit(1); - } - console.log('✓ vite-plus@' + pkg.version + ' installed correctly'); - " + node "$GITHUB_WORKSPACE/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/assert-version.mjs" . 0.0.0 - name: Verify monorepo sub-package deps if: matrix.template.name == 'monorepo' @@ -367,8 +359,8 @@ jobs: PACKAGE_MANAGER: ${{ matrix.package-manager }} run: | # Issue 1: packages/utils inherits `vite-plus: ^x.y.z` from the - # library template. In catalog-supporting monorepos (pnpm/yarn/bun) - # the migrator must normalize it so siblings don't drift. + # library template. Creation must align it with the CLI before + # integration optionally converts the version to a catalog reference. # Issue 2: apps/website is scaffolded by create-vite which ships # `vite` (and sometimes `vitest`) in devDependencies. After # migration the scripts are rewritten to `vp ...` and `vite-plus` @@ -418,7 +410,7 @@ jobs: process.exit(1); } - if (pm !== 'npm' && appDev['vite-plus'] !== utilsDev['vite-plus']) { + if (appDev['vite-plus'] !== utilsDev['vite-plus']) { console.error('✗ vite-plus spec drift: apps/website=' + appDev['vite-plus'] + ' packages/utils=' + utilsDev['vite-plus']); process.exit(1); } diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/assert-version.mjs b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/assert-version.mjs index 63cbd7ca27..6bfa7ac594 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/assert-version.mjs +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/assert-version.mjs @@ -1,9 +1,26 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; +import { createRequire } from 'node:module'; +import path from 'node:path'; const read = (file) => JSON.parse(fs.readFileSync(file, 'utf8')); -const pkg = read('library/package.json'); -const version = pkg.overrides.vite.replace('npm:@voidzero-dev/vite-plus-core@', ''); -assert.equal(pkg.devDependencies['vite-plus'], version); -assert.equal(read('library/node_modules/vite-plus/package.json').version, version); -console.log('Library dependency and installed Vite+ match the toolchain override.'); +const directory = path.resolve(process.argv[2] ?? 'library'); +const pkg = read(path.join(directory, 'package.json')); +const version = process.argv[3] ?? pkg.overrides.vite.replace('npm:@voidzero-dev/vite-plus-core@', ''); +for (const relative of ['.', 'apps/website', 'packages/utils']) { + const manifest = path.join(directory, relative, 'package.json'); + if (relative !== '.' && !fs.existsSync(manifest)) continue; + const spec = read(manifest).devDependencies['vite-plus']; + assert.ok(spec === version || spec === 'catalog:', `${relative}: unexpected vite-plus ${spec}`); + const require = createRequire(manifest); + const cliPath = require.resolve('vite-plus/package.json'); + assert.equal(read(cliPath).version, version, `${relative}: installed vite-plus version`); + const corePath = createRequire(cliPath).resolve('vite/package.json'); + assert.equal(read(corePath).name, '@voidzero-dev/vite-plus-core'); + assert.equal(read(corePath).version, version, `${relative}: resolved core version`); +} +if (process.argv[2]) { + console.log('All scaffolded packages resolve the expected Vite+ and core versions.'); +} else { + console.log('Library dependency and installed Vite+ match the toolchain override.'); +} diff --git a/packages/cli/src/create/__tests__/monorepo-library-version.spec.ts b/packages/cli/src/create/__tests__/monorepo-library-version.spec.ts new file mode 100644 index 0000000000..133c0c60df --- /dev/null +++ b/packages/cli/src/create/__tests__/monorepo-library-version.spec.ts @@ -0,0 +1,86 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { PackageManager, type WorkspaceInfo } from '../../types/index.ts'; +import { VITE_PLUS_VERSION } from '../../utils/constants.ts'; +import { readJsonFile } from '../../utils/json.ts'; +import { executeMonorepoTemplate } from '../templates/monorepo.ts'; +import { runRemoteTemplateCommand } from '../templates/remote.ts'; +import { BuiltinTemplate, TemplateType } from '../templates/types.ts'; + +vi.mock('../templates/remote.ts', () => ({ runRemoteTemplateCommand: vi.fn() })); + +let rootDir: string; +afterEach(() => { + vi.resetAllMocks(); + if (rootDir) { + fs.rmSync(rootDir, { recursive: true, force: true }); + } +}); + +describe('monorepo library toolchain version', () => { + it.each([ + [PackageManager.npm, '12.2.0', VITE_PLUS_VERSION], + [PackageManager.pnpm, '12.8.1', 'catalog:'], + [PackageManager.yarn, '4.18.1', 'catalog:'], + [PackageManager.bun, '1.4.2', 'catalog:'], + ])('aligns the downloaded library for %s', async (packageManager, version, expected) => { + rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vp-monorepo-library-version-')); + const workspace: WorkspaceInfo = { + rootDir, + isMonorepo: false, + monorepoScope: '', + workspacePatterns: [], + parentDirs: [], + packages: [], + packageManager, + packageManagerVersion: version, + downloadPackageManager: { + name: packageManager, + packageName: packageManager, + version, + installDir: '', + binPrefix: '', + }, + }; + vi.mocked(runRemoteTemplateCommand).mockImplementation(async (_workspace, cwd, template) => { + const isLibrary = template.command === 'degit'; + const dir = path.join(cwd, isLibrary ? 'packages/utils' : 'apps/website'); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync( + path.join(dir, 'package.json'), + JSON.stringify({ + name: 'template', + devDependencies: isLibrary + ? { 'vite-plus': '^0.2.4', typescript: '^7.0.2', bumpp: '^11.1.0' } + : { vite: '^8.0.0', typescript: '^7.0.2' }, + }), + ); + return { exitCode: 0 }; + }); + + const result = await executeMonorepoTemplate( + workspace, + { + command: BuiltinTemplate.monorepo, + type: TemplateType.builtin, + packageName: 'my-monorepo', + targetDir: 'project', + interactive: false, + args: [], + envs: {}, + }, + { silent: true }, + ); + + expect(result.exitCode).toBe(0); + const pkg = readJsonFile(path.join(rootDir, 'project/packages/utils/package.json')); + expect(pkg).toMatchObject({ + name: 'utils', + devDependencies: { 'vite-plus': expected, bumpp: '^11.1.0' }, + }); + }); +}); diff --git a/packages/cli/src/create/templates/builtin.ts b/packages/cli/src/create/templates/builtin.ts index 3574cbfb49..399a2028a7 100644 --- a/packages/cli/src/create/templates/builtin.ts +++ b/packages/cli/src/create/templates/builtin.ts @@ -5,12 +5,11 @@ import * as prompts from '@voidzero-dev/vite-plus-prompts'; import colors from 'picocolors'; import type { WorkspaceInfo } from '../../types/index.ts'; -import { VITE_PLUS_NAME, VITE_PLUS_VERSION } from '../../utils/constants.ts'; -import { editJsonFile } from '../../utils/json.ts'; import type { ExecutionWithProjectDir } from '../command.ts'; import { discoverTemplate } from '../discovery.ts'; import { setPackageName } from '../utils.ts'; import { executeGeneratorScaffold } from './generator.ts'; +import { prepareLibraryPackage } from './library.ts'; import { removeNestedLibraryToolConfig } from './monorepo.ts'; import { runRemoteTemplateCommand } from './remote.ts'; import { BuiltinTemplate, type BuiltinTemplateInfo, LibraryTemplateRepo } from './types.ts'; @@ -51,17 +50,7 @@ export async function executeBuiltinTemplate( return { exitCode: result.exitCode }; } const fullPath = path.join(workspaceInfo.rootDir, templateInfo.targetDir); - setPackageName(fullPath, templateInfo.packageName); - // The remote template can lag behind the CLI. Align a newly scaffolded - // library before project setup injects this CLI's toolchain overrides. - editJsonFile<{ devDependencies?: Record }>( - path.join(fullPath, 'package.json'), - (pkg) => { - pkg.devDependencies ??= {}; - pkg.devDependencies[VITE_PLUS_NAME] = VITE_PLUS_VERSION; - return pkg; - }, - ); + prepareLibraryPackage(fullPath, templateInfo.packageName); if (workspaceInfo.isMonorepo) { removeNestedLibraryToolConfig(fullPath); } diff --git a/packages/cli/src/create/templates/library.ts b/packages/cli/src/create/templates/library.ts new file mode 100644 index 0000000000..54f823bed5 --- /dev/null +++ b/packages/cli/src/create/templates/library.ts @@ -0,0 +1,18 @@ +import path from 'node:path'; + +import { VITE_PLUS_NAME, VITE_PLUS_VERSION } from '../../utils/constants.ts'; +import { editJsonFile } from '../../utils/json.ts'; + +export function prepareLibraryPackage(projectPath: string, packageName: string): void { + // The remote template can lag behind the CLI. Use the creating CLI's version + // before project integration writes its core override or catalog references. + editJsonFile<{ name?: string; devDependencies?: Record }>( + path.join(projectPath, 'package.json'), + (pkg) => { + pkg.name = packageName; + pkg.devDependencies ??= {}; + pkg.devDependencies[VITE_PLUS_NAME] = VITE_PLUS_VERSION; + return pkg; + }, + ); +} diff --git a/packages/cli/src/create/templates/monorepo.ts b/packages/cli/src/create/templates/monorepo.ts index b3ca28278d..5ce2292b0d 100644 --- a/packages/cli/src/create/templates/monorepo.ts +++ b/packages/cli/src/create/templates/monorepo.ts @@ -14,6 +14,7 @@ import { editYamlFile, readYamlFile } from '../../utils/yaml.ts'; import type { ExecutionWithProjectDir } from '../command.ts'; import { discoverTemplate } from '../discovery.ts'; import { copyDir, formatDisplayTargetDir, renameFiles, setPackageName } from '../utils.ts'; +import { prepareLibraryPackage } from './library.ts'; import { runRemoteTemplateCommand } from './remote.ts'; import { type BuiltinTemplateInfo, LibraryTemplateRepo } from './types.ts'; @@ -152,7 +153,7 @@ export async function executeMonorepoTemplate( ? `${workspaceInfo.monorepoScope}/utils` : 'utils'; const libraryProjectPath = path.join(fullPath, libraryDir); - setPackageName(libraryProjectPath, libraryPackageName); + prepareLibraryPackage(libraryProjectPath, libraryPackageName); removeNestedLibraryToolConfig(libraryProjectPath); // Perform auto-migration on the created library rewriteMonorepoProject( From 6f01450eba54fcfba6a188844338fd5753734991 Mon Sep 17 00:00:00 2001 From: Sakana <15715093608@163.com> Date: Sat, 3 Oct 2026 12:39:14 +0800 Subject: [PATCH 3/3] test(create): cover libraries added to existing workspaces Exercise library creation inside an installed workspace for all four package managers. Check member and root toolchain resolution, preserve root configuration, and run member tests. --- .github/workflows/test-vp-create.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/test-vp-create.yml b/.github/workflows/test-vp-create.yml index 140c3f8d52..c25a1ce717 100644 --- a/.github/workflows/test-vp-create.yml +++ b/.github/workflows/test-vp-create.yml @@ -490,3 +490,29 @@ jobs: exit 1 fi echo "✓ 100% cache hit verified" + + - name: Verify library added to existing workspace + if: matrix.template.name == 'monorepo' + working-directory: ${{ runner.temp }}/test-project + run: | + workspace_configs=(package.json) + for file in pnpm-workspace.yaml .yarnrc.yml; do + if [ -f "$file" ]; then + workspace_configs+=("$file") + fi + done + for file in "${workspace_configs[@]}"; do + cp "$file" "$RUNNER_TEMP/$file.before-library" + done + + vp create vite:library --directory packages/additional \ + --no-interactive --no-hooks --no-agent --no-editor + + for file in "${workspace_configs[@]}"; do + cmp "$file" "$RUNNER_TEMP/$file.before-library" + done + assert_script="$GITHUB_WORKSPACE/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_library_npm/assert-version.mjs" + node "$assert_script" . 0.0.0 + node "$assert_script" packages/additional 0.0.0 + cd packages/additional + vp test run