Skip to content

Commit f47d2a4

Browse files
committed
fix(create): add vp create e2e tests and fix yarn/bun migration bugs (#1317)
## Summary Motivated by #1297 the `.yarnrc` template fix revealed that `vp create` had no e2e test coverage, making it easy for template bugs to ship undetected. - Add a new GitHub Actions workflow (`test-vp-create.yml`) that e2e tests `vp create` across all 3 built-in templates (monorepo, application, library) and all 4 package managers (pnpm, npm, yarn, bun) — 12 test combinations - Fix bun monorepo migration writing empty `catalog` with dangling `catalog:` references when using `file:` protocol - Fix yarn standalone projects missing `.yarnrc.yml` with `nodeLinker: node-modules` - Pass `--no-frozen-lockfile` from `vp create` in CI so yarn Berry can create fresh lockfiles ## Changes ### New: `vp create` e2e test workflow - Uses the same tgz-packaging approach as ecosystem-ci: build → pack → install vp from tgz → run `vp create` with `VP_OVERRIDE_PACKAGES`/`VP_VERSION` pointing to local packages - Each test verifies: project structure, correct lockfile, `vite-plus@0.0.0` installed, `vp check`, and build/test commands - Monorepo tests run `vp run ready` twice and assert 100% cache hit on the second run - Triggered on push to main (path-filtered), PRs with `test: create-e2e` label, or create-related file changes ### Fix: bun monorepo catalog with `file:` protocol - `rewriteBunCatalog()` unconditionally set all overrides to `catalog:` but skipped `file:` entries from the catalog, leaving unresolvable references - Now uses `file:` paths directly in overrides when the value starts with `file:` ### Fix: yarn standalone projects - `rewriteYarnrcYml()` now sets `nodeLinker: node-modules` when absent (yarn 4 defaults to PnP) - `rewriteStandaloneProject()` now calls `rewriteYarnrcYml()` for yarn projects (previously only called for monorepos) - `vp create` passes `--no-frozen-lockfile` to `vp install` in CI environments ## Test plan - [x] All 12 matrix jobs pass in CI (3 templates × 4 package managers) - [x] Existing migration tests pass (`vp test run packages/cli/src/migration/`) - [x] New `bun-catalog-file-protocol.spec.ts` test verifies the bun override fix 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent f64bb6c commit f47d2a4

File tree

5 files changed

+377
-5
lines changed

5 files changed

+377
-5
lines changed
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
name: Test vp create
2+
3+
permissions: {}
4+
5+
on:
6+
workflow_dispatch:
7+
push:
8+
branches:
9+
- main
10+
paths:
11+
- 'packages/cli/src/create/**'
12+
- 'packages/cli/templates/**'
13+
- 'packages/cli/src/migration/**'
14+
- '.github/workflows/test-vp-create.yml'
15+
pull_request:
16+
types: [opened, synchronize, labeled]
17+
18+
concurrency:
19+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
20+
cancel-in-progress: ${{ github.ref_name != 'main' }}
21+
22+
defaults:
23+
run:
24+
shell: bash
25+
26+
jobs:
27+
detect-changes:
28+
runs-on: namespace-profile-linux-x64-default
29+
permissions:
30+
contents: read
31+
pull-requests: read
32+
outputs:
33+
related-files-changed: ${{ steps.filter.outputs.related-files }}
34+
steps:
35+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
36+
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
37+
id: filter
38+
with:
39+
filters: |
40+
related-files:
41+
- 'packages/cli/src/create/**'
42+
- 'packages/cli/templates/**'
43+
- 'packages/cli/src/migration/**'
44+
- .github/workflows/test-vp-create.yml
45+
46+
download-previous-rolldown-binaries:
47+
needs: detect-changes
48+
runs-on: namespace-profile-linux-x64-default
49+
# Run if: not a PR, OR PR has 'test: create-e2e' label, OR create-related files changed
50+
if: >-
51+
github.event_name != 'pull_request' ||
52+
contains(github.event.pull_request.labels.*.name, 'test: create-e2e') ||
53+
needs.detect-changes.outputs.related-files-changed == 'true'
54+
permissions:
55+
contents: read
56+
packages: read
57+
steps:
58+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
59+
- uses: ./.github/actions/download-rolldown-binaries
60+
with:
61+
github-token: ${{ secrets.GITHUB_TOKEN }}
62+
63+
build:
64+
name: Build vite-plus packages
65+
runs-on: namespace-profile-linux-x64-default
66+
permissions:
67+
contents: read
68+
packages: read
69+
needs:
70+
- download-previous-rolldown-binaries
71+
steps:
72+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
73+
- uses: ./.github/actions/clone
74+
75+
- uses: oxc-project/setup-rust@23f38cfb0c04af97a055f76acee94d5be71c7c82 # v1.0.16
76+
with:
77+
save-cache: ${{ github.ref_name == 'main' }}
78+
cache-key: create-e2e-build
79+
80+
- uses: oxc-project/setup-node@4c26e7cb3605b6bdef5450dacd02c434b10fd8ba # v1.2.0
81+
82+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
83+
with:
84+
name: rolldown-binaries
85+
path: ./rolldown/packages/rolldown/src
86+
merge-multiple: true
87+
88+
- name: Build with upstream
89+
uses: ./.github/actions/build-upstream
90+
with:
91+
target: x86_64-unknown-linux-gnu
92+
93+
- name: Pack packages into tgz
94+
run: |
95+
mkdir -p tmp/tgz
96+
cd packages/core && pnpm pack --pack-destination ../../tmp/tgz && cd ../..
97+
cd packages/test && pnpm pack --pack-destination ../../tmp/tgz && cd ../..
98+
cd packages/cli && pnpm pack --pack-destination ../../tmp/tgz && cd ../..
99+
# Copy vp binary for test jobs
100+
cp target/x86_64-unknown-linux-gnu/release/vp tmp/tgz/vp
101+
ls -la tmp/tgz
102+
103+
- name: Upload tgz artifacts
104+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
105+
with:
106+
name: vite-plus-packages
107+
path: tmp/tgz/
108+
retention-days: 1
109+
110+
test-vp-create:
111+
name: vp create ${{ matrix.template.name }} (${{ matrix.package-manager }})
112+
runs-on: namespace-profile-linux-x64-default
113+
permissions:
114+
contents: read
115+
needs:
116+
- build
117+
timeout-minutes: 15
118+
strategy:
119+
fail-fast: false
120+
matrix:
121+
template:
122+
- name: monorepo
123+
create-args: vite:monorepo --directory test-project
124+
template-args: ''
125+
verify-command: vp run ready
126+
- name: application
127+
create-args: vite:application --directory test-project
128+
template-args: '-- --template vanilla-ts'
129+
verify-command: vp run build
130+
- name: library
131+
create-args: vite:library --directory test-project
132+
template-args: ''
133+
verify-command: |
134+
vp run build
135+
vp run test
136+
package-manager:
137+
- pnpm
138+
- npm
139+
- yarn
140+
- bun
141+
env:
142+
VP_OVERRIDE_PACKAGES: '{"vite":"file:${{ github.workspace }}/tmp/tgz/voidzero-dev-vite-plus-core-0.0.0.tgz","vitest":"file:${{ github.workspace }}/tmp/tgz/voidzero-dev-vite-plus-test-0.0.0.tgz","@voidzero-dev/vite-plus-core":"file:${{ github.workspace }}/tmp/tgz/voidzero-dev-vite-plus-core-0.0.0.tgz","@voidzero-dev/vite-plus-test":"file:${{ github.workspace }}/tmp/tgz/voidzero-dev-vite-plus-test-0.0.0.tgz"}'
143+
VP_VERSION: 'file:${{ github.workspace }}/tmp/tgz/vite-plus-0.0.0.tgz'
144+
# Force full dependency rewriting so the library template's existing
145+
# vite-plus dep gets overridden with the local tgz
146+
VP_FORCE_MIGRATE: '1'
147+
steps:
148+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
149+
150+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
151+
with:
152+
node-version: 24
153+
154+
- name: Download vite-plus packages
155+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
156+
with:
157+
name: vite-plus-packages
158+
path: tmp/tgz
159+
160+
- name: Install vp CLI
161+
run: |
162+
mkdir -p target/release
163+
cp tmp/tgz/vp target/release/vp
164+
chmod +x target/release/vp
165+
node $GITHUB_WORKSPACE/packages/tools/src/install-global-cli.ts --tgz $GITHUB_WORKSPACE/tmp/tgz/vite-plus-0.0.0.tgz
166+
echo "$HOME/.vite-plus/bin" >> $GITHUB_PATH
167+
168+
- name: Verify vp installation
169+
run: |
170+
which vp
171+
vp --version
172+
173+
- name: Run vp create ${{ matrix.template.name }} with ${{ matrix.package-manager }}
174+
working-directory: ${{ runner.temp }}
175+
run: |
176+
vp create ${{ matrix.template.create-args }} \
177+
--no-interactive \
178+
--no-agent \
179+
--package-manager ${{ matrix.package-manager }} \
180+
${{ matrix.template.template-args }}
181+
182+
- name: Verify project structure
183+
working-directory: ${{ runner.temp }}/test-project
184+
run: |
185+
# package.json must exist
186+
test -f package.json
187+
echo "✓ package.json exists"
188+
cat package.json
189+
190+
# List all files for debugging
191+
echo "--- Project root files ---"
192+
ls -la
193+
194+
# Check correct lockfile exists
195+
case "${{ matrix.package-manager }}" in
196+
pnpm)
197+
test -f pnpm-lock.yaml
198+
echo "✓ pnpm-lock.yaml exists"
199+
;;
200+
npm)
201+
test -f package-lock.json
202+
echo "✓ package-lock.json exists"
203+
;;
204+
yarn)
205+
test -f yarn.lock
206+
echo "✓ yarn.lock exists"
207+
;;
208+
bun)
209+
if [ -f bun.lock ]; then
210+
echo "✓ bun.lock exists"
211+
elif [ -f bun.lockb ]; then
212+
echo "✓ bun.lockb exists"
213+
else
214+
echo "✗ No bun lockfile found"
215+
exit 1
216+
fi
217+
;;
218+
esac
219+
220+
# node_modules must exist (vp install ran successfully)
221+
test -d node_modules
222+
echo "✓ node_modules exists"
223+
224+
# Monorepo-specific checks
225+
if [ "${{ matrix.template.name }}" = "monorepo" ]; then
226+
test -d apps/website
227+
echo "✓ apps/website exists"
228+
test -d packages/utils
229+
echo "✓ packages/utils exists"
230+
231+
case "${{ matrix.package-manager }}" in
232+
pnpm)
233+
test -f pnpm-workspace.yaml
234+
echo "✓ pnpm-workspace.yaml exists"
235+
;;
236+
yarn)
237+
test -f .yarnrc.yml
238+
echo "✓ .yarnrc.yml exists"
239+
;;
240+
esac
241+
fi
242+
243+
- name: Verify local tgz packages installed
244+
working-directory: ${{ runner.temp }}/test-project
245+
run: |
246+
node -e "
247+
const path = require('path');
248+
const pkg = require(path.resolve('node_modules/vite-plus/package.json'));
249+
if (pkg.version !== '0.0.0') {
250+
console.error('Expected vite-plus@0.0.0, got ' + pkg.version);
251+
process.exit(1);
252+
}
253+
console.log('✓ vite-plus@' + pkg.version + ' installed correctly');
254+
"
255+
256+
- name: Run vp check
257+
working-directory: ${{ runner.temp }}/test-project
258+
run: vp check
259+
260+
- name: Verify project builds
261+
working-directory: ${{ runner.temp }}/test-project
262+
run: ${{ matrix.template.verify-command }}
263+
264+
- name: Verify cache (monorepo only)
265+
if: matrix.template.name == 'monorepo'
266+
working-directory: ${{ runner.temp }}/test-project
267+
run: |
268+
output=$(vp run ready 2>&1)
269+
echo "$output"
270+
if ! echo "$output" | grep -q 'cache hit (100%)'; then
271+
echo "✗ Expected 100% cache hit on second run"
272+
exit 1
273+
fi
274+
echo "✓ 100% cache hit verified"

packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export default defineConfig({
6565
}
6666

6767
> cat .yarnrc.yml # check .yarnrc.yml
68+
nodeLinker: node-modules
6869
catalog:
6970
vite: npm:@voidzero-dev/vite-plus-core@latest
7071
vitest: npm:@voidzero-dev/vite-plus-test@latest

packages/cli/src/create/bin.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
439439
let selectedParentDir: string | undefined;
440440
let remoteTargetDir: string | undefined;
441441
let shouldSetupHooks = false;
442+
const installArgs = process.env.CI ? ['--no-frozen-lockfile'] : undefined;
442443

443444
if (!selectedTemplateName) {
444445
const template = await prompts.select({
@@ -804,7 +805,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
804805
installGitHooks(fullPath, compactOutput);
805806
}
806807
updateCreateProgress('Installing dependencies');
807-
const installSummary = await runViteInstall(fullPath, options.interactive, undefined, {
808+
const installSummary = await runViteInstall(fullPath, options.interactive, installArgs, {
808809
silent: compactOutput,
809810
});
810811
updateCreateProgress('Formatting code');
@@ -955,7 +956,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
955956

956957
updateWorkspaceConfig(projectDir, workspaceInfo);
957958
updateCreateProgress('Installing dependencies');
958-
installSummary = await runViteInstall(workspaceInfo.rootDir, options.interactive, undefined, {
959+
installSummary = await runViteInstall(workspaceInfo.rootDir, options.interactive, installArgs, {
959960
silent: compactOutput,
960961
});
961962
updateCreateProgress('Formatting code');
@@ -969,7 +970,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
969970
installGitHooks(fullPath, compactOutput);
970971
}
971972
updateCreateProgress('Installing dependencies');
972-
installSummary = await runViteInstall(fullPath, options.interactive, undefined, {
973+
installSummary = await runViteInstall(fullPath, options.interactive, installArgs, {
973974
silent: compactOutput,
974975
});
975976
updateCreateProgress('Formatting code');

0 commit comments

Comments
 (0)