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
3 changes: 3 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
"/^oxc-.*/",
"@oxc-node/*",
"@oxc-project/*",
"@tsdown/css",
"@tsdown/exe",
"@vitejs/devtools",
"lightningcss",
"oxfmt",
"oxlint",
"oxlint-tsgolint",
Expand Down
56 changes: 53 additions & 3 deletions .github/scripts/upgrade-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type LatestTagOptions = {

type NpmLatestResponse = {
version?: unknown;
dependencies?: Record<string, string>;
};

type UpstreamVersions = {
Expand All @@ -42,6 +43,7 @@ type UpstreamVersions = {
type PnpmWorkspaceVersions = {
vitest: string;
tsdown: string;
lightningcss: string;
oxcNodeCli: string;
oxcNodeCore: string;
oxfmt: string;
Expand Down Expand Up @@ -131,20 +133,37 @@ async function getLatestTag(
}

// ============ npm Registry ============
async function getLatestNpmVersion(packageName: string): Promise<string> {
async function fetchNpmLatest(packageName: string): Promise<NpmLatestResponse> {
const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
if (!res.ok) {
throw new Error(
`Failed to fetch npm version for ${packageName}: ${res.status} ${res.statusText}`,
`Failed to fetch npm metadata for ${packageName}: ${res.status} ${res.statusText}`,
);
}
const data = (await res.json()) as NpmLatestResponse;
return (await res.json()) as NpmLatestResponse;
}

async function getLatestNpmVersion(packageName: string): Promise<string> {
const data = await fetchNpmLatest(packageName);
if (typeof data.version !== 'string') {
throw new Error(`Invalid npm response for ${packageName}: missing version field`);
}
return data.version;
}

// Read a dependency range from the latest published version of `packageName`,
// e.g. the `lightningcss` range that the bundled `@tsdown/css` depends on.
async function getNpmDependencyRange(packageName: string, dependencyName: string): Promise<string> {
const data = await fetchNpmLatest(packageName);
const range = data.dependencies?.[dependencyName];
if (typeof range !== 'string') {
throw new Error(
`Invalid npm response for ${packageName}: missing dependencies.${dependencyName}`,
);
}
return range;
}

// ============ Update .upstream-versions.json ============
async function updateUpstreamVersions(): Promise<void> {
const filePath = path.join(ROOT, 'packages/tools/.upstream-versions.json');
Expand Down Expand Up @@ -212,6 +231,32 @@ async function updatePnpmWorkspace(versions: PnpmWorkspaceVersions): Promise<voi
replacement: `tsdown: ^${versions.tsdown}`,
newVersion: versions.tsdown,
},
// `@tsdown/css` and `@tsdown/exe` are bundled into core and published in
// lockstep with tsdown (they exact-peer-depend on the same tsdown version),
// so pin both catalog entries to the tsdown version to avoid drift.
{
name: '@tsdown/css',
pattern: /'@tsdown\/css': \^([\d.]+(?:-[\w.]+)?)/,
replacement: `'@tsdown/css': ^${versions.tsdown}`,
newVersion: versions.tsdown,
},
{
name: '@tsdown/exe',
pattern: /'@tsdown\/exe': \^([\d.]+(?:-[\w.]+)?)/,
replacement: `'@tsdown/exe': ^${versions.tsdown}`,
newVersion: versions.tsdown,
},
// `lightningcss` is a core dependency consumed by the bundled `@tsdown/css`.
// Track exactly what `@tsdown/css` requires (already an `^x.y.z` range) so a
// tsdown upgrade that bumps lightningcss is mirrored here.
{
name: 'lightningcss',
// Match any range value (not just `^x.y.z`) so the pattern can re-match
// whatever `@tsdown/css` declares (`~`, `>=`, compound ranges) on the next run.
pattern: /\n {2}lightningcss: ([^\n]+)\n/,
replacement: `\n lightningcss: ${versions.lightningcss}\n`,
newVersion: versions.lightningcss,
},
{
name: '@oxc-node/cli',
pattern: /'@oxc-node\/cli': \^([\d.]+(?:-[\w.]+)?)/,
Expand Down Expand Up @@ -516,6 +561,7 @@ console.log('Fetching latest versions…');
const [
vitestVersion,
tsdownVersion,
lightningcssVersion,
devtoolsVersion,
oxcNodeCliVersion,
oxcNodeCoreVersion,
Expand All @@ -530,6 +576,8 @@ const [
] = await Promise.all([
getLatestNpmVersion('vitest'),
getLatestNpmVersion('tsdown'),
// Mirror exactly what the bundled @tsdown/css depends on.
getNpmDependencyRange('@tsdown/css', 'lightningcss'),
getLatestNpmVersion('@vitejs/devtools'),
getLatestNpmVersion('@oxc-node/cli'),
getLatestNpmVersion('@oxc-node/core'),
Expand All @@ -545,6 +593,7 @@ const [

console.log(`vitest: ${vitestVersion}`);
console.log(`tsdown: ${tsdownVersion}`);
console.log(`lightningcss (from @tsdown/css): ${lightningcssVersion}`);
console.log(`@vitejs/devtools: ${devtoolsVersion}`);
console.log(`@oxc-node/cli: ${oxcNodeCliVersion}`);
console.log(`@oxc-node/core: ${oxcNodeCoreVersion}`);
Expand All @@ -561,6 +610,7 @@ await updateUpstreamVersions();
await updatePnpmWorkspace({
vitest: vitestVersion,
tsdown: tsdownVersion,
lightningcss: lightningcssVersion,
oxcNodeCli: oxcNodeCliVersion,
oxcNodeCore: oxcNodeCoreVersion,
oxfmt: oxfmtVersion,
Expand Down
8 changes: 8 additions & 0 deletions docs/guide/pack.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,12 @@ export default defineConfig({
});
```

Executable support is bundled into Vite+, so you do not need to install `@tsdown/exe` separately.

Building executables uses Node's [Single Executable Applications](https://nodejs.org/api/single-executable-applications.html) support and requires Node.js 25.7.0 or later. Switch the active runtime with `vp env use 26` if `vp pack --exe` reports an unsupported version.

See the official [tsdown executable docs](https://tsdown.dev/options/exe#executable) for details about configuring custom file names, embedded assets, and cross-platform targets.

## CSS Bundling

`vp pack` can transform and bundle CSS (including CSS Modules and [Lightning CSS](https://lightningcss.dev/) optimizations) for your entry points. This support is bundled into Vite+, so you do not need to install `@tsdown/css` or `lightningcss` separately, it works out of the box.
5 changes: 5 additions & 0 deletions packages/cli/snap-tests/command-pack-css/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "command-pack-css",
"version": "1.0.0",
"type": "module"
}
16 changes: 16 additions & 0 deletions packages/cli/snap-tests/command-pack-css/snap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
> vp pack src/index.ts --minify # bundles CSS via the bundled @tsdown/css + lightningcss (issue #1586)
ℹ entry: src/index.ts
ℹ Build start
ℹ dist/index.mjs <variable> kB │ gzip: <variable> kB
ℹ dist/style.css <variable> kB │ gzip: <variable> kB
ℹ 2 files, total: <variable> kB
✔ Build complete in <variable>ms

> cat dist/style.css # lightningcss-optimized output proves @tsdown/css ran
.foo {
color: red;
}

.bar {
margin: 0;
}
3 changes: 3 additions & 0 deletions packages/cli/snap-tests/command-pack-css/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import './style.css';

export const hello = 'world';
7 changes: 7 additions & 0 deletions packages/cli/snap-tests/command-pack-css/src/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.foo {
color: #ff0000;
}

.bar {
margin: 0px 0px 0px 0px;
}
7 changes: 7 additions & 0 deletions packages/cli/snap-tests/command-pack-css/steps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"ignoredPlatforms": ["win32"],
"commands": [
"vp pack src/index.ts --minify # bundles CSS via the bundled @tsdown/css + lightningcss (issue #1586)",
"cat dist/style.css # lightningcss-optimized output proves @tsdown/css ran"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "command-pack-tsdown-extensions",
"version": "1.0.0",
"type": "module"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> node verify-extensions.mjs # bundled @tsdown/exe and @tsdown/css load without a top-level tsdown (issue #1586)
tsdown-exe.js: getCacheDir, getCachedBinaryPath, getTargetSuffix, resolveNodeBinary
tsdown-css.js: CssPlugin, resolveCssOptions
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"ignoredPlatforms": ["win32"],
"commands": [
"node verify-extensions.mjs # bundled @tsdown/exe and @tsdown/css load without a top-level tsdown (issue #1586)"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Issue #1586: `@tsdown/exe` and `@tsdown/css` have a hard peer dependency on
// `tsdown` and import `tsdown/internal`, but Vite+ bundles tsdown internally and
// does not expose a resolvable top-level `tsdown` package. Before the fix, the
// bundled tsdown loaded them as external top-level packages, so `vp pack --exe`
// (and CSS bundling) failed with `Failed to import module "@tsdown/exe"`.
//
// They are now bundled into core, so this loads the bundled extension chunks
// directly to prove they resolve `tsdown/internal` against the bundled tsdown.
// `vp pack --exe` itself needs Node >= 25.7 (SEA), so it cannot run end-to-end
// in CI; this check is Node-version independent.
import { createRequire } from 'node:module';
import path from 'node:path';
import { pathToFileURL } from 'node:url';

const require = createRequire(import.meta.url);
const packEntry = require.resolve('@voidzero-dev/vite-plus-core/pack');
const tsdownDir = path.dirname(packEntry);

for (const chunk of ['tsdown-exe.js', 'tsdown-css.js']) {
const mod = await import(pathToFileURL(path.join(tsdownDir, chunk)).href);
console.log(`${chunk}: ${Object.keys(mod).sort().join(', ')}`);
}
Loading
Loading