From 5df3ed11b9117d17783223165543da06bf389ad5 Mon Sep 17 00:00:00 2001 From: jycouet Date: Tue, 28 Apr 2026 22:51:00 +0200 Subject: [PATCH 01/13] prepare for pnpm 11 --- .changeset/pnpm-11-allow-builds.md | 5 ++ documentation/docs/50-api/20-sv-utils.md | 11 ++- package.json | 4 +- packages/sv-utils/api-surface.md | 6 +- packages/sv-utils/src/pnpm.ts | 86 +++++++++++++++++-- packages/sv-utils/src/tests/pnpm.ts | 74 ++++++++++++++++ packages/sv/src/addons/drizzle.ts | 2 +- packages/sv/src/addons/tailwindcss.ts | 2 +- packages/sv/src/cli/add.ts | 4 +- packages/sv/src/cli/create.ts | 4 +- .../pnpm-workspace.yaml | 4 +- packages/sv/src/core/config.ts | 2 +- packages/sv/src/core/engine.ts | 8 +- packages/sv/src/core/package-manager.ts | 4 +- packages/sv/src/testing.ts | 4 +- pnpm-workspace.yaml | 4 +- 16 files changed, 192 insertions(+), 32 deletions(-) create mode 100644 .changeset/pnpm-11-allow-builds.md create mode 100644 packages/sv-utils/src/tests/pnpm.ts diff --git a/.changeset/pnpm-11-allow-builds.md b/.changeset/pnpm-11-allow-builds.md new file mode 100644 index 000000000..a093ee714 --- /dev/null +++ b/.changeset/pnpm-11-allow-builds.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/sv-utils': patch +--- + +prepare for `pnpm@11`: add `pnpm.allowBuilds` helper that auto-detects the installed pnpm version and writes to `allowBuilds` (pnpm 11+) or the legacy `onlyBuiltDependencies` list (pnpm 10). Deprecate `pnpm.onlyBuiltDependencies` diff --git a/documentation/docs/50-api/20-sv-utils.md b/documentation/docs/50-api/20-sv-utils.md index 3e5a543cf..062863068 100644 --- a/documentation/docs/50-api/20-sv-utils.md +++ b/documentation/docs/50-api/20-sv-utils.md @@ -232,15 +232,20 @@ Namespaced helpers for AST manipulation: ## Package manager helpers -### `pnpm.onlyBuiltDependencies` +### `pnpm.allowBuilds` -Returns a transform for `pnpm-workspace.yaml` that adds packages to the `onlyBuiltDependencies` list. Use with `sv.file` when the project uses pnpm. +Returns a transform for `pnpm-workspace.yaml` that adds packages to the pnpm "allow builds" config. Use with `sv.file` when the project uses pnpm. + +The helper detects the installed pnpm version via `pnpm --version`: + +- pnpm `>= 11`: writes to the unified `allowBuilds` map (`{ pkg: true }`), migrating any legacy `onlyBuiltDependencies` list into the map. +- pnpm `< 11`: writes to the legacy `onlyBuiltDependencies` list. ```js // @noErrors import { pnpm } from '@sveltejs/sv-utils'; if (packageManager === 'pnpm') { - sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.onlyBuiltDependencies('my-native-dep')); + sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.allowBuilds('my-native-dep')); } ``` diff --git a/package.json b/package.json index 5ac840964..cc0825324 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "description": "monorepo for sv and friends", "engines": { - "pnpm": "^10.0.0" + "pnpm": "^11.0.0" }, "scripts": { "build": "tsdown", @@ -42,5 +42,5 @@ "typescript-eslint": "^8.58.1", "vitest": "4.1.1" }, - "packageManager": "pnpm@10.17.0" + "packageManager": "pnpm@11.0.0" } diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index 0e105cdda..317b5c1cf 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -717,9 +717,13 @@ declare const transforms: { text(cb: (file: { content: string; text: typeof text_d_exports }) => string | false): TransformFn; }; declare namespace pnpm_d_exports { - export { onlyBuiltDependencies }; + export { allowBuilds, onlyBuiltDependencies }; } +declare function allowBuilds(...packages: string[]): TransformFn; +/** + * @deprecated Use {@link allowBuilds} instead. + */ declare function onlyBuiltDependencies(...packages: string[]): TransformFn; type Version = { major?: number; diff --git a/packages/sv-utils/src/pnpm.ts b/packages/sv-utils/src/pnpm.ts index c7979e8f3..94115ca6c 100644 --- a/packages/sv-utils/src/pnpm.ts +++ b/packages/sv-utils/src/pnpm.ts @@ -1,20 +1,85 @@ +import { execFileSync } from 'node:child_process'; import { transforms, type TransformFn } from './tooling/transforms.ts'; +type YamlMap = { + get(key: string): unknown; + set(key: string, value: unknown): void; + has(key: string): boolean; +}; + +type YamlSeq = { items?: Array<{ value: string } | string> }; + +type YamlDoc = { + get(key: string): unknown; + set(key: string, value: unknown): void; + has(key: string): boolean; + delete(key: string): boolean; + createNode(value: unknown, options?: { flow?: boolean }): unknown; +}; + +function detectPnpmMajor(): number { + try { + const out = execFileSync('pnpm', ['--version'], { + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'ignore'] + }); + const major = Number.parseInt(out.trim().split('.')[0]!, 10); + if (Number.isFinite(major)) return major; + } catch { + // pnpm not on PATH — assume modern + } + return 11; +} + /** - * Returns a TransformFn for `pnpm-workspace.yaml` that adds packages to `onlyBuiltDependencies`. + * Returns a TransformFn for `pnpm-workspace.yaml` that adds packages to the + * pnpm "allow builds" config. + * + * The helper detects the installed pnpm version (via `pnpm --version`) and: + * - on pnpm `>= 11` writes to the unified `allowBuilds` map (`{ pkg: true }`), + * migrating any legacy `onlyBuiltDependencies` list into the map; + * - on pnpm `< 11` writes to the legacy `onlyBuiltDependencies` list. * - * Use with `sv.file`: * ```ts * if (packageManager === 'pnpm') { - * sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.onlyBuiltDependencies('my-native-dep')); + * sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.allowBuilds('my-native-dep')); * } * ``` */ -export function onlyBuiltDependencies(...packages: string[]): TransformFn { +export function allowBuilds(...packages: string[]): TransformFn { + if (detectPnpmMajor() < 11) return writeLegacy(packages); + return writeAllowBuilds(packages); +} + +function writeAllowBuilds(packages: string[]): TransformFn { return transforms.yaml(({ data }) => { - const existing = data.get('onlyBuiltDependencies') as - | { items?: Array<{ value: string } | string> } - | undefined; + const doc = data as unknown as YamlDoc; + + const toMigrate: string[] = []; + const legacy = doc.get('onlyBuiltDependencies') as YamlSeq | undefined; + if (legacy?.items) { + for (const item of legacy.items) { + toMigrate.push(typeof item === 'object' ? item.value : item); + } + } + + let map = doc.get('allowBuilds') as YamlMap | undefined; + if (!map || typeof map.set !== 'function') { + map = doc.createNode({}, { flow: false }) as YamlMap; + doc.set('allowBuilds', map); + } + + for (const pkg of [...toMigrate, ...packages]) { + if (!map.has(pkg)) map.set(pkg, true); + } + + if (legacy) doc.delete('onlyBuiltDependencies'); + }); +} + +function writeLegacy(packages: string[]): TransformFn { + return transforms.yaml(({ data }) => { + const existing = data.get('onlyBuiltDependencies') as YamlSeq | undefined; const items: Array<{ value: string } | string> = existing?.items ?? []; for (const pkg of packages) { if (items.includes(pkg)) continue; @@ -24,3 +89,10 @@ export function onlyBuiltDependencies(...packages: string[]): TransformFn { data.set('onlyBuiltDependencies', items); }); } + +/** + * @deprecated Use {@link allowBuilds} instead. + */ +export function onlyBuiltDependencies(...packages: string[]): TransformFn { + return allowBuilds(...packages); +} diff --git a/packages/sv-utils/src/tests/pnpm.ts b/packages/sv-utils/src/tests/pnpm.ts new file mode 100644 index 000000000..17b42b6b6 --- /dev/null +++ b/packages/sv-utils/src/tests/pnpm.ts @@ -0,0 +1,74 @@ +import { describe, expect, it } from 'vitest'; +import { allowBuilds, onlyBuiltDependencies } from '../pnpm.ts'; + +// We assume the test runner has pnpm 11+ available on PATH (the repo itself is on pnpm 11). +describe('allowBuilds', () => { + it('creates allowBuilds map in empty file', () => { + expect(allowBuilds('esbuild')('')).toBe('allowBuilds:\n esbuild: true\n'); + }); + + it('appends to existing allowBuilds map', () => { + const input = `packages: + - 'packages/*' +allowBuilds: + bar: true +`; + expect(allowBuilds('esbuild')(input)).toBe(`packages: + - 'packages/*' +allowBuilds: + bar: true + esbuild: true +`); + }); + + it('preserves false entries when adding new packages', () => { + const input = `allowBuilds: + core-js: false +`; + expect(allowBuilds('esbuild')(input)).toBe(`allowBuilds: + core-js: false + esbuild: true +`); + }); + + it('migrates legacy onlyBuiltDependencies to allowBuilds', () => { + const input = `packages: + - 'packages/*' +onlyBuiltDependencies: + - foo + - bar +`; + expect(allowBuilds('esbuild')(input)).toBe(`packages: + - 'packages/*' +allowBuilds: + foo: true + bar: true + esbuild: true +`); + }); + + it('merges legacy and existing allowBuilds without duplicating', () => { + const input = `onlyBuiltDependencies: + - shared +allowBuilds: + shared: false +`; + expect(allowBuilds('newone')(input)).toBe(`allowBuilds: + shared: false + newone: true +`); + }); + + it('is idempotent when package already present', () => { + const input = `allowBuilds: + esbuild: true +`; + expect(allowBuilds('esbuild')(input)).toBe(input); + }); +}); + +describe('onlyBuiltDependencies (deprecated)', () => { + it('delegates to allowBuilds', () => { + expect(onlyBuiltDependencies('esbuild')('')).toBe('allowBuilds:\n esbuild: true\n'); + }); +}); diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index 6c1adabbb..de03f8885 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -136,7 +136,7 @@ export default defineAddon({ sv.dependency('better-sqlite3', '^12.8.0'); sv.devDependency('@types/better-sqlite3', '^7.6.13'); if (packageManager === 'pnpm') { - sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.onlyBuiltDependencies('better-sqlite3')); + sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.allowBuilds('better-sqlite3')); } } diff --git a/packages/sv/src/addons/tailwindcss.ts b/packages/sv/src/addons/tailwindcss.ts index 6c9d59993..3545b95bd 100644 --- a/packages/sv/src/addons/tailwindcss.ts +++ b/packages/sv/src/addons/tailwindcss.ts @@ -36,7 +36,7 @@ export default defineAddon({ sv.devDependency('tailwindcss', '^4.2.2'); sv.devDependency('@tailwindcss/vite', '^4.2.2'); if (packageManager === 'pnpm') { - sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.onlyBuiltDependencies('@tailwindcss/oxide')); + sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.allowBuilds('@tailwindcss/oxide')); } if (prettierInstalled) sv.devDependency('prettier-plugin-tailwindcss', '^0.7.2'); diff --git a/packages/sv/src/cli/add.ts b/packages/sv/src/cli/add.ts index 5f967f901..a717ffaf7 100644 --- a/packages/sv/src/cli/add.ts +++ b/packages/sv/src/cli/add.ts @@ -23,7 +23,7 @@ import { downloadPackage, getPackageJSON } from '../core/fetch-packages.ts'; import { formatFiles } from '../core/formatFiles.ts'; import { AGENT_NAMES, - addPnpmOnlyBuiltDependencies, + addPnpmAllowBuilds, installDependencies, installOption, packageManagerPrompt @@ -712,7 +712,7 @@ export async function runAddonsApply({ ? await packageManagerPrompt(options.cwd) : options.install; - addPnpmOnlyBuiltDependencies(workspace.cwd, packageManager, 'esbuild'); + addPnpmAllowBuilds(workspace.cwd, packageManager, 'esbuild'); const argsFormattedAddons: string[] = []; for (const loaded of successfulAddons) { diff --git a/packages/sv/src/cli/create.ts b/packages/sv/src/cli/create.ts index 5f28b9aa5..f2851b506 100644 --- a/packages/sv/src/cli/create.ts +++ b/packages/sv/src/cli/create.ts @@ -10,7 +10,7 @@ import type { LoadedAddon, OptionValues, SetupResult } from '../core/config.ts'; import { formatFiles } from '../core/formatFiles.ts'; import { AGENT_NAMES, - addPnpmOnlyBuiltDependencies, + addPnpmAllowBuilds, detectPackageManager, installDependencies, installOption, @@ -396,7 +396,7 @@ async function createProject(cwd: ProjectPath, options: Options) { } const addOnNextSteps = getNextSteps(addOnSuccessfulAddons, workspace, answers, addonSetupResults); - addPnpmOnlyBuiltDependencies(projectPath, packageManager, 'esbuild'); + addPnpmAllowBuilds(projectPath, packageManager, 'esbuild'); if (packageManager) { await installDependencies(packageManager, projectPath); await formatFiles({ packageManager, cwd: projectPath, filesToFormat: addOnFilesToFormat }); diff --git a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/pnpm-workspace.yaml b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/pnpm-workspace.yaml index fd050a463..14aa49a29 100644 --- a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/pnpm-workspace.yaml +++ b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/pnpm-workspace.yaml @@ -1,2 +1,2 @@ -onlyBuiltDependencies: - - '@tailwindcss/oxide' +allowBuilds: + '@tailwindcss/oxide': true diff --git a/packages/sv/src/core/config.ts b/packages/sv/src/core/config.ts index f1467f09f..0987e1aaf 100644 --- a/packages/sv/src/core/config.ts +++ b/packages/sv/src/core/config.ts @@ -7,7 +7,7 @@ export type { OptionValues } from './options.ts'; export type ConditionDefinition = (Workspace: Workspace) => boolean; export type SvApi = { - /** @deprecated use `pnpm.onlyBuiltDependencies` from `@sveltejs/sv-utils` instead */ + /** @deprecated use `pnpm.allowBuilds` from `@sveltejs/sv-utils` instead */ pnpmBuildDependency: (pkg: string) => void; /** Add a package to the dependencies. */ dependency: (pkg: string, version: string) => void; diff --git a/packages/sv/src/core/engine.ts b/packages/sv/src/core/engine.ts index ecd19cd47..490c244ad 100644 --- a/packages/sv/src/core/engine.ts +++ b/packages/sv/src/core/engine.ts @@ -23,7 +23,7 @@ import { } from './config.ts'; import { svDeprecated } from './deprecated.ts'; import { TESTING } from './env.ts'; -import { addPnpmOnlyBuiltDependencies } from './package-manager.ts'; +import { addPnpmAllowBuilds } from './package-manager.ts'; import { createWorkspace, type Workspace } from './workspace.ts'; function alphabetizeRecord(obj: Record) { @@ -257,12 +257,12 @@ async function runAddon({ addon, loaded, multiple, workspace, workspaceOptions } devDependency: (pkg, version) => { dependencies.push({ pkg, version, dev: true }); }, - /** @deprecated use `pnpm.onlyBuiltDependencies` from `@sveltejs/sv-utils` instead */ + /** @deprecated use `pnpm.allowBuilds` from `@sveltejs/sv-utils` instead */ pnpmBuildDependency: (pkg) => { svDeprecated( - 'use `pnpm.onlyBuiltDependencies` from `@sveltejs/sv-utils` instead of `sv.pnpmBuildDependency`' + 'use `pnpm.allowBuilds` from `@sveltejs/sv-utils` instead of `sv.pnpmBuildDependency`' ); - addPnpmOnlyBuiltDependencies(workspace.cwd, workspace.packageManager, pkg); + addPnpmAllowBuilds(workspace.cwd, workspace.packageManager, pkg); } }; diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index 5154a3ceb..32c7cf557 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -92,7 +92,7 @@ export function getUserAgent(): AgentName | undefined { return AGENTS.includes(name) ? name : undefined; } -export function addPnpmOnlyBuiltDependencies( +export function addPnpmAllowBuilds( cwd: string, packageManager: AgentName | null | undefined, ...packages: string[] @@ -102,6 +102,6 @@ export function addPnpmOnlyBuiltDependencies( const found = find.up('pnpm-workspace.yaml', { cwd }); const filePath = found ?? path.join(cwd, 'pnpm-workspace.yaml'); const content = found ? fs.readFileSync(found, 'utf-8') : ''; - const newContent = pnpm.onlyBuiltDependencies(...packages)(content); + const newContent = pnpm.allowBuilds(...packages)(content); if (newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); } diff --git a/packages/sv/src/testing.ts b/packages/sv/src/testing.ts index 1075f4496..13cca0535 100644 --- a/packages/sv/src/testing.ts +++ b/packages/sv/src/testing.ts @@ -7,7 +7,7 @@ import pstree, { type PS } from 'ps-tree'; import { exec, x } from 'tinyexec'; import type { TestProject } from 'vitest/node'; import { add, type AddonMap, type OptionMap } from './core/engine.ts'; -import { addPnpmOnlyBuiltDependencies } from './core/package-manager.ts'; +import { addPnpmAllowBuilds } from './core/package-manager.ts'; import { create } from './create/index.ts'; export type ProjectVariant = 'kit-js' | 'kit-ts' | 'vite-js' | 'vite-ts'; @@ -343,7 +343,7 @@ export function createSetupTest( options: kind.options, packageManager: 'pnpm' }); - addPnpmOnlyBuiltDependencies(cwd, 'pnpm', 'esbuild'); + addPnpmAllowBuilds(cwd, 'pnpm', 'esbuild'); } execSync('pnpm install', { cwd: path.resolve(cwd, testName), stdio: 'pipe' }); diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4b6a43f87..5fe4ccd5f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,5 +3,5 @@ packages: - '!.test-tmp/**' - '!**/.test-output/**' -onlyBuiltDependencies: - - esbuild +allowBuilds: + esbuild: true From 8ac47bd2970aa206888961b76c6185096b1c2498 Mon Sep 17 00:00:00 2001 From: jycouet Date: Tue, 28 Apr 2026 22:54:11 +0200 Subject: [PATCH 02/13] ci: bump node to 22.13 (pnpm 11 minimum) --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/update-template-repo.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78635b75c..cf99f9bae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22.13 cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm lint @@ -33,7 +33,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22.13 cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm build @@ -50,7 +50,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22.13 cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm exec playwright install chromium @@ -70,7 +70,7 @@ jobs: - name: setup node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22.13 cache: pnpm - name: install dependencies diff --git a/.github/workflows/update-template-repo.yml b/.github/workflows/update-template-repo.yml index d6877bf61..638bf7c58 100644 --- a/.github/workflows/update-template-repo.yml +++ b/.github/workflows/update-template-repo.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22.13 cache: pnpm - name: Install From cbb82df87586ef5346413a0b3ee17d997fe284f2 Mon Sep 17 00:00:00 2001 From: jycouet Date: Tue, 28 Apr 2026 23:14:12 +0200 Subject: [PATCH 03/13] test(cli): pnpm 11 compat - use '*' peer and fix sv-utils path --- packages/sv/src/cli/tests/cli.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index 507f6d0d2..4db13c164 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -142,9 +142,10 @@ describe('cli', () => { // replace sv and sv-utils versions in package.json for tests const packageJsonPath = path.resolve(testOutputPath, 'package.json'); const { data: packageJson } = parse.json(fs.readFileSync(packageJsonPath, 'utf-8')); - packageJson.peerDependencies['sv'] = 'file:../../../..'; + // pnpm 11 rejects `file:` in `peerDependencies`; `*` accepts the workspace-resolved sv + packageJson.peerDependencies['sv'] = '*'; packageJson.devDependencies['sv'] = 'file:../../../..'; - packageJson.devDependencies['@sveltejs/sv-utils'] = 'file:../../../../sv-utils'; + packageJson.devDependencies['@sveltejs/sv-utils'] = 'file:../../../../../sv-utils'; fs.writeFileSync( packageJsonPath, JSON.stringify(packageJson, null, 3).replaceAll(' ', '\t') From 521a7eb810568870ba1b4ca18050bd724a3e9855 Mon Sep 17 00:00:00 2001 From: jycouet Date: Tue, 28 Apr 2026 23:31:30 +0200 Subject: [PATCH 04/13] test(cli): pin .npmrc ignore-workspace so addon test installs locally --- packages/sv/.test-tmp/repro/package.json | 1 + packages/sv/src/cli/tests/cli.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 packages/sv/.test-tmp/repro/package.json diff --git a/packages/sv/.test-tmp/repro/package.json b/packages/sv/.test-tmp/repro/package.json new file mode 100644 index 000000000..6b17dc692 --- /dev/null +++ b/packages/sv/.test-tmp/repro/package.json @@ -0,0 +1 @@ +{"name":"repro-root","private":true,"devDependencies":{"esbuild":"^0.25.0"}} diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index 4db13c164..e997a8328 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -142,7 +142,7 @@ describe('cli', () => { // replace sv and sv-utils versions in package.json for tests const packageJsonPath = path.resolve(testOutputPath, 'package.json'); const { data: packageJson } = parse.json(fs.readFileSync(packageJsonPath, 'utf-8')); - // pnpm 11 rejects `file:` in `peerDependencies`; `*` accepts the workspace-resolved sv + // pnpm 11 rejects `file:` in `peerDependencies`; `*` accepts any version packageJson.peerDependencies['sv'] = '*'; packageJson.devDependencies['sv'] = 'file:../../../..'; packageJson.devDependencies['@sveltejs/sv-utils'] = 'file:../../../../../sv-utils'; @@ -151,6 +151,10 @@ describe('cli', () => { JSON.stringify(packageJson, null, 3).replaceAll(' ', '\t') ); + // the test cwd is excluded from the monorepo's pnpm-workspace.yaml; pin pnpm to + // install/run locally so it doesn't walk up and treat this as a workspace install + fs.writeFileSync(path.resolve(testOutputPath, '.npmrc'), 'ignore-workspace=true\n'); + const cmds = [ // list of cmds to test ['i'], From a85aee804ad37ad3a9c13bc5f255098d6e67f8ee Mon Sep 17 00:00:00 2001 From: jycouet Date: Tue, 28 Apr 2026 23:31:37 +0200 Subject: [PATCH 05/13] chore: remove stray repro file --- packages/sv/.test-tmp/repro/package.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 packages/sv/.test-tmp/repro/package.json diff --git a/packages/sv/.test-tmp/repro/package.json b/packages/sv/.test-tmp/repro/package.json deleted file mode 100644 index 6b17dc692..000000000 --- a/packages/sv/.test-tmp/repro/package.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"repro-root","private":true,"devDependencies":{"esbuild":"^0.25.0"}} From 29ec98cc87a52cb2568baf033f66ea3b3de9b8f3 Mon Sep 17 00:00:00 2001 From: jycouet Date: Tue, 28 Apr 2026 23:43:59 +0200 Subject: [PATCH 06/13] revert: stay on pnpm 10 + node 20 (keep allowBuilds helper) The allowBuilds helper detects pnpm at runtime, so on pnpm 10 it writes the legacy onlyBuiltDependencies list. The actual pnpm 11 bump can come later once test fixtures are pnpm-11-ready. --- .github/workflows/ci.yml | 8 +-- .github/workflows/update-template-repo.yml | 2 +- package.json | 4 +- packages/sv-utils/src/tests/pnpm.ts | 49 +++++++++++++++++-- .../pnpm-workspace.yaml | 4 +- pnpm-workspace.yaml | 4 +- 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf99f9bae..78635b75c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 22.13 + node-version: 20 cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm lint @@ -33,7 +33,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 22.13 + node-version: 20 cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm build @@ -50,7 +50,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 22.13 + node-version: 20 cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm exec playwright install chromium @@ -70,7 +70,7 @@ jobs: - name: setup node.js uses: actions/setup-node@v4 with: - node-version: 22.13 + node-version: 20 cache: pnpm - name: install dependencies diff --git a/.github/workflows/update-template-repo.yml b/.github/workflows/update-template-repo.yml index 638bf7c58..d6877bf61 100644 --- a/.github/workflows/update-template-repo.yml +++ b/.github/workflows/update-template-repo.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 22.13 + node-version: 20 cache: pnpm - name: Install diff --git a/package.json b/package.json index cc0825324..5ac840964 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "description": "monorepo for sv and friends", "engines": { - "pnpm": "^11.0.0" + "pnpm": "^10.0.0" }, "scripts": { "build": "tsdown", @@ -42,5 +42,5 @@ "typescript-eslint": "^8.58.1", "vitest": "4.1.1" }, - "packageManager": "pnpm@11.0.0" + "packageManager": "pnpm@10.17.0" } diff --git a/packages/sv-utils/src/tests/pnpm.ts b/packages/sv-utils/src/tests/pnpm.ts index 17b42b6b6..148ad8fe5 100644 --- a/packages/sv-utils/src/tests/pnpm.ts +++ b/packages/sv-utils/src/tests/pnpm.ts @@ -1,8 +1,22 @@ +import { execFileSync } from 'node:child_process'; import { describe, expect, it } from 'vitest'; import { allowBuilds, onlyBuiltDependencies } from '../pnpm.ts'; -// We assume the test runner has pnpm 11+ available on PATH (the repo itself is on pnpm 11). -describe('allowBuilds', () => { +const pnpmMajor = (() => { + try { + const out = execFileSync('pnpm', ['--version'], { + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'ignore'] + }); + return Number.parseInt(out.trim().split('.')[0]!, 10); + } catch { + return 11; + } +})(); + +const isPnpm11 = pnpmMajor >= 11; + +describe.runIf(isPnpm11)('allowBuilds (pnpm >= 11: writes allowBuilds map)', () => { it('creates allowBuilds map in empty file', () => { expect(allowBuilds('esbuild')('')).toBe('allowBuilds:\n esbuild: true\n'); }); @@ -65,10 +79,35 @@ allowBuilds: `; expect(allowBuilds('esbuild')(input)).toBe(input); }); -}); -describe('onlyBuiltDependencies (deprecated)', () => { - it('delegates to allowBuilds', () => { + it('deprecated onlyBuiltDependencies delegates to allowBuilds', () => { expect(onlyBuiltDependencies('esbuild')('')).toBe('allowBuilds:\n esbuild: true\n'); }); }); + +describe.runIf(!isPnpm11)('allowBuilds (pnpm < 11: writes onlyBuiltDependencies list)', () => { + it('creates onlyBuiltDependencies list in empty file', () => { + expect(allowBuilds('esbuild')('')).toBe('onlyBuiltDependencies:\n - esbuild\n'); + }); + + it('appends to existing onlyBuiltDependencies list', () => { + const input = `onlyBuiltDependencies: + - foo +`; + expect(allowBuilds('esbuild')(input)).toBe(`onlyBuiltDependencies: + - foo + - esbuild +`); + }); + + it('is idempotent on legacy list', () => { + const input = `onlyBuiltDependencies: + - esbuild +`; + expect(allowBuilds('esbuild')(input)).toBe(input); + }); + + it('deprecated onlyBuiltDependencies delegates to allowBuilds', () => { + expect(onlyBuiltDependencies('esbuild')('')).toBe('onlyBuiltDependencies:\n - esbuild\n'); + }); +}); diff --git a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/pnpm-workspace.yaml b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/pnpm-workspace.yaml index 14aa49a29..fd050a463 100644 --- a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/pnpm-workspace.yaml +++ b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/pnpm-workspace.yaml @@ -1,2 +1,2 @@ -allowBuilds: - '@tailwindcss/oxide': true +onlyBuiltDependencies: + - '@tailwindcss/oxide' diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5fe4ccd5f..4b6a43f87 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,5 +3,5 @@ packages: - '!.test-tmp/**' - '!**/.test-output/**' -allowBuilds: - esbuild: true +onlyBuiltDependencies: + - esbuild From a23d00591211f7cf2df48fd0c9e3ea98410ae135 Mon Sep 17 00:00:00 2001 From: jycouet Date: Wed, 29 Apr 2026 10:06:10 +0200 Subject: [PATCH 07/13] test(cli): use npm + COREPACK_ENABLE_STRICT=0 for addon test cmds Switching addon test cmds back to npm (with corepack strict mode off) sidesteps pnpm walking up to the monorepo workspace, so the .npmrc ignore-workspace pin is no longer needed. Reverted peerDependencies['sv'] to file: since pnpm 10 accepts it. Also reword changeset to "handle" pnpm@11 since the repo itself is staying on pnpm 10. --- .changeset/pnpm-11-allow-builds.md | 2 +- packages/sv/src/cli/tests/cli.ts | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.changeset/pnpm-11-allow-builds.md b/.changeset/pnpm-11-allow-builds.md index a093ee714..2113b959b 100644 --- a/.changeset/pnpm-11-allow-builds.md +++ b/.changeset/pnpm-11-allow-builds.md @@ -2,4 +2,4 @@ '@sveltejs/sv-utils': patch --- -prepare for `pnpm@11`: add `pnpm.allowBuilds` helper that auto-detects the installed pnpm version and writes to `allowBuilds` (pnpm 11+) or the legacy `onlyBuiltDependencies` list (pnpm 10). Deprecate `pnpm.onlyBuiltDependencies` +handle `pnpm@11`: add `pnpm.allowBuilds` helper that auto-detects the installed pnpm version and writes to `allowBuilds` (pnpm 11+) or the legacy `onlyBuiltDependencies` list (pnpm 10). Deprecate `pnpm.onlyBuiltDependencies` diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index e997a8328..ccf9988fc 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -142,8 +142,7 @@ describe('cli', () => { // replace sv and sv-utils versions in package.json for tests const packageJsonPath = path.resolve(testOutputPath, 'package.json'); const { data: packageJson } = parse.json(fs.readFileSync(packageJsonPath, 'utf-8')); - // pnpm 11 rejects `file:` in `peerDependencies`; `*` accepts any version - packageJson.peerDependencies['sv'] = '*'; + packageJson.peerDependencies['sv'] = 'file:../../../..'; packageJson.devDependencies['sv'] = 'file:../../../..'; packageJson.devDependencies['@sveltejs/sv-utils'] = 'file:../../../../../sv-utils'; fs.writeFileSync( @@ -151,10 +150,6 @@ describe('cli', () => { JSON.stringify(packageJson, null, 3).replaceAll(' ', '\t') ); - // the test cwd is excluded from the monorepo's pnpm-workspace.yaml; pin pnpm to - // install/run locally so it doesn't walk up and treat this as a workspace install - fs.writeFileSync(path.resolve(testOutputPath, '.npmrc'), 'ignore-workspace=true\n'); - const cmds = [ // list of cmds to test ['i'], @@ -163,8 +158,16 @@ describe('cli', () => { ['run', 'test'] ]; for (const cmd of cmds) { - const res = await exec('pnpm', cmd, { - nodeOptions: { stdio: 'pipe', cwd: testOutputPath } + const res = await exec('npm', cmd, { + nodeOptions: { + stdio: 'pipe', + cwd: testOutputPath, + env: { + ...process.env, + // allow npm under a repo whose packageManager is pnpm + COREPACK_ENABLE_STRICT: '0' + } + } }); expect( res.exitCode, From 71069cf5aa83d4a0f97703e6d3ca85ba7a49afa5 Mon Sep 17 00:00:00 2001 From: jycouet Date: Wed, 29 Apr 2026 11:05:32 +0200 Subject: [PATCH 08/13] fix(sv-utils): detect pnpm major via execSync so Windows resolves .cmd execFileSync('pnpm', ...) doesn't resolve `pnpm.cmd` on Windows without shell:true, so detectPnpmMajor() always threw and fell back to 11. On a pnpm 10 host this caused allowBuilds to write the unrecognized allowBuilds map, the build script for better-sqlite3 was never approved, and the drizzle addon test failed to load native bindings on Windows. Switch to execSync to match the rest of the codebase. --- packages/sv-utils/src/pnpm.ts | 4 ++-- packages/sv-utils/src/tests/pnpm.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sv-utils/src/pnpm.ts b/packages/sv-utils/src/pnpm.ts index 94115ca6c..394099ad2 100644 --- a/packages/sv-utils/src/pnpm.ts +++ b/packages/sv-utils/src/pnpm.ts @@ -1,4 +1,4 @@ -import { execFileSync } from 'node:child_process'; +import { execSync } from 'node:child_process'; import { transforms, type TransformFn } from './tooling/transforms.ts'; type YamlMap = { @@ -19,7 +19,7 @@ type YamlDoc = { function detectPnpmMajor(): number { try { - const out = execFileSync('pnpm', ['--version'], { + const out = execSync('pnpm --version', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }); diff --git a/packages/sv-utils/src/tests/pnpm.ts b/packages/sv-utils/src/tests/pnpm.ts index 148ad8fe5..7024a9982 100644 --- a/packages/sv-utils/src/tests/pnpm.ts +++ b/packages/sv-utils/src/tests/pnpm.ts @@ -1,10 +1,10 @@ -import { execFileSync } from 'node:child_process'; +import { execSync } from 'node:child_process'; import { describe, expect, it } from 'vitest'; import { allowBuilds, onlyBuiltDependencies } from '../pnpm.ts'; const pnpmMajor = (() => { try { - const out = execFileSync('pnpm', ['--version'], { + const out = execSync('pnpm --version', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }); From d1c1d4802a8244022966c688f023e5dc4182827a Mon Sep 17 00:00:00 2001 From: jycouet Date: Wed, 29 Apr 2026 11:14:17 +0200 Subject: [PATCH 09/13] refactor(sv-utils): reuse coerceVersion + share detectPnpmMajor with tests - detectPnpmMajor now uses coerceVersion instead of a hand-rolled split - tests import detectPnpmMajor from pnpm.ts instead of duplicating the exec + parse logic - index.ts exports the pnpm namespace as a named object so internal helpers like detectPnpmMajor stay out of the public API surface --- packages/sv-utils/src/index.ts | 6 +++++- packages/sv-utils/src/pnpm.ts | 7 ++++--- packages/sv-utils/src/tests/pnpm.ts | 17 ++--------------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/packages/sv-utils/src/index.ts b/packages/sv-utils/src/index.ts index be4b18b60..c814e0ef1 100644 --- a/packages/sv-utils/src/index.ts +++ b/packages/sv-utils/src/index.ts @@ -1,3 +1,4 @@ +import { allowBuilds, onlyBuiltDependencies } from './pnpm.ts'; import { parseCss, parseHtml, @@ -32,7 +33,10 @@ export * as json from './tooling/json.ts'; export * as svelte from './tooling/svelte/index.ts'; // Package manager helpers -export * as pnpm from './pnpm.ts'; +export const pnpm = { + allowBuilds, + onlyBuiltDependencies +}; // Transforms — sv-utils = what to do to content, sv = where and when to do it. export { transforms } from './tooling/transforms.ts'; diff --git a/packages/sv-utils/src/pnpm.ts b/packages/sv-utils/src/pnpm.ts index 394099ad2..b7a23f07f 100644 --- a/packages/sv-utils/src/pnpm.ts +++ b/packages/sv-utils/src/pnpm.ts @@ -1,4 +1,5 @@ import { execSync } from 'node:child_process'; +import { coerceVersion } from './semver.ts'; import { transforms, type TransformFn } from './tooling/transforms.ts'; type YamlMap = { @@ -17,14 +18,14 @@ type YamlDoc = { createNode(value: unknown, options?: { flow?: boolean }): unknown; }; -function detectPnpmMajor(): number { +export function detectPnpmMajor(): number { try { const out = execSync('pnpm --version', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }); - const major = Number.parseInt(out.trim().split('.')[0]!, 10); - if (Number.isFinite(major)) return major; + const { major } = coerceVersion(out.trim()); + if (major !== undefined) return major; } catch { // pnpm not on PATH — assume modern } diff --git a/packages/sv-utils/src/tests/pnpm.ts b/packages/sv-utils/src/tests/pnpm.ts index 7024a9982..6bacce268 100644 --- a/packages/sv-utils/src/tests/pnpm.ts +++ b/packages/sv-utils/src/tests/pnpm.ts @@ -1,20 +1,7 @@ -import { execSync } from 'node:child_process'; import { describe, expect, it } from 'vitest'; -import { allowBuilds, onlyBuiltDependencies } from '../pnpm.ts'; +import { allowBuilds, detectPnpmMajor, onlyBuiltDependencies } from '../pnpm.ts'; -const pnpmMajor = (() => { - try { - const out = execSync('pnpm --version', { - encoding: 'utf-8', - stdio: ['ignore', 'pipe', 'ignore'] - }); - return Number.parseInt(out.trim().split('.')[0]!, 10); - } catch { - return 11; - } -})(); - -const isPnpm11 = pnpmMajor >= 11; +const isPnpm11 = detectPnpmMajor() >= 11; describe.runIf(isPnpm11)('allowBuilds (pnpm >= 11: writes allowBuilds map)', () => { it('creates allowBuilds map in empty file', () => { From eb76837f195e6917034c2f2e33af5d2686e59eb8 Mon Sep 17 00:00:00 2001 From: jycouet Date: Wed, 29 Apr 2026 11:16:18 +0200 Subject: [PATCH 10/13] fix(sv-utils): annotate pnpm namespace properties for isolatedDeclarations --- packages/sv-utils/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sv-utils/src/index.ts b/packages/sv-utils/src/index.ts index c814e0ef1..d26ea004b 100644 --- a/packages/sv-utils/src/index.ts +++ b/packages/sv-utils/src/index.ts @@ -34,8 +34,8 @@ export * as svelte from './tooling/svelte/index.ts'; // Package manager helpers export const pnpm = { - allowBuilds, - onlyBuiltDependencies + allowBuilds: allowBuilds as typeof allowBuilds, + onlyBuiltDependencies: onlyBuiltDependencies as typeof onlyBuiltDependencies }; // Transforms — sv-utils = what to do to content, sv = where and when to do it. From b2d310367dcfc38126816b4993406f69d8d2a6e5 Mon Sep 17 00:00:00 2001 From: jycouet Date: Wed, 29 Apr 2026 11:20:01 +0200 Subject: [PATCH 11/13] chore(sv-utils): regenerate api-surface for named pnpm namespace --- packages/sv-utils/api-surface.md | 235 ++++++++++++++++--------------- 1 file changed, 118 insertions(+), 117 deletions(-) diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index 317b5c1cf..0cc9c5f8e 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -162,84 +162,6 @@ declare class Comments { ): void; remove(predicate: (comment: estree.Comment) => boolean | undefined | null): void; } - -type YamlDocument = { - get(key: string): unknown; - set(key: string, value: unknown): void; -}; -type ParseBase = { - source: string; - - generateCode(): string; -}; -declare function parseScript(source: string): { - ast: estree.Program; - comments: Comments; -} & ParseBase; -declare function parseCss(source: string): { - ast: Omit; -} & ParseBase; -declare function parseHtml(source: string): { - ast: SvelteAst.Fragment; -} & ParseBase; -declare function parseJson(source: string): { - data: any; -} & ParseBase; -declare function parseYaml(source: string): { - data: YamlDocument; -} & ParseBase; -declare function parseSvelte(source: string): { - ast: SvelteAst.Root; -} & ParseBase; -declare function parseToml(source: string): { - data: TomlTable; -} & ParseBase; -interface DedentOptions { - alignValues?: boolean; - escapeSpecialCharacters?: boolean; - trimWhitespace?: boolean; -} -interface Dedent { - (literals: string): string; - (strings: TemplateStringsArray, ...values: unknown[]): string; - withOptions: CreateDedent; -} -type CreateDedent = (options: DedentOptions) => Dedent; -declare const dedent: Dedent; -declare module 'zimmerframe' { - export function walk< - T extends { - type: string; - }, - U extends Record | null - >(node: T, state: U, visitors: Visitors): T; - type BaseNode = { - type: string; - }; - type NodeOf = X extends { - type: T; - } - ? X - : never; - type SpecialisedVisitors = { - [K in T['type']]?: Visitor, U, T>; - }; - export type Visitor = (node: T, context: Context) => V | void; - export type Visitors = T['type'] extends '_' - ? never - : SpecialisedVisitors & { - _?: Visitor; - }; - export interface Context { - next: (state?: U) => T | void; - path: T[]; - state: U; - stop: () => void; - visit: (node: T, state?: U) => T; - } - export {}; -} //# sourceMappingURL=index.d.ts.map -declare function resolveCommandArray(agent: Agent, command: Command, args: string[]): string[]; declare namespace index_d_exports$1 { export { addAtRule, addDeclaration, addImports, addRule }; } @@ -270,6 +192,23 @@ declare function addAtRule( append: boolean; } ): SvelteAst.CSS.Atrule; +declare namespace index_d_exports$2 { + export { addAttribute, addFromRawHtml, appendElement, createElement, insertElement }; +} +declare function createElement( + tagName: string, + attributes?: Record +): SvelteAst.RegularElement; +declare function addAttribute(element: SvelteAst.RegularElement, name: string, value: string): void; +declare function insertElement( + fragment: SvelteAst.Fragment, + elementToInsert: SvelteAst.Fragment['nodes'][0] +): void; +declare function appendElement( + fragment: SvelteAst.Fragment, + elementToAppend: SvelteAst.Fragment['nodes'][0] +): void; +declare function addFromRawHtml(fragment: SvelteAst.Fragment, html: string): void; declare namespace array_d_exports { export { append, create$1 as create, prepend }; } @@ -561,41 +500,6 @@ declare namespace index_d_exports$3 { vite_d_exports as vite }; } -declare namespace index_d_exports$2 { - export { addAttribute, addFromRawHtml, appendElement, createElement, insertElement }; -} -declare function createElement( - tagName: string, - attributes?: Record -): SvelteAst.RegularElement; -declare function addAttribute(element: SvelteAst.RegularElement, name: string, value: string): void; -declare function insertElement( - fragment: SvelteAst.Fragment, - elementToInsert: SvelteAst.Fragment['nodes'][0] -): void; -declare function appendElement( - fragment: SvelteAst.Fragment, - elementToAppend: SvelteAst.Fragment['nodes'][0] -): void; -declare function addFromRawHtml(fragment: SvelteAst.Fragment, html: string): void; -declare namespace text_d_exports { - export { upsert }; -} -type CommentEntry = { - text: string; - mode: 'append' | 'prepend'; -}; -type CommentOption = string | Array; - -declare function upsert( - content: string, - key: string, - options?: { - value?: string; - comment?: CommentOption; - separator?: boolean; - } -): string; declare namespace json_d_exports { export { arrayUpsert, packageScriptsUpsert }; } @@ -615,6 +519,38 @@ declare function packageScriptsUpsert( mode?: 'append' | 'prepend'; } ): void; + +type YamlDocument = { + get(key: string): unknown; + set(key: string, value: unknown): void; +}; +type ParseBase = { + source: string; + + generateCode(): string; +}; +declare function parseScript(source: string): { + ast: estree.Program; + comments: Comments; +} & ParseBase; +declare function parseCss(source: string): { + ast: Omit; +} & ParseBase; +declare function parseHtml(source: string): { + ast: SvelteAst.Fragment; +} & ParseBase; +declare function parseJson(source: string): { + data: any; +} & ParseBase; +declare function parseYaml(source: string): { + data: YamlDocument; +} & ParseBase; +declare function parseSvelte(source: string): { + ast: SvelteAst.Root; +} & ParseBase; +declare function parseToml(source: string): { + data: TomlTable; +} & ParseBase; declare namespace index_d_exports$4 { export { RootWithInstance, addFragment, addSlot, ensureScript }; } @@ -642,6 +578,24 @@ declare function addFragment( language?: 'ts' | 'js'; } ): void; +declare namespace text_d_exports { + export { upsert }; +} +type CommentEntry = { + text: string; + mode: 'append' | 'prepend'; +}; +type CommentOption = string | Array; + +declare function upsert( + content: string, + key: string, + options?: { + value?: string; + comment?: CommentOption; + separator?: boolean; + } +): string; type TransformFn = (content: string) => string; type TransformOptions = { onError?: (error: unknown) => void; @@ -716,15 +670,58 @@ declare const transforms: { text(cb: (file: { content: string; text: typeof text_d_exports }) => string | false): TransformFn; }; -declare namespace pnpm_d_exports { - export { allowBuilds, onlyBuiltDependencies }; -} declare function allowBuilds(...packages: string[]): TransformFn; /** * @deprecated Use {@link allowBuilds} instead. */ declare function onlyBuiltDependencies(...packages: string[]): TransformFn; +interface DedentOptions { + alignValues?: boolean; + escapeSpecialCharacters?: boolean; + trimWhitespace?: boolean; +} +interface Dedent { + (literals: string): string; + (strings: TemplateStringsArray, ...values: unknown[]): string; + withOptions: CreateDedent; +} +type CreateDedent = (options: DedentOptions) => Dedent; +declare const dedent: Dedent; +declare module 'zimmerframe' { + export function walk< + T extends { + type: string; + }, + U extends Record | null + >(node: T, state: U, visitors: Visitors): T; + type BaseNode = { + type: string; + }; + type NodeOf = X extends { + type: T; + } + ? X + : never; + type SpecialisedVisitors = { + [K in T['type']]?: Visitor, U, T>; + }; + export type Visitor = (node: T, context: Context) => V | void; + export type Visitors = T['type'] extends '_' + ? never + : SpecialisedVisitors & { + _?: Visitor; + }; + export interface Context { + next: (state?: U) => T | void; + path: T[]; + state: U; + stop: () => void; + visit: (node: T, state?: U) => T; + } + export {}; +} //# sourceMappingURL=index.d.ts.map +declare function resolveCommandArray(agent: Agent, command: Command, args: string[]): string[]; type Version = { major?: number; minor?: number; @@ -787,6 +784,10 @@ declare const color: { error: (str: ColorInput) => string; hidden: (str: ColorInput) => string; }; +declare const pnpm: { + allowBuilds: typeof allowBuilds; + onlyBuiltDependencies: typeof onlyBuiltDependencies; +}; declare const parse: { css: typeof parseCss; @@ -825,7 +826,7 @@ export { loadPackageJson, minVersion, parse, - pnpm_d_exports as pnpm, + pnpm, resolveCommand, resolveCommandArray, sanitizeName, From 100786893fdd161bd430a0814f5bc4ff61ada8e6 Mon Sep 17 00:00:00 2001 From: jycouet Date: Wed, 29 Apr 2026 11:55:05 +0200 Subject: [PATCH 12/13] refactor(sv-utils): flat allowBuilds/onlyBuiltDependencies exports Drops the `pnpm.*` namespace in favor of named exports at the package root. Consumers (drizzle, tailwindcss, package-manager) now import `allowBuilds` directly. detectPnpmMajor returns `undefined` instead of defaulting to 11 when pnpm is not on PATH so callers can decide. --- .changeset/pnpm-11-allow-builds.md | 2 +- packages/sv-utils/api-surface.md | 233 ++++++++++++------------ packages/sv-utils/src/index.ts | 6 +- packages/sv-utils/src/pnpm.ts | 13 +- packages/sv-utils/src/tests/pnpm.ts | 3 +- packages/sv/src/addons/drizzle.ts | 4 +- packages/sv/src/addons/tailwindcss.ts | 4 +- packages/sv/src/cli/tests/cli.ts | 2 + packages/sv/src/core/config.ts | 2 +- packages/sv/src/core/engine.ts | 4 +- packages/sv/src/core/package-manager.ts | 6 +- 11 files changed, 137 insertions(+), 142 deletions(-) diff --git a/.changeset/pnpm-11-allow-builds.md b/.changeset/pnpm-11-allow-builds.md index 2113b959b..885ff2e2e 100644 --- a/.changeset/pnpm-11-allow-builds.md +++ b/.changeset/pnpm-11-allow-builds.md @@ -2,4 +2,4 @@ '@sveltejs/sv-utils': patch --- -handle `pnpm@11`: add `pnpm.allowBuilds` helper that auto-detects the installed pnpm version and writes to `allowBuilds` (pnpm 11+) or the legacy `onlyBuiltDependencies` list (pnpm 10). Deprecate `pnpm.onlyBuiltDependencies` +handle `pnpm@11`: add `allowBuilds` helper that auto-detects the installed pnpm version and writes to `allowBuilds` (pnpm 11+) or the legacy `onlyBuiltDependencies` list (pnpm 10). Deprecate `onlyBuiltDependencies` diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index 0cc9c5f8e..a02b667d3 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -162,6 +162,84 @@ declare class Comments { ): void; remove(predicate: (comment: estree.Comment) => boolean | undefined | null): void; } + +type YamlDocument = { + get(key: string): unknown; + set(key: string, value: unknown): void; +}; +type ParseBase = { + source: string; + + generateCode(): string; +}; +declare function parseScript(source: string): { + ast: estree.Program; + comments: Comments; +} & ParseBase; +declare function parseCss(source: string): { + ast: Omit; +} & ParseBase; +declare function parseHtml(source: string): { + ast: SvelteAst.Fragment; +} & ParseBase; +declare function parseJson(source: string): { + data: any; +} & ParseBase; +declare function parseYaml(source: string): { + data: YamlDocument; +} & ParseBase; +declare function parseSvelte(source: string): { + ast: SvelteAst.Root; +} & ParseBase; +declare function parseToml(source: string): { + data: TomlTable; +} & ParseBase; +interface DedentOptions { + alignValues?: boolean; + escapeSpecialCharacters?: boolean; + trimWhitespace?: boolean; +} +interface Dedent { + (literals: string): string; + (strings: TemplateStringsArray, ...values: unknown[]): string; + withOptions: CreateDedent; +} +type CreateDedent = (options: DedentOptions) => Dedent; +declare const dedent: Dedent; +declare module 'zimmerframe' { + export function walk< + T extends { + type: string; + }, + U extends Record | null + >(node: T, state: U, visitors: Visitors): T; + type BaseNode = { + type: string; + }; + type NodeOf = X extends { + type: T; + } + ? X + : never; + type SpecialisedVisitors = { + [K in T['type']]?: Visitor, U, T>; + }; + export type Visitor = (node: T, context: Context) => V | void; + export type Visitors = T['type'] extends '_' + ? never + : SpecialisedVisitors & { + _?: Visitor; + }; + export interface Context { + next: (state?: U) => T | void; + path: T[]; + state: U; + stop: () => void; + visit: (node: T, state?: U) => T; + } + export {}; +} //# sourceMappingURL=index.d.ts.map +declare function resolveCommandArray(agent: Agent, command: Command, args: string[]): string[]; declare namespace index_d_exports$1 { export { addAtRule, addDeclaration, addImports, addRule }; } @@ -192,23 +270,6 @@ declare function addAtRule( append: boolean; } ): SvelteAst.CSS.Atrule; -declare namespace index_d_exports$2 { - export { addAttribute, addFromRawHtml, appendElement, createElement, insertElement }; -} -declare function createElement( - tagName: string, - attributes?: Record -): SvelteAst.RegularElement; -declare function addAttribute(element: SvelteAst.RegularElement, name: string, value: string): void; -declare function insertElement( - fragment: SvelteAst.Fragment, - elementToInsert: SvelteAst.Fragment['nodes'][0] -): void; -declare function appendElement( - fragment: SvelteAst.Fragment, - elementToAppend: SvelteAst.Fragment['nodes'][0] -): void; -declare function addFromRawHtml(fragment: SvelteAst.Fragment, html: string): void; declare namespace array_d_exports { export { append, create$1 as create, prepend }; } @@ -500,6 +561,41 @@ declare namespace index_d_exports$3 { vite_d_exports as vite }; } +declare namespace index_d_exports$2 { + export { addAttribute, addFromRawHtml, appendElement, createElement, insertElement }; +} +declare function createElement( + tagName: string, + attributes?: Record +): SvelteAst.RegularElement; +declare function addAttribute(element: SvelteAst.RegularElement, name: string, value: string): void; +declare function insertElement( + fragment: SvelteAst.Fragment, + elementToInsert: SvelteAst.Fragment['nodes'][0] +): void; +declare function appendElement( + fragment: SvelteAst.Fragment, + elementToAppend: SvelteAst.Fragment['nodes'][0] +): void; +declare function addFromRawHtml(fragment: SvelteAst.Fragment, html: string): void; +declare namespace text_d_exports { + export { upsert }; +} +type CommentEntry = { + text: string; + mode: 'append' | 'prepend'; +}; +type CommentOption = string | Array; + +declare function upsert( + content: string, + key: string, + options?: { + value?: string; + comment?: CommentOption; + separator?: boolean; + } +): string; declare namespace json_d_exports { export { arrayUpsert, packageScriptsUpsert }; } @@ -519,38 +615,6 @@ declare function packageScriptsUpsert( mode?: 'append' | 'prepend'; } ): void; - -type YamlDocument = { - get(key: string): unknown; - set(key: string, value: unknown): void; -}; -type ParseBase = { - source: string; - - generateCode(): string; -}; -declare function parseScript(source: string): { - ast: estree.Program; - comments: Comments; -} & ParseBase; -declare function parseCss(source: string): { - ast: Omit; -} & ParseBase; -declare function parseHtml(source: string): { - ast: SvelteAst.Fragment; -} & ParseBase; -declare function parseJson(source: string): { - data: any; -} & ParseBase; -declare function parseYaml(source: string): { - data: YamlDocument; -} & ParseBase; -declare function parseSvelte(source: string): { - ast: SvelteAst.Root; -} & ParseBase; -declare function parseToml(source: string): { - data: TomlTable; -} & ParseBase; declare namespace index_d_exports$4 { export { RootWithInstance, addFragment, addSlot, ensureScript }; } @@ -578,24 +642,6 @@ declare function addFragment( language?: 'ts' | 'js'; } ): void; -declare namespace text_d_exports { - export { upsert }; -} -type CommentEntry = { - text: string; - mode: 'append' | 'prepend'; -}; -type CommentOption = string | Array; - -declare function upsert( - content: string, - key: string, - options?: { - value?: string; - comment?: CommentOption; - separator?: boolean; - } -): string; type TransformFn = (content: string) => string; type TransformOptions = { onError?: (error: unknown) => void; @@ -676,52 +722,6 @@ declare function allowBuilds(...packages: string[]): TransformFn; * @deprecated Use {@link allowBuilds} instead. */ declare function onlyBuiltDependencies(...packages: string[]): TransformFn; -interface DedentOptions { - alignValues?: boolean; - escapeSpecialCharacters?: boolean; - trimWhitespace?: boolean; -} -interface Dedent { - (literals: string): string; - (strings: TemplateStringsArray, ...values: unknown[]): string; - withOptions: CreateDedent; -} -type CreateDedent = (options: DedentOptions) => Dedent; -declare const dedent: Dedent; -declare module 'zimmerframe' { - export function walk< - T extends { - type: string; - }, - U extends Record | null - >(node: T, state: U, visitors: Visitors): T; - type BaseNode = { - type: string; - }; - type NodeOf = X extends { - type: T; - } - ? X - : never; - type SpecialisedVisitors = { - [K in T['type']]?: Visitor, U, T>; - }; - export type Visitor = (node: T, context: Context) => V | void; - export type Visitors = T['type'] extends '_' - ? never - : SpecialisedVisitors & { - _?: Visitor; - }; - export interface Context { - next: (state?: U) => T | void; - path: T[]; - state: U; - stop: () => void; - visit: (node: T, state?: U) => T; - } - export {}; -} //# sourceMappingURL=index.d.ts.map -declare function resolveCommandArray(agent: Agent, command: Command, args: string[]): string[]; type Version = { major?: number; minor?: number; @@ -784,10 +784,6 @@ declare const color: { error: (str: ColorInput) => string; hidden: (str: ColorInput) => string; }; -declare const pnpm: { - allowBuilds: typeof allowBuilds; - onlyBuiltDependencies: typeof onlyBuiltDependencies; -}; declare const parse: { css: typeof parseCss; @@ -809,6 +805,7 @@ export { type TransformFn, index_d_exports as Walker, type YamlDocument, + allowBuilds, coerceVersion, color, constructCommand, @@ -825,8 +822,8 @@ export { loadFile, loadPackageJson, minVersion, + onlyBuiltDependencies, parse, - pnpm, resolveCommand, resolveCommandArray, sanitizeName, diff --git a/packages/sv-utils/src/index.ts b/packages/sv-utils/src/index.ts index d26ea004b..83d95d5cf 100644 --- a/packages/sv-utils/src/index.ts +++ b/packages/sv-utils/src/index.ts @@ -1,4 +1,3 @@ -import { allowBuilds, onlyBuiltDependencies } from './pnpm.ts'; import { parseCss, parseHtml, @@ -33,10 +32,7 @@ export * as json from './tooling/json.ts'; export * as svelte from './tooling/svelte/index.ts'; // Package manager helpers -export const pnpm = { - allowBuilds: allowBuilds as typeof allowBuilds, - onlyBuiltDependencies: onlyBuiltDependencies as typeof onlyBuiltDependencies -}; +export { allowBuilds, onlyBuiltDependencies } from './pnpm.ts'; // Transforms — sv-utils = what to do to content, sv = where and when to do it. export { transforms } from './tooling/transforms.ts'; diff --git a/packages/sv-utils/src/pnpm.ts b/packages/sv-utils/src/pnpm.ts index b7a23f07f..83f0e9716 100644 --- a/packages/sv-utils/src/pnpm.ts +++ b/packages/sv-utils/src/pnpm.ts @@ -18,18 +18,16 @@ type YamlDoc = { createNode(value: unknown, options?: { flow?: boolean }): unknown; }; -export function detectPnpmMajor(): number { +export function detectPnpmMajor(): number | undefined { try { const out = execSync('pnpm --version', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }); - const { major } = coerceVersion(out.trim()); - if (major !== undefined) return major; + return coerceVersion(out.trim()).major; } catch { - // pnpm not on PATH — assume modern + return undefined; } - return 11; } /** @@ -43,12 +41,13 @@ export function detectPnpmMajor(): number { * * ```ts * if (packageManager === 'pnpm') { - * sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.allowBuilds('my-native-dep')); + * sv.file(file.findUp('pnpm-workspace.yaml'), allowBuilds('my-native-dep')); * } * ``` */ export function allowBuilds(...packages: string[]): TransformFn { - if (detectPnpmMajor() < 11) return writeLegacy(packages); + const major = detectPnpmMajor(); + if (major !== undefined && major < 11) return writeLegacy(packages); return writeAllowBuilds(packages); } diff --git a/packages/sv-utils/src/tests/pnpm.ts b/packages/sv-utils/src/tests/pnpm.ts index 6bacce268..2b1a95aee 100644 --- a/packages/sv-utils/src/tests/pnpm.ts +++ b/packages/sv-utils/src/tests/pnpm.ts @@ -1,7 +1,8 @@ import { describe, expect, it } from 'vitest'; import { allowBuilds, detectPnpmMajor, onlyBuiltDependencies } from '../pnpm.ts'; -const isPnpm11 = detectPnpmMajor() >= 11; +const major = detectPnpmMajor(); +const isPnpm11 = major === undefined || major >= 11; describe.runIf(isPnpm11)('allowBuilds (pnpm >= 11: writes allowBuilds map)', () => { it('creates allowBuilds map in empty file', () => { diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index de03f8885..8b8b82e84 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -1,9 +1,9 @@ import { + allowBuilds, color, dedent, type TransformFn, transforms, - pnpm, resolveCommandArray, fileExists, createPrinter @@ -136,7 +136,7 @@ export default defineAddon({ sv.dependency('better-sqlite3', '^12.8.0'); sv.devDependency('@types/better-sqlite3', '^7.6.13'); if (packageManager === 'pnpm') { - sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.allowBuilds('better-sqlite3')); + sv.file(file.findUp('pnpm-workspace.yaml'), allowBuilds('better-sqlite3')); } } diff --git a/packages/sv/src/addons/tailwindcss.ts b/packages/sv/src/addons/tailwindcss.ts index 3545b95bd..59ebdb3b2 100644 --- a/packages/sv/src/addons/tailwindcss.ts +++ b/packages/sv/src/addons/tailwindcss.ts @@ -1,4 +1,4 @@ -import { pnpm, transforms } from '@sveltejs/sv-utils'; +import { allowBuilds, transforms } from '@sveltejs/sv-utils'; import { defineAddon, defineAddonOptions } from '../core/config.ts'; const plugins = [ @@ -36,7 +36,7 @@ export default defineAddon({ sv.devDependency('tailwindcss', '^4.2.2'); sv.devDependency('@tailwindcss/vite', '^4.2.2'); if (packageManager === 'pnpm') { - sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.allowBuilds('@tailwindcss/oxide')); + sv.file(file.findUp('pnpm-workspace.yaml'), allowBuilds('@tailwindcss/oxide')); } if (prettierInstalled) sv.devDependency('prettier-plugin-tailwindcss', '^0.7.2'); diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index ccf9988fc..1a671bb8b 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -158,6 +158,8 @@ describe('cli', () => { ['run', 'test'] ]; for (const cmd of cmds) { + // use npm here so the install doesn't walk up into the monorepo's + // pnpm workspace and try to resolve packages from there const res = await exec('npm', cmd, { nodeOptions: { stdio: 'pipe', diff --git a/packages/sv/src/core/config.ts b/packages/sv/src/core/config.ts index 0987e1aaf..e9965ee2a 100644 --- a/packages/sv/src/core/config.ts +++ b/packages/sv/src/core/config.ts @@ -7,7 +7,7 @@ export type { OptionValues } from './options.ts'; export type ConditionDefinition = (Workspace: Workspace) => boolean; export type SvApi = { - /** @deprecated use `pnpm.allowBuilds` from `@sveltejs/sv-utils` instead */ + /** @deprecated use `allowBuilds` from `@sveltejs/sv-utils` instead */ pnpmBuildDependency: (pkg: string) => void; /** Add a package to the dependencies. */ dependency: (pkg: string, version: string) => void; diff --git a/packages/sv/src/core/engine.ts b/packages/sv/src/core/engine.ts index 490c244ad..3c975d141 100644 --- a/packages/sv/src/core/engine.ts +++ b/packages/sv/src/core/engine.ts @@ -257,10 +257,10 @@ async function runAddon({ addon, loaded, multiple, workspace, workspaceOptions } devDependency: (pkg, version) => { dependencies.push({ pkg, version, dev: true }); }, - /** @deprecated use `pnpm.allowBuilds` from `@sveltejs/sv-utils` instead */ + /** @deprecated use `allowBuilds` from `@sveltejs/sv-utils` instead */ pnpmBuildDependency: (pkg) => { svDeprecated( - 'use `pnpm.allowBuilds` from `@sveltejs/sv-utils` instead of `sv.pnpmBuildDependency`' + 'use `allowBuilds` from `@sveltejs/sv-utils` instead of `sv.pnpmBuildDependency`' ); addPnpmAllowBuilds(workspace.cwd, workspace.packageManager, pkg); } diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index 32c7cf557..6e2e89558 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -3,10 +3,10 @@ import { AGENTS, type AgentName, COMMANDS, + allowBuilds, color, constructCommand, - detect, - pnpm + detect } from '@sveltejs/sv-utils'; import { Option } from 'commander'; import * as find from 'empathic/find'; @@ -102,6 +102,6 @@ export function addPnpmAllowBuilds( const found = find.up('pnpm-workspace.yaml', { cwd }); const filePath = found ?? path.join(cwd, 'pnpm-workspace.yaml'); const content = found ? fs.readFileSync(found, 'utf-8') : ''; - const newContent = pnpm.allowBuilds(...packages)(content); + const newContent = allowBuilds(...packages)(content); if (newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); } From 5b74f7e0c03d5c0006873b6acd40014c2a27bba4 Mon Sep 17 00:00:00 2001 From: jycouet Date: Wed, 29 Apr 2026 12:30:42 +0200 Subject: [PATCH 13/13] refactor(sv-utils): keep pnpm namespace, hide detectPnpmMajor in pnpm-internals Move detectPnpmMajor into a separate pnpm-internals.ts file that isn't re-exported from index.ts. Restores the `export * as pnpm` namespace and the `pnpm.allowBuilds` consumer call sites while keeping the version detection helper out of the public API surface. --- .changeset/pnpm-11-allow-builds.md | 2 +- packages/sv-utils/api-surface.md | 6 ++++-- packages/sv-utils/src/index.ts | 2 +- packages/sv-utils/src/pnpm-internals.ts | 14 ++++++++++++++ packages/sv-utils/src/pnpm.ts | 17 ++--------------- packages/sv-utils/src/tests/pnpm.ts | 3 ++- packages/sv/src/addons/drizzle.ts | 4 ++-- packages/sv/src/addons/tailwindcss.ts | 4 ++-- packages/sv/src/core/config.ts | 2 +- packages/sv/src/core/engine.ts | 4 ++-- packages/sv/src/core/package-manager.ts | 6 +++--- 11 files changed, 34 insertions(+), 30 deletions(-) create mode 100644 packages/sv-utils/src/pnpm-internals.ts diff --git a/.changeset/pnpm-11-allow-builds.md b/.changeset/pnpm-11-allow-builds.md index 885ff2e2e..2113b959b 100644 --- a/.changeset/pnpm-11-allow-builds.md +++ b/.changeset/pnpm-11-allow-builds.md @@ -2,4 +2,4 @@ '@sveltejs/sv-utils': patch --- -handle `pnpm@11`: add `allowBuilds` helper that auto-detects the installed pnpm version and writes to `allowBuilds` (pnpm 11+) or the legacy `onlyBuiltDependencies` list (pnpm 10). Deprecate `onlyBuiltDependencies` +handle `pnpm@11`: add `pnpm.allowBuilds` helper that auto-detects the installed pnpm version and writes to `allowBuilds` (pnpm 11+) or the legacy `onlyBuiltDependencies` list (pnpm 10). Deprecate `pnpm.onlyBuiltDependencies` diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index a02b667d3..317b5c1cf 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -716,6 +716,9 @@ declare const transforms: { text(cb: (file: { content: string; text: typeof text_d_exports }) => string | false): TransformFn; }; +declare namespace pnpm_d_exports { + export { allowBuilds, onlyBuiltDependencies }; +} declare function allowBuilds(...packages: string[]): TransformFn; /** @@ -805,7 +808,6 @@ export { type TransformFn, index_d_exports as Walker, type YamlDocument, - allowBuilds, coerceVersion, color, constructCommand, @@ -822,8 +824,8 @@ export { loadFile, loadPackageJson, minVersion, - onlyBuiltDependencies, parse, + pnpm_d_exports as pnpm, resolveCommand, resolveCommandArray, sanitizeName, diff --git a/packages/sv-utils/src/index.ts b/packages/sv-utils/src/index.ts index 83d95d5cf..be4b18b60 100644 --- a/packages/sv-utils/src/index.ts +++ b/packages/sv-utils/src/index.ts @@ -32,7 +32,7 @@ export * as json from './tooling/json.ts'; export * as svelte from './tooling/svelte/index.ts'; // Package manager helpers -export { allowBuilds, onlyBuiltDependencies } from './pnpm.ts'; +export * as pnpm from './pnpm.ts'; // Transforms — sv-utils = what to do to content, sv = where and when to do it. export { transforms } from './tooling/transforms.ts'; diff --git a/packages/sv-utils/src/pnpm-internals.ts b/packages/sv-utils/src/pnpm-internals.ts new file mode 100644 index 000000000..a36f3697c --- /dev/null +++ b/packages/sv-utils/src/pnpm-internals.ts @@ -0,0 +1,14 @@ +import { execSync } from 'node:child_process'; +import { coerceVersion } from './semver.ts'; + +export function detectPnpmMajor(): number | undefined { + try { + const out = execSync('pnpm --version', { + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'ignore'] + }); + return coerceVersion(out.trim()).major; + } catch { + return undefined; + } +} diff --git a/packages/sv-utils/src/pnpm.ts b/packages/sv-utils/src/pnpm.ts index 83f0e9716..def7c636b 100644 --- a/packages/sv-utils/src/pnpm.ts +++ b/packages/sv-utils/src/pnpm.ts @@ -1,5 +1,4 @@ -import { execSync } from 'node:child_process'; -import { coerceVersion } from './semver.ts'; +import { detectPnpmMajor } from './pnpm-internals.ts'; import { transforms, type TransformFn } from './tooling/transforms.ts'; type YamlMap = { @@ -18,18 +17,6 @@ type YamlDoc = { createNode(value: unknown, options?: { flow?: boolean }): unknown; }; -export function detectPnpmMajor(): number | undefined { - try { - const out = execSync('pnpm --version', { - encoding: 'utf-8', - stdio: ['ignore', 'pipe', 'ignore'] - }); - return coerceVersion(out.trim()).major; - } catch { - return undefined; - } -} - /** * Returns a TransformFn for `pnpm-workspace.yaml` that adds packages to the * pnpm "allow builds" config. @@ -41,7 +28,7 @@ export function detectPnpmMajor(): number | undefined { * * ```ts * if (packageManager === 'pnpm') { - * sv.file(file.findUp('pnpm-workspace.yaml'), allowBuilds('my-native-dep')); + * sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.allowBuilds('my-native-dep')); * } * ``` */ diff --git a/packages/sv-utils/src/tests/pnpm.ts b/packages/sv-utils/src/tests/pnpm.ts index 2b1a95aee..7b803a164 100644 --- a/packages/sv-utils/src/tests/pnpm.ts +++ b/packages/sv-utils/src/tests/pnpm.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { allowBuilds, detectPnpmMajor, onlyBuiltDependencies } from '../pnpm.ts'; +import { detectPnpmMajor } from '../pnpm-internals.ts'; +import { allowBuilds, onlyBuiltDependencies } from '../pnpm.ts'; const major = detectPnpmMajor(); const isPnpm11 = major === undefined || major >= 11; diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index 8b8b82e84..de03f8885 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -1,9 +1,9 @@ import { - allowBuilds, color, dedent, type TransformFn, transforms, + pnpm, resolveCommandArray, fileExists, createPrinter @@ -136,7 +136,7 @@ export default defineAddon({ sv.dependency('better-sqlite3', '^12.8.0'); sv.devDependency('@types/better-sqlite3', '^7.6.13'); if (packageManager === 'pnpm') { - sv.file(file.findUp('pnpm-workspace.yaml'), allowBuilds('better-sqlite3')); + sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.allowBuilds('better-sqlite3')); } } diff --git a/packages/sv/src/addons/tailwindcss.ts b/packages/sv/src/addons/tailwindcss.ts index 59ebdb3b2..3545b95bd 100644 --- a/packages/sv/src/addons/tailwindcss.ts +++ b/packages/sv/src/addons/tailwindcss.ts @@ -1,4 +1,4 @@ -import { allowBuilds, transforms } from '@sveltejs/sv-utils'; +import { pnpm, transforms } from '@sveltejs/sv-utils'; import { defineAddon, defineAddonOptions } from '../core/config.ts'; const plugins = [ @@ -36,7 +36,7 @@ export default defineAddon({ sv.devDependency('tailwindcss', '^4.2.2'); sv.devDependency('@tailwindcss/vite', '^4.2.2'); if (packageManager === 'pnpm') { - sv.file(file.findUp('pnpm-workspace.yaml'), allowBuilds('@tailwindcss/oxide')); + sv.file(file.findUp('pnpm-workspace.yaml'), pnpm.allowBuilds('@tailwindcss/oxide')); } if (prettierInstalled) sv.devDependency('prettier-plugin-tailwindcss', '^0.7.2'); diff --git a/packages/sv/src/core/config.ts b/packages/sv/src/core/config.ts index e9965ee2a..0987e1aaf 100644 --- a/packages/sv/src/core/config.ts +++ b/packages/sv/src/core/config.ts @@ -7,7 +7,7 @@ export type { OptionValues } from './options.ts'; export type ConditionDefinition = (Workspace: Workspace) => boolean; export type SvApi = { - /** @deprecated use `allowBuilds` from `@sveltejs/sv-utils` instead */ + /** @deprecated use `pnpm.allowBuilds` from `@sveltejs/sv-utils` instead */ pnpmBuildDependency: (pkg: string) => void; /** Add a package to the dependencies. */ dependency: (pkg: string, version: string) => void; diff --git a/packages/sv/src/core/engine.ts b/packages/sv/src/core/engine.ts index 3c975d141..490c244ad 100644 --- a/packages/sv/src/core/engine.ts +++ b/packages/sv/src/core/engine.ts @@ -257,10 +257,10 @@ async function runAddon({ addon, loaded, multiple, workspace, workspaceOptions } devDependency: (pkg, version) => { dependencies.push({ pkg, version, dev: true }); }, - /** @deprecated use `allowBuilds` from `@sveltejs/sv-utils` instead */ + /** @deprecated use `pnpm.allowBuilds` from `@sveltejs/sv-utils` instead */ pnpmBuildDependency: (pkg) => { svDeprecated( - 'use `allowBuilds` from `@sveltejs/sv-utils` instead of `sv.pnpmBuildDependency`' + 'use `pnpm.allowBuilds` from `@sveltejs/sv-utils` instead of `sv.pnpmBuildDependency`' ); addPnpmAllowBuilds(workspace.cwd, workspace.packageManager, pkg); } diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index 6e2e89558..32c7cf557 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -3,10 +3,10 @@ import { AGENTS, type AgentName, COMMANDS, - allowBuilds, color, constructCommand, - detect + detect, + pnpm } from '@sveltejs/sv-utils'; import { Option } from 'commander'; import * as find from 'empathic/find'; @@ -102,6 +102,6 @@ export function addPnpmAllowBuilds( const found = find.up('pnpm-workspace.yaml', { cwd }); const filePath = found ?? path.join(cwd, 'pnpm-workspace.yaml'); const content = found ? fs.readFileSync(found, 'utf-8') : ''; - const newContent = allowBuilds(...packages)(content); + const newContent = pnpm.allowBuilds(...packages)(content); if (newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); }