Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 33 additions & 15 deletions .github/workflows/test-vp-create.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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@<bundled oxlint>` fails with
Expand Down Expand Up @@ -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'
Expand All @@ -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`
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -498,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
Original file line number Diff line number Diff line change
@@ -0,0 +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 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.');
}
Original file line number Diff line number Diff line change
@@ -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"] },
]
Original file line number Diff line number Diff line change
@@ -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.
```
53 changes: 53 additions & 0 deletions packages/cli/src/create/__tests__/builtin.spec.ts
Original file line number Diff line number Diff line change
@@ -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() }));
Expand Down Expand Up @@ -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 });
}
},
);
});
86 changes: 86 additions & 0 deletions packages/cli/src/create/__tests__/monorepo-library-version.spec.ts
Original file line number Diff line number Diff line change
@@ -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' },
});
});
});
3 changes: 2 additions & 1 deletion packages/cli/src/create/templates/builtin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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';
Expand Down Expand Up @@ -49,7 +50,7 @@ export async function executeBuiltinTemplate(
return { exitCode: result.exitCode };
}
const fullPath = path.join(workspaceInfo.rootDir, templateInfo.targetDir);
setPackageName(fullPath, templateInfo.packageName);
prepareLibraryPackage(fullPath, templateInfo.packageName);
if (workspaceInfo.isMonorepo) {
removeNestedLibraryToolConfig(fullPath);
}
Expand Down
18 changes: 18 additions & 0 deletions packages/cli/src/create/templates/library.ts
Original file line number Diff line number Diff line change
@@ -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<string, string> }>(
path.join(projectPath, 'package.json'),
(pkg) => {
pkg.name = packageName;
pkg.devDependencies ??= {};
pkg.devDependencies[VITE_PLUS_NAME] = VITE_PLUS_VERSION;
return pkg;
},
);
}
3 changes: 2 additions & 1 deletion packages/cli/src/create/templates/monorepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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(
Expand Down
Loading