Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
ad42c43
docs(rfc): run TypeScript scripts with vpx
fengmk2 Oct 7, 2026
3e66633
feat(binding): compile vendored oxc-node into the native binding
fengmk2 Oct 7, 2026
d4eb80c
feat(cli): add the vpx script loader hooks
fengmk2 Oct 7, 2026
98ec66b
feat(vpx): run script files with the oxc-node loader
fengmk2 Oct 7, 2026
f97d0bf
feat(cli): ship a project-local vpx bin
fengmk2 Oct 7, 2026
850e758
test(vpx): cover script mode in CLI snapshots and document it
fengmk2 Oct 7, 2026
80d19b0
docs(rfc): record the vpx script prototype findings
fengmk2 Oct 7, 2026
274a362
fix(oxc-node): carry the vendored source changes as a patch
fengmk2 Oct 7, 2026
b17fe55
chore(deps): follow Rolldown's @oxc-project/runtime pin
fengmk2 Oct 7, 2026
3157566
fix(vpx): register the script hooks from a CommonJS preload
fengmk2 Oct 7, 2026
78024e5
feat(vpx): print the Vite+ version for -v/--version
fengmk2 Oct 7, 2026
efde533
feat(toolchain): report the vendored oxc-node
fengmk2 Oct 7, 2026
632efb3
test(vpx): cover more script-mode cases in CLI snapshots
fengmk2 Oct 7, 2026
34d402c
docs(vpx): mark script mode experimental and update the RFC
fengmk2 Oct 7, 2026
9a58bee
docs(rfc): use a real word for the mistyped-path example
fengmk2 Oct 7, 2026
41d57ae
build: exclude the vendored oxc-node clone from tests, fmt, and tsc
fengmk2 Oct 7, 2026
9295497
test(snapshots): redact Windows file URLs to the Unix spelling
fengmk2 Oct 7, 2026
77329ad
build(oxc-node): sync the vendored source to v0.1.5
fengmk2 Oct 8, 2026
ae75e4e
docs(rfc): record the oxc-node v0.1.5 fixes
fengmk2 Oct 8, 2026
90ac0a6
ci: run the oxc-node patch script with node instead of npx tsx
fengmk2 Oct 8, 2026
484d870
docs(vpx): keep `.cts` ESM support and link the oxc-node follow-ups
fengmk2 Oct 8, 2026
cd182b5
build(oxc-node): sync the vendored source to v0.1.6
fengmk2 Oct 10, 2026
858408f
docs(vpx): record the oxc-node v0.1.6 fixes
fengmk2 Oct 10, 2026
6a96994
refactor(vpx): simplify the loader, patch tooling, and detection
fengmk2 Oct 10, 2026
aa206a3
perf(vpx): use in-thread hooks on Node.js 22.22.3+ and 24.11.1+
fengmk2 Oct 10, 2026
08e2abd
docs(rfc): link the oxc-node request for TypeScript dependencies
fengmk2 Oct 10, 2026
77ae04c
docs(rfc): compare vpx startup with tsx
fengmk2 Oct 10, 2026
3cb251e
docs(rfc): benchmark the large file as ESM too
fengmk2 Oct 10, 2026
8e30233
bench: add the vpx script startup benchmark
fengmk2 Oct 10, 2026
57c6e75
docs(vpx): document tsconfig selection and the defaults without one
fengmk2 Oct 10, 2026
4ae9f52
docs(vpx): split package binaries and scripts into separate sections
fengmk2 Oct 10, 2026
ffd703c
docs(vpx): drop the experimental label from script mode
fengmk2 Oct 10, 2026
f09f23e
test(vpx): cover JSX customizations in CLI snapshots
fengmk2 Oct 12, 2026
b3b6099
docs(vpx): add a JSX section and compare tsconfig handling with tsx
fengmk2 Oct 12, 2026
bc6cef4
docs(vpx): note that tsx ignores a lone @jsxImportSource comment
fengmk2 Oct 12, 2026
e71c558
docs(vpx): list supported JSX libraries and test Hono's pragma pair
fengmk2 Oct 12, 2026
8a0190a
docs(vpx): keep the JSX section library-neutral
fengmk2 Oct 12, 2026
505615a
ci(upgrade-deps): keep an oxc-node pin that the latest tag lacks
fengmk2 Oct 12, 2026
0451509
build(oxc-node): sync the fix for transforming CommonJS twice
fengmk2 Oct 12, 2026
50cb62c
docs(rfc): link the oxc-node issue about extra CommonJS reads
fengmk2 Oct 12, 2026
100fa3a
docs(rfc): link the oxc-node issue about extensionless ES modules
fengmk2 Oct 12, 2026
cd9394d
docs(rfc): note the Node.js releases with nodejs/node#62920
fengmk2 Oct 12, 2026
5050ff0
build(oxc-node): sync the TypeScript dependencies option and extensio…
fengmk2 Oct 13, 2026
37a6496
build(oxc-node): sync the fix for extra CommonJS reads
fengmk2 Oct 13, 2026
4c9e56c
refactor(vpx): give oxc-node a private helper module name
fengmk2 Oct 13, 2026
781e7c7
test(snapshots): redact percent-encoded file URLs
fengmk2 Oct 13, 2026
f6cfbb5
perf(vpx): require the binding in the off-thread hooks
fengmk2 Oct 13, 2026
9e5a7a5
refactor(vpx): tidy loader comments and reuse helpers
fengmk2 Oct 13, 2026
08ab784
docs(rfc): note what package mode costs through the local bin
fengmk2 Oct 13, 2026
4f74784
docs(rfc): rerun the Node.js 22.18 benchmark
fengmk2 Oct 13, 2026
26fb20d
build(oxc-node): link v0.1.7 without its napi feature and drop the patch
fengmk2 Oct 14, 2026
9b0c939
fix(binding): keep the oxcNode forwarders lint-clean
fengmk2 Oct 14, 2026
74399b6
test(vpx): retry the edit in the watch snapshot until it restarts
fengmk2 Oct 14, 2026
02dd857
docs(vpx): correct how tsx applies tsconfig paths
fengmk2 Oct 15, 2026
7966a2b
docs(rfc): bring status, toolchain output, and checklist up to date
fengmk2 Oct 15, 2026
7fc85c6
test(vpx): share detection cases between the two CLIs and cover loade…
fengmk2 Oct 15, 2026
57bafa9
docs(vpx): add a "Coming from tsx" section and fill RFC gaps from review
fengmk2 Oct 15, 2026
ed83b40
fix(vpx): leave bare names after Node.js options to Node.js
fengmk2 Oct 15, 2026
8828599
fix(vpx): report a missing explicit path even with --package
fengmk2 Oct 15, 2026
a6d696c
feat(vpx): print script-mode help from the local bin without the glob…
fengmk2 Oct 15, 2026
c809bde
test(vpx): cover the in-thread hooks on Node.js 22.23
fengmk2 Oct 15, 2026
3d275c1
docs(vpx): point agents and `vp --help` to `vpx <file>`
fengmk2 Oct 15, 2026
f2eb613
docs(vpx): align the guide and RFC help example with the code
fengmk2 Oct 15, 2026
468b92a
fix(vpx): run package mode in the local bin instead of the global vpx
fengmk2 Oct 15, 2026
4aa76bf
fix(vpx): hand vp exec the binary the local bin found
fengmk2 Oct 15, 2026
8856fe3
fix(vpx): run installed binaries directly from the local bin
fengmk2 Oct 15, 2026
dfde76d
chore(deps): regenerate pnpm-lock.yaml after the rebase onto oxc 0.154.0
fengmk2 Oct 15, 2026
ad6b713
docs(vpx): condense the RFC to the design as built
fengmk2 Oct 15, 2026
f830c7c
docs(vpx): correct where a script process loads the binding
fengmk2 Oct 15, 2026
6e79364
docs(vpx): compare the global and local vpx with tsx separately
fengmk2 Oct 15, 2026
cfae7c1
docs(vpx): list the tsx differences found in Windows testing
fengmk2 Oct 15, 2026
46f3253
fix(vpx): relay IPC through the local bin to the script
fengmk2 Oct 15, 2026
1403ce1
docs(vpx): document the differences from tsx found in e2e testing
fengmk2 Oct 15, 2026
333087d
chore(deps): sync oxc-node to ed51b8b for the fixes from e2e testing
fengmk2 Oct 16, 2026
718a88b
docs(vpx): drop the tsx differences that oxc-node fixed
fengmk2 Oct 16, 2026
7437cd7
docs(vpx): list the # specifier difference in the RFC
fengmk2 Oct 17, 2026
92f6a1d
chore(deps): sync oxc-node to 1c23b37
fengmk2 Oct 17, 2026
4e39ef5
docs(vpx): record the oxc-node fixes from 1c23b37
fengmk2 Oct 17, 2026
24c759f
test(snapshots): expose the local vpx bin on Windows
fengmk2 Oct 17, 2026
8e3a17b
fix(vpx): keep the script's exit code when the parent closes IPC first
fengmk2 Oct 17, 2026
ddf8bdf
test(snapshots): run a .cmd binary through vpx on Windows
fengmk2 Oct 17, 2026
3b6fdaa
docs(vpx): note the Windows limits of the local bin
fengmk2 Oct 17, 2026
3af61d5
test(snapshots): model the Windows .cmd tool on cmd-shim
fengmk2 Oct 17, 2026
9bf5222
refactor(vpx): simplify the loader, the local bin, and the global vpx
fengmk2 Oct 18, 2026
122dfd3
fix(vpx): skip the entry-file check after --eval, --print, and --run
fengmk2 Oct 18, 2026
06cc9bc
fix(vpx): let Node.js take --test globs and underscore option spellings
fengmk2 Oct 18, 2026
e81ee9a
fix(vpx): exec the script in place from the project-local bin on Unix
fengmk2 Oct 18, 2026
af99e8c
fix(vpx): resolve runtime helpers for require() on the in-thread hook…
fengmk2 Oct 18, 2026
97428cc
fix(vpx): pass the loader's real path to Node.js from the global vpx
fengmk2 Oct 18, 2026
d0efe61
fix(vpx): run scripts on the system-first Node.js that vp node selects
fengmk2 Oct 18, 2026
2bd29a1
docs(vpx): record the off-thread hook limits and the new loader behavior
fengmk2 Oct 18, 2026
23daf2d
fix(vpx): resolve from the working directory when Node.js passes it a…
fengmk2 Oct 18, 2026
5d23a14
docs(vpx): link the oxc-node issue behind the directory parent workar…
fengmk2 Oct 18, 2026
1abca79
docs(vpx): refresh the startup numbers after the local bin execs in p…
fengmk2 Oct 18, 2026
6601fe7
perf(vpx): exec a package binary directly when it can run
fengmk2 Oct 18, 2026
18e385a
chore(deps): keep @oxc-project/runtime on Rolldown's pin after the re…
fengmk2 Oct 20, 2026
2d68bab
chore(deps): sync oxc-node to ce22828
fengmk2 Oct 20, 2026
bd39101
chore(deps): let @oxc-project/runtime follow the latest oxc release a…
fengmk2 Oct 20, 2026
1467d49
docs(vpx): bring the RFC up to date
fengmk2 Oct 20, 2026
fddc0a7
fix(vpx): die by the child's signal in the local bin's spawn path
fengmk2 Oct 20, 2026
7ecedca
fix(vpx): report --tsconfig without a path as a vpx option
fengmk2 Oct 20, 2026
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
14 changes: 14 additions & 0 deletions .github/actions/clone/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ runs:
run: |
node -e "console.log('ROLLDOWN_HASH=' + require('./packages/tools/.upstream-versions.json').rolldown.hash)" >> $GITHUB_OUTPUT
node -e "console.log('ROLLDOWN_VITE_HASH=' + require('./packages/tools/.upstream-versions.json')['vite'].hash)" >> $GITHUB_OUTPUT
node -e "console.log('OXC_NODE_HASH=' + require('./packages/tools/.upstream-versions.json')['oxc-node'].hash)" >> $GITHUB_OUTPUT

- name: Output ecosystem ci project hash
shell: bash
Expand All @@ -62,6 +63,19 @@ runs:
ref: ${{ steps.upstream-versions.outputs.ROLLDOWN_VITE_HASH }}
persist-credentials: false

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: oxc-project/oxc-node
path: oxc-node
ref: ${{ steps.upstream-versions.outputs.OXC_NODE_HASH }}
persist-credentials: false

# oxc-node compiles into the native binding, and Cargo.lock records the
# patched manifest, so patch it before any cargo command reads it.
- name: Patch vendored oxc-node
shell: bash
run: node packages/tools/src/patch-oxc-node.ts

# Match the manifests used by sync-remote when it generated the root lockfile.
# The later dependency installation uses --frozen-lockfile, so alignment must run first.
- name: Align vendored Vite's Vitest dependencies
Expand Down
2 changes: 2 additions & 0 deletions .github/actions/compute-native-cache-input-hash/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ runs:
${{
hashFiles(
'packages/tools/.upstream-versions.json',
'packages/tools/src/patch-oxc-node.ts',
Comment thread
fengmk2 marked this conversation as resolved.
'packages/tools/src/cargo-toml.ts',
'rust-toolchain.toml',
'Cargo.lock',
'Cargo.toml',
Expand Down
14 changes: 14 additions & 0 deletions .github/scripts/__tests__/upgrade-deps.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,16 @@ test.each([false, true])(
vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
vi.spyOn(console, 'log').mockImplementation(() => {});
vi.stubEnv('UPGRADE_DEPS_META_DIR', metaDir);
// Each run also covers one side of the oxc-node pin guard: a release that does not
// contain the pinned commit leaves the pin alone.
const pinnedOxcNode = JSON.parse(
readFileSync(join(tempDir, 'packages/tools/.upstream-versions.json'), 'utf8'),
)['oxc-node'].hash;
vi.stubGlobal('fetch', async (url: string) => {
if (url.startsWith('https://api.github.com/repos/oxc-project/oxc-node/compare/')) {
expect(url).toContain(`/compare/${pinnedOxcNode}...`);
return Response.json({ status: failInstall ? 'diverged' : 'ahead' });
}
if (url.startsWith('https://api.github.com/repos/')) {
return Response.json([{ name: 'v1.2.3', commit: { sha: 'a'.repeat(40) } }]);
}
Expand Down Expand Up @@ -121,6 +130,11 @@ test.each([false, true])(
expect(versions['@oxlint/plugins'].new).toBe('1.88.0');
expect(versions['@oxlint/plugins'].new).toBe(versions.oxlint.new);
expect(versions['oxlint-tsgolint'].new).toBe('7.0.2004');
expect(versions['oxc-node']).toEqual(
failInstall
? { old: pinnedOxcNode, new: pinnedOxcNode }
: { old: pinnedOxcNode, new: 'a'.repeat(40), tag: 'v1.2.3' },
);
if (!failInstall) {
expect(versions['yuku-parser']).toEqual({ old: '^0.9.3', new: '0.11.2' });
}
Expand Down
53 changes: 43 additions & 10 deletions .github/scripts/upgrade-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ type UpstreamVersions = {
vite: {
hash: string;
};
'oxc-node': {
hash: string;
};
};

type PnpmWorkspaceVersions = {
Expand Down Expand Up @@ -106,21 +109,22 @@ function recordChange(
}

// ============ GitHub API ============
function fetchGitHubRepo(owner: string, repo: string, endpoint: string): Promise<Response> {
return fetch(`https://api.github.com/repos/${owner}/${repo}/${endpoint}`, {
headers: {
Authorization: `token ${process.env.GITHUB_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
},
});
}

async function getLatestTag(
owner: string,
repo: string,
options: LatestTagOptions = {},
): Promise<LatestTag> {
const perPage = options.stableOnly ? 100 : 1;
const res = await fetch(
`https://api.github.com/repos/${owner}/${repo}/tags?per_page=${perPage}`,
{
headers: {
Authorization: `token ${process.env.GITHUB_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
},
},
);
const res = await fetchGitHubRepo(owner, repo, `tags?per_page=${perPage}`);
if (!res.ok) {
throw new Error(`Failed to fetch tags for ${owner}/${repo}: ${res.status} ${res.statusText}`);
}
Expand All @@ -141,6 +145,25 @@ async function getLatestTag(
return { sha: latest.commit.sha, tag: latest.name };
}

// Whether `head` contains every commit of `base`, so moving a pin from `base` to `head`
// drops nothing.
async function containsCommit(
owner: string,
repo: string,
base: string,
head: string,
): Promise<boolean> {
if (base === head) {
return true;
}
const res = await fetchGitHubRepo(owner, repo, `compare/${base}...${head}`);
if (!res.ok) {
throw new Error(`Failed to compare ${owner}/${repo} ${base}...${head}: ${res.status}`);
}
const { status } = (await res.json()) as { status?: unknown };
return status === 'ahead' || status === 'identical';
}

// ============ npm Registry ============
async function fetchNpmLatest(packageName: string): Promise<NpmLatestResponse> {
const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
Expand Down Expand Up @@ -197,14 +220,24 @@ async function updateUpstreamVersions(): Promise<void> {

const oldRolldownHash = data.rolldown.hash;
const oldViteHash = data.vite.hash;
const [rolldown, vite] = await Promise.all([
const oldOxcNodeHash = data['oxc-node'].hash;
const [rolldown, vite, oxcNode] = await Promise.all([
getLatestTag('rolldown', 'rolldown'),
getLatestTag('vitejs', 'vite', { stableOnly: true }),
// Vendored into the native binding; `sync-remote` re-applies its patch.
getLatestTag('oxc-project', 'oxc-node', { stableOnly: true }),
]);
data.rolldown.hash = rolldown.sha;
data.vite.hash = vite.sha;
recordChange('rolldown', oldRolldownHash, rolldown.sha, rolldown.tag);
recordChange('vite', oldViteHash, vite.sha, vite.tag);
// A pin to an unreleased fix on oxc-node's main stays until a release contains it.
if (await containsCommit('oxc-project', 'oxc-node', oldOxcNodeHash, oxcNode.sha)) {
data['oxc-node'].hash = oxcNode.sha;
recordChange('oxc-node', oldOxcNodeHash, oxcNode.sha, oxcNode.tag);
} else {
recordChange('oxc-node', oldOxcNodeHash, oldOxcNodeHash);
}

fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
console.log('Updated .upstream-versions.json');
Expand Down
23 changes: 22 additions & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ jobs:
- name: Output rolldown hash
id: upstream-versions
shell: bash
run: node -e "console.log('ROLLDOWN_HASH=' + require('./packages/tools/.upstream-versions.json').rolldown.hash)" >> $GITHUB_OUTPUT
run: |
node -e "console.log('ROLLDOWN_HASH=' + require('./packages/tools/.upstream-versions.json').rolldown.hash)" >> $GITHUB_OUTPUT
node -e "console.log('OXC_NODE_HASH=' + require('./packages/tools/.upstream-versions.json')['oxc-node'].hash)" >> $GITHUB_OUTPUT

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -41,6 +43,25 @@ jobs:
ref: ${{ steps.upstream-versions.outputs.ROLLDOWN_HASH }}
persist-credentials: false

# The binding's `oxc-node` path dependency must resolve against the
# patched manifest recorded in Cargo.lock.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: oxc-project/oxc-node
path: oxc-node
ref: ${{ steps.upstream-versions.outputs.OXC_NODE_HASH }}
persist-credentials: false

# Run the patch script with native TypeScript support instead of unpinned `npx tsx`.
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
package-manager-cache: false

- name: Patch vendored oxc-node
shell: bash
run: node packages/tools/src/patch-oxc-node.ts

- name: Clear git origin for nested checkout
shell: bash
run: git remote remove origin
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dist
rolldown
rolldown-vite
vite
oxc-node
/crates/vp_global_cli/vp
.void/
.direnv
Expand Down
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ members = ["bench", "crates/*", "packages/cli/binding"]
# It needs a separate release profile and a crate-local build-std config.
# Cargo ignores `panic` in per-package profile overrides.
# See crates/vp_trampoline/Cargo.toml.
exclude = ["crates/vp_trampoline"]
# oxc-node is vendored upstream source (see packages/tools/src/patch-oxc-node.ts);
# excluding it keeps it out of workspace lints, like the nested rolldown workspace.
exclude = ["crates/vp_trampoline", "oxc-node"]

[workspace.metadata.cargo-shear]
ignored = [
Expand Down Expand Up @@ -354,6 +356,13 @@ oxc_resolver = { version = "11.24.3", features = ["yarn_pnp"] }
oxc_resolver_napi = { version = "11.24.3", default-features = false, features = ["yarn_pnp"] }
oxc_sourcemap = "9.0.0"

# vendored oxc-node, compiled into the native binding for `vpx <script>`; without its
# `napi` feature it registers no exports and declares no global allocator, and
# `packages/cli/binding/src/oxc_node.rs` forwards what the loader uses. `yarn_pnp`
# matches the `oxc_resolver` feature the workspace enables, which changes oxc-node's
# file system signature.
oxc-node = { path = "./oxc-node", default-features = false, features = ["yarn_pnp"] }

# rolldown crates
rolldown = { path = "./rolldown/crates/rolldown" }
rolldown_binding = { path = "./rolldown/crates/rolldown_binding" }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ Use `vp migrate` to migrate to Vite+. It merges tool-specific config files such

- **run** - Run monorepo tasks
- **exec** - Execute a command from local `node_modules/.bin`
- **node** - Run a Node.js script with the resolved Vite+ environment
- **node** - Run a Node.js script with the resolved Vite+ environment; use `vpx <file>` for TypeScript
- **dlx** - Execute a package binary without installing it as a dependency
- **cache** - Manage the task cache

Expand Down
Loading
Loading