Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
"vite",
"vitest",
"vitest-dev",
"yuku-parser"
"yuku"
],
"enabled": false
},
Expand Down
70 changes: 32 additions & 38 deletions .github/scripts/__tests__/upgrade-deps.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ test.each([false, true])(
'runs the upgrade pipeline and retains metadata (install fails: %s)',
async (failInstall) => {
const root = resolve(import.meta.dirname, '../../..');
const { root: tempDir, metaDir, corePath, pluginPath, parserPath } = createSyncFixture();
const { root: tempDir, metaDir, corePath, pluginPath, yukuPath } = createSyncFixture();
onTestFinished(() => {
vi.mocked(childProcess.execFileSync).mockReset();
vi.restoreAllMocks();
Expand Down Expand Up @@ -114,12 +114,12 @@ test.each([false, true])(
if (args?.[0] === 'dedupe') {
// The actual bundled plugin can change during dependency deduplication.
writeJson(pluginPath, {
dependencies: { 'yuku-codegen': '^0.11.0', 'yuku-parser': '^0.11.0' },
dependencies: { yuku: '^0.11.0' },
});
writeJson(parserPath, { version: '0.11.2' });
writeJson(yukuPath, { version: '0.11.2' });
}
if (commands.length === 4) {
expect(readJson(corePath).dependencies['yuku-parser']).toBe('0.11.2');
expect(readJson(corePath).dependencies.yuku).toBe('0.11.2');
}
return Buffer.from('');
});
Expand All @@ -128,7 +128,7 @@ test.each([false, true])(
if (failInstall) {
await expect(import('../upgrade-deps.ts')).rejects.toThrow('Installation failed');
expect(commands).toEqual([['install', '--no-frozen-lockfile']]);
expect(readJson(corePath).dependencies['yuku-parser']).toBe('^0.9.3');
expect(readJson(corePath).dependencies.yuku).toBe('^0.9.3');
} else {
await import('../upgrade-deps.ts');
expect(commands).toEqual([
Expand Down Expand Up @@ -158,7 +158,7 @@ test.each([false, true])(
: { 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' });
expect(versions.yuku).toEqual({ old: '^0.9.3', new: '0.11.2' });
}
expect(readFileSync(join(metaDir, 'commit-message.txt'), 'utf8')).toContain(
'- lint-staged: 16.2.6 -> 17.5.1',
Expand All @@ -183,26 +183,22 @@ function createSyncFixture() {
onTestFinished(() => rmSync(root, { recursive: true, force: true }));
const corePath = join(root, 'packages/core/package.json');
const pluginPath = join(root, 'store/tsdown/node_modules/rolldown-plugin-dts/package.json');
const parserPath = join(dirname(pluginPath), 'node_modules/yuku-parser/package.json');
const yukuPath = join(dirname(pluginPath), 'node_modules/yuku/package.json');
const vitePath = join(root, 'vite/packages/vite/package.json');
const metaDir = join(root, 'meta');
const versionsPath = join(metaDir, 'versions.json');
writeJson(corePath, {
dependencies: { 'yuku-parser': '^0.9.3', postcss: '^8.5.6' },
dependencies: { yuku: '^0.9.3', postcss: '^8.5.6' },
optionalDependencies: { fsevents: '~2.3.3' },
devDependencies: { 'rolldown-plugin-dts': '^0.28.0' },
});
writeJson(join(root, 'store/tsdown/package.json'), { name: 'tsdown', version: '0.23.0' });
writeJson(pluginPath, {
dependencies: {
'yuku-ast': '^0.10.1',
'yuku-codegen': '^0.10.1',
'yuku-parser': '~0.10.2',
},
dependencies: { yuku: '~0.10.2' },
});
writeJson(parserPath, { version: '0.10.3' });
// Core's existing parser is not the version used by the bundled plugin.
writeJson(join(root, 'packages/core/node_modules/yuku-parser/package.json'), {
writeJson(yukuPath, { version: '0.10.3' });
// Core's existing Yuku is not the version used by the bundled plugin.
writeJson(join(root, 'packages/core/node_modules/yuku/package.json'), {
version: '0.9.3',
});
// A pnpm-style symlink ensures resolution starts at the real tsdown package.
Expand All @@ -213,7 +209,7 @@ function createSyncFixture() {
'junction',
);
writeJson(join(root, 'packages/core/node_modules/rolldown-plugin-dts/package.json'), {
dependencies: { 'yuku-codegen': '^0.8.1', 'yuku-parser': '^0.8.1' },
dependencies: { yuku: '^0.8.1' },
});
writeJson(vitePath, {
dependencies: { postcss: '^8.5.28' },
Expand All @@ -231,60 +227,58 @@ function createSyncFixture() {
stdio: 'pipe',
},
);
return { root, corePath, pluginPath, parserPath, vitePath, metaDir, versionsPath, run };
return { root, corePath, pluginPath, yukuPath, vitePath, metaDir, versionsPath, run };
}

test('pins the bundled parser, syncs Vite ranges, and preserves upgrade metadata', () => {
test('pins bundled Yuku, syncs Vite ranges, and preserves upgrade metadata', () => {
const fixture = createSyncFixture();
const { corePath, versionsPath, metaDir, run } = fixture;
run();
expect(readJson(corePath)).toEqual({
dependencies: { 'yuku-parser': '0.10.3', postcss: '^8.5.28' },
dependencies: { yuku: '0.10.3', postcss: '^8.5.28' },
optionalDependencies: { fsevents: '~2.4.0' },
devDependencies: { 'rolldown-plugin-dts': '^0.28.0' },
});
expect(readJson(versionsPath)).toEqual({
oxlint: { old: '1.86.0', new: '1.87.0' },
'yuku-parser': { old: '^0.9.3', new: '0.10.3' },
yuku: { old: '^0.9.3', new: '0.10.3' },
postcss: { old: '^8.5.6', new: '^8.5.28' },
fsevents: { old: '~2.3.3', new: '~2.4.0' },
});
expect(readFileSync(join(metaDir, 'pr-body.md'), 'utf8')).toContain(
'| `yuku-parser` | `^0.9.3` | `0.10.3` |',
'| `yuku` | `^0.9.3` | `0.10.3` |',
);

// A second pass after lockfile deduplication must retain the original ranges.
writeJson(fixture.pluginPath, {
dependencies: { 'yuku-codegen': '^0.11.0', 'yuku-parser': '^0.11.0' },
dependencies: { yuku: '^0.11.0' },
});
writeJson(fixture.parserPath, { version: '0.11.2' });
writeJson(fixture.yukuPath, { version: '0.11.2' });
run();
expect(readJson(corePath).dependencies).not.toHaveProperty('yuku-ast');
expect(readJson(corePath).dependencies).not.toHaveProperty('yuku-codegen');
expect(readJson(corePath).dependencies['yuku-parser']).toBe('0.11.2');
expect(readJson(versionsPath)['yuku-parser']).toEqual({ old: '^0.9.3', new: '0.11.2' });
expect(readJson(corePath).dependencies.yuku).toBe('0.11.2');
expect(readJson(versionsPath).yuku).toEqual({ old: '^0.9.3', new: '0.11.2' });
expect(readFileSync(join(metaDir, 'commit-message.txt'), 'utf8')).toContain(
'- yuku-parser: ^0.9.3 -> 0.11.2',
'- yuku: ^0.9.3 -> 0.11.2',
);
const before = readFileSync(corePath, 'utf8');
run();
expect(readFileSync(corePath, 'utf8')).toBe(before);
});

test('updates the parser pin when its installed version changes within the same range', () => {
const { corePath, parserPath, versionsPath, run } = createSyncFixture();
test('updates the Yuku pin when its installed version changes within the same range', () => {
const { corePath, yukuPath, versionsPath, run } = createSyncFixture();
run();
writeJson(parserPath, { version: '0.10.4' });
writeJson(yukuPath, { version: '0.10.4' });
run();
expect(readJson(corePath).dependencies['yuku-parser']).toBe('0.10.4');
expect(readJson(versionsPath)['yuku-parser']).toEqual({ old: '^0.9.3', new: '0.10.4' });
expect(readJson(corePath).dependencies.yuku).toBe('0.10.4');
expect(readJson(versionsPath).yuku).toEqual({ old: '^0.9.3', new: '0.10.4' });
});

test.each([undefined, '', ' '])(
'fails without writing core for an invalid parser version: %s',
'fails without writing core for an invalid Yuku version: %s',
(version) => {
const { corePath, parserPath, versionsPath, run } = createSyncFixture();
writeJson(parserPath, { version });
const { corePath, yukuPath, versionsPath, run } = createSyncFixture();
writeJson(yukuPath, { version });
const before = readFileSync(corePath, 'utf8');
expect(run).toThrow('Missing version');
expect(readFileSync(corePath, 'utf8')).toBe(before);
Expand All @@ -295,7 +289,7 @@ test.each([undefined, '', ' '])(
);

test.each([
{ source: 'pluginPath', field: 'dependencies', name: 'yuku-parser' },
{ source: 'pluginPath', field: 'dependencies', name: 'yuku' },
{ source: 'vitePath', field: 'dependencies', name: 'postcss' },
{ source: 'vitePath', field: 'optionalDependencies', name: 'fsevents' },
] as const)('fails without writing core when upstream removes $name', ({ source, field, name }) => {
Expand Down
14 changes: 7 additions & 7 deletions .github/scripts/upgrade-deps-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function getBundledDependencyRanges(
// These packages stay external in packages/core/build.ts. Follow their
// bundled consumers rather than independently selecting the latest releases.
const sources: { path: string; field: DependencyField; names: string[] }[] = [
{ path: pluginPath, field: 'dependencies', names: ['yuku-parser'] },
{ path: pluginPath, field: 'dependencies', names: ['yuku'] },
{ path: vitePath, field: 'dependencies', names: ['postcss'] },
{ path: vitePath, field: 'optionalDependencies', names: ['fsevents'] },
];
Expand All @@ -28,13 +28,13 @@ export function getBundledDependencyRanges(
if (typeof range !== 'string' || range.trim() === '') {
throw new Error(`Missing ${field}.${name} in ${path}; review core's externals`);
}
if (name === 'yuku-parser') {
// The AST helpers and codegen are bundled. Pin the runtime parser to
// the exact version resolved by the same plugin so it cannot drift.
const parserPath = createRequire(path).resolve('yuku-parser/package.json');
const { version } = JSON.parse(readFileSync(parserPath, 'utf8')) as { version?: unknown };
if (name === 'yuku') {
// Pin the consolidated native runtime to the exact version resolved by
// the bundled plugin so it cannot drift.
const yukuPath = createRequire(path).resolve('yuku/package.json');
const { version } = JSON.parse(readFileSync(yukuPath, 'utf8')) as { version?: unknown };
if (typeof version !== 'string' || version.trim() === '') {
throw new Error(`Missing version in ${parserPath}; review core's externals`);
throw new Error(`Missing version in ${yukuPath}; review core's externals`);
}
range = version;
}
Expand Down
22 changes: 9 additions & 13 deletions packages/core/__tests__/build-artifacts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe('build artifacts', () => {
expect(fs.existsSync(path.resolve(path.dirname(binPath), '../node/cli.js'))).toBe(true);
});

it('bundles JavaScript Yuku packages and keeps the native parser external', async () => {
it('keeps the consolidated native Yuku package external', async () => {
await init();
const tsdownDir = path.join(distDir, 'tsdown');
const imports = fs.readdirSync(tsdownDir).flatMap((file) => {
Expand All @@ -142,28 +142,24 @@ describe('build artifacts', () => {
const [imports] = parse(fs.readFileSync(path.join(tsdownDir, file), 'utf8'));
return imports.map((entry) => entry.specifier);
});
expect(imports).toContain('yuku-parser');
expect(imports).not.toContain('yuku-ast');
expect(imports).not.toContain('yuku-codegen');
expect(imports).toContain('yuku');
expect(imports).toContain('yuku/utils');
expect(imports).not.toContain('yuku-parser');
});

it('pins the external parser to the Yuku versions used by the bundled dts plugin', () => {
it('pins external Yuku to the version used by the bundled dts plugin', () => {
const coreRequire = createRequire(path.join(coreDir, 'package.json'));
const pkg = coreRequire('./package.json');
const tsdownRequire = createRequire(coreRequire.resolve('tsdown/package.json'));
const pluginRequire = createRequire(tsdownRequire.resolve('rolldown-plugin-dts/package.json'));
const parserVersion = pluginRequire('yuku-parser/package.json').version;
const yukuVersion = pluginRequire('yuku/package.json').version;

expect(pkg.dependencies['yuku-parser']).toBe(parserVersion);
expect(coreRequire('yuku-parser/package.json').version).toBe(parserVersion);
for (const name of ['yuku-ast', 'yuku-codegen']) {
expect(pluginRequire(`${name}/package.json`).version, name).toBe(parserVersion);
}
expect(pkg.dependencies.yuku).toBe(yukuVersion);
expect(coreRequire('yuku/package.json').version).toBe(yukuVersion);
});

// The bundled dts plugin resolves `vue-tsc`, `@volar/typescript`,
// `@vue/language-core` and `@typescript/native-preview` from core at runtime,
// so isolated installs such as pnpm's global virtual store need core to
// `@vue/language-core` and `tsrs` from core at runtime, so isolated installs such as pnpm's global virtual store need core to
// declare them.
it('should declare the peers of the bundled rolldown-plugin-dts as optional', () => {
const pkg = JSON.parse(fs.readFileSync(path.join(coreDir, 'package.json'), 'utf8'));
Expand Down
14 changes: 6 additions & 8 deletions packages/core/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,16 +473,15 @@ async function bundleTsdown() {
// in and they cannot be bundled. Both are core `dependencies`, so they resolve
// at runtime and CSS bundling works without users installing anything.
//
// `yuku-parser` (used by `rolldown-plugin-dts`) loads native bindings through
// `yuku-core`. Keep it external and declare it as a core dependency so the
// native loader resolves its platform package at runtime. Pin its version
// to the one resolved by the bundled plugin. `yuku-ast` and
// `yuku-codegen` are pure JavaScript in Yuku 0.19 and are bundled into core.
// `yuku` (used by `rolldown-plugin-dts`) loads native bindings through its
// optional platform packages. Keep it external and declare it as a core
// dependency so the native loader resolves its platform package at runtime.
// Pin its version to the one resolved by the bundled plugin.
const tsdownExternal = [
...Object.keys(pkgJson.peerDependencies).filter((name) => !bundledTsdownPackages.has(name)),
'lightningcss',
'postcss',
'yuku-parser',
'yuku',
];
const isExternal = (id: string) => tsdownExternal.some((e) => id === e || id.startsWith(`${e}/`));

Expand Down Expand Up @@ -755,8 +754,7 @@ async function mergePackageJson() {

// Merge peerDependencies from rolldown-plugin-dts, tsdown and vite. The bundled
// dts plugin still resolves `vue-tsc`, `@volar/typescript`,
// `@vue/language-core` and `@typescript/native-preview` from core at runtime,
// and its declarations import their types, so isolated installs need them
// `@vue/language-core` and `tsrs` from core at runtime, and its declarations import their types, so isolated installs need them
// declared here.
destPkg.peerDependencies = {
...dtsPkg.peerDependencies,
Expand Down
13 changes: 7 additions & 6 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"@oxc-project/types": "catalog:",
"lightningcss": "catalog:",
"postcss": "^8.5.32",
"yuku-parser": "0.19.0"
"yuku": "0.25.0"
},
"devDependencies": {
"@ast-grep/napi": "^0.51.0",
Expand All @@ -141,13 +141,13 @@
"tinyglobby": "^0.2.15",
"tree-kill": "^1.2.2",
"tsdown": "catalog:",
"tsrs": "0.23.0-ts7.1.0-beta",
"typescript": "catalog:",
"vite": "workspace:*"
},
"peerDependencies": {
"@arethetypeswrong/core": "^0.18.1",
"@types/node": "^20.19.0 || >=22.12.0",
"@typescript/native-preview": "*",
"@vitejs/devtools": "^0.7.1 || ^0.8.0",
"@volar/typescript": "~2.4.0",
"@vue/language-core": "~3.2.0 || ~3.3.0",
Expand All @@ -160,6 +160,7 @@
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsrs": "*",
"tsx": "^4.8.1",
"typescript": "^5.0.0 || ^6.0.0 || ^7.0.0",
"unplugin-unused": ">=0.5.0",
Expand All @@ -168,15 +169,15 @@
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@typescript/native-preview": {
"optional": true
},
"@volar/typescript": {
"optional": true
},
"@vue/language-core": {
"optional": true
},
"tsrs": {
"optional": true
},
"typescript": {
"optional": true
},
Expand Down Expand Up @@ -241,6 +242,6 @@
"bundledVersions": {
"vite": "8.3.7",
"rolldown": "1.2.18",
"tsdown": "0.23.4"
"tsdown": "0.23.5"
}
}
Loading
Loading