From 083e23c79ad94a1b8d7d5a62e70d8d428f15ce07 Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Tue, 29 Sep 2026 14:59:31 +0200 Subject: [PATCH 01/20] fix typo in existing tests ref #1341 --- packages/sv-utils/src/tests/transforms.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sv-utils/src/tests/transforms.ts b/packages/sv-utils/src/tests/transforms.ts index ad0620f7a..ebaadfe19 100644 --- a/packages/sv-utils/src/tests/transforms.ts +++ b/packages/sv-utils/src/tests/transforms.ts @@ -165,7 +165,7 @@ describe('transforms', () => { expect(result).toContain('name: new'); }); - it('abort: returns fals if transform is cacelled', () => { + it('abort: returns false if transform is cancelled', () => { const input = 'name: old\n'; expect(transforms.yaml(() => false)(input)).toBe(false); }); @@ -180,7 +180,7 @@ describe('transforms', () => { expect(result).toContain('name = "new"'); }); - it('abort: returns fals if transform is cacelled', () => { + it('abort: returns false if transform is cancelled', () => { const input = 'name = "old"\n'; expect(transforms.toml(() => false)(input)).toBe(false); }); From e9c836c1c56ac08525ff167b875ce623eef0b799 Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Tue, 29 Sep 2026 15:05:40 +0200 Subject: [PATCH 02/20] add pnpm internal for `engineStrict` ref #1341 --- packages/sv-utils/src/pnpm-internals.ts | 10 ++++++++++ packages/sv-utils/src/tests/pnpm.ts | 23 ++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/sv-utils/src/pnpm-internals.ts b/packages/sv-utils/src/pnpm-internals.ts index 63fdc9848..2723e7bf1 100644 --- a/packages/sv-utils/src/pnpm-internals.ts +++ b/packages/sv-utils/src/pnpm-internals.ts @@ -87,3 +87,13 @@ export function writeLegacy(packages: string[]): TransformFn { data.set('onlyBuiltDependencies', items); }); } + +export function writeEngineStrict(): TransformFn { + return transforms.yaml(({ data }) => { + const existing = data.get('engineStrict'); + // if not set, set to true + if (typeof existing === 'undefined') { + data.set('engineStrict', true); + } + }); +} diff --git a/packages/sv-utils/src/tests/pnpm.ts b/packages/sv-utils/src/tests/pnpm.ts index 7296805a7..6955b5888 100644 --- a/packages/sv-utils/src/tests/pnpm.ts +++ b/packages/sv-utils/src/tests/pnpm.ts @@ -1,7 +1,12 @@ import fs from 'node:fs'; import path from 'node:path'; import { describe, expect, it } from 'vitest'; -import { detectPnpmMajor, writeAllowBuilds, writeLegacy } from '../pnpm-internals.ts'; +import { + detectPnpmMajor, + writeAllowBuilds, + writeEngineStrict, + writeLegacy +} from '../pnpm-internals.ts'; import { allowBuilds } from '../pnpm.ts'; import { coerceVersion } from '../semver.ts'; @@ -121,3 +126,19 @@ describe('allowBuilds version detection', () => { } ); }); + +describe('writes engineStrict', () => { + const transform = () => writeEngineStrict(); + + it('adds `engineStrict` in empty file', () => { + expect(transform()('')).toBe('engineStrict: true\n'); + }); + + it.each([{ value: true }, { value: false }])( + 'does not overwrite when already set to $value', + ({ value }) => { + const input = `engineStrict: ${value}\n`; + expect(transform()(input)).toBe(input); + } + ); +}); From a2de7a6c9739b5bcfcfbf100412fdf3e1cafdfb0 Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Tue, 29 Sep 2026 15:43:54 +0200 Subject: [PATCH 03/20] wrap pnpm internal in pnpm and package-manager ref #1341 - followed the same approach as `allowBuilds` --- packages/sv-utils/api-surface.md | 3 ++- packages/sv-utils/src/pnpm.ts | 15 ++++++++++++++- packages/sv/src/cli/create.ts | 2 ++ packages/sv/src/core/package-manager.ts | 19 +++++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index fdcff4f1a..f12bc0fb3 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -1030,9 +1030,10 @@ export declare function resolveCommandArray( args: string[] ): string[]; declare namespace pnpm_d_exports { - export { allowBuilds }; + export { allowBuilds, engineStrict }; } declare function allowBuilds(options: { cwd: string; packages: string[] }): TransformFn; +declare function engineStrict(): TransformFn; export declare function commandExists(command: string): boolean; type Package = { name: string; diff --git a/packages/sv-utils/src/pnpm.ts b/packages/sv-utils/src/pnpm.ts index 62a276d9b..2677df100 100644 --- a/packages/sv-utils/src/pnpm.ts +++ b/packages/sv-utils/src/pnpm.ts @@ -1,4 +1,9 @@ -import { detectPnpmMajor, writeAllowBuilds, writeLegacy } from './pnpm-internals.ts'; +import { + detectPnpmMajor, + writeAllowBuilds, + writeEngineStrict, + writeLegacy +} from './pnpm-internals.ts'; import { type TransformFn } from './tooling/transforms.ts'; /** @@ -26,3 +31,11 @@ export function allowBuilds(options: { if (major !== undefined && major < 11) return writeLegacy(packages); return writeAllowBuilds(packages); } + +/** + * Returns a TransformFn for `pnpm-workspace.yaml` that adds `engineStrict` config + * if not defined already + */ +export function engineStrict(): TransformFn { + return writeEngineStrict(); +} diff --git a/packages/sv/src/cli/create.ts b/packages/sv/src/cli/create.ts index 4c666336a..ac84fca67 100644 --- a/packages/sv/src/cli/create.ts +++ b/packages/sv/src/cli/create.ts @@ -17,6 +17,7 @@ import { formatFiles } from '../core/formatFiles.ts'; import { AGENT_NAMES, addAllowBuildsIfPnpm, + addEngineStrictIfPnpm, detectPackageManager, installDependencies, installOption, @@ -416,6 +417,7 @@ export async function createProject(cwd: ProjectPath, options: Options) { const addOnNextSteps = getNextSteps(addOnSuccessfulAddons, workspace, answers, addonSetupResults); + addEngineStrictIfPnpm({ cwd: projectPath, packageManager }); addAllowBuildsIfPnpm({ cwd: projectPath, packageManager, packages: ['esbuild'] }); let depsInstalled = false; diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index 6c81cdafe..a494f44ca 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -146,3 +146,22 @@ export function addAllowBuildsIfPnpm(options: { const newContent = pnpm.allowBuilds({ cwd, packages })(content); if (newContent && newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); } + +/** + * `pnpm.engineStrict` only transforms content. Add-ons get the read/write for free through + * `sv.file`, but the CLI itself runs outside that pipeline, so it locates (or creates) + * `pnpm-workspace.yaml` by hand. + */ +export function addEngineStrictIfPnpm(options: { + cwd: string; + packageManager: AgentName | null | undefined; +}): void { + const { cwd, packageManager } = options; + if (packageManager !== 'pnpm') return; + + 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.engineStrict()(content); + if (newContent && newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); +} From fb969dcf8e484b10f0e58f8cf6071be066391dda Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Thu, 1 Oct 2026 15:01:24 +0200 Subject: [PATCH 04/20] add changeset ref #1341 --- .changeset/early-kiwis-drum.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/early-kiwis-drum.md diff --git a/.changeset/early-kiwis-drum.md b/.changeset/early-kiwis-drum.md new file mode 100644 index 000000000..90c6c4e0d --- /dev/null +++ b/.changeset/early-kiwis-drum.md @@ -0,0 +1,7 @@ +--- +"@sveltejs/sv-utils": patch +"sv": patch +--- + +Adding `engineStrict` to `pnpm-workspace.yaml` file when package manager is `pnpm` + \ No newline at end of file From 7cd919fd1946b5602e60b4e4c2aecba021cff3d7 Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Fri, 2 Oct 2026 14:24:02 +0200 Subject: [PATCH 05/20] ref(tech): follow code convention for `undefined` ref #1341 --- packages/sv-utils/src/pnpm-internals.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sv-utils/src/pnpm-internals.ts b/packages/sv-utils/src/pnpm-internals.ts index 2723e7bf1..2428859e9 100644 --- a/packages/sv-utils/src/pnpm-internals.ts +++ b/packages/sv-utils/src/pnpm-internals.ts @@ -92,7 +92,7 @@ export function writeEngineStrict(): TransformFn { return transforms.yaml(({ data }) => { const existing = data.get('engineStrict'); // if not set, set to true - if (typeof existing === 'undefined') { + if (existing === undefined) { data.set('engineStrict', true); } }); From dcca4e9427fc6690589185659fbc6e6e58baae83 Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Mon, 5 Oct 2026 10:07:09 +0200 Subject: [PATCH 06/20] write engineStrict to pnpm-workspace.yaml internally ref #1341 --- packages/sv-utils/api-surface.md | 3 +-- packages/sv-utils/src/pnpm-internals.ts | 10 ---------- packages/sv-utils/src/pnpm.ts | 15 +-------------- packages/sv-utils/src/tests/pnpm.ts | 23 +---------------------- packages/sv/src/core/package-manager.ts | 23 ++++++++++++++++++----- 5 files changed, 21 insertions(+), 53 deletions(-) diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index f12bc0fb3..fdcff4f1a 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -1030,10 +1030,9 @@ export declare function resolveCommandArray( args: string[] ): string[]; declare namespace pnpm_d_exports { - export { allowBuilds, engineStrict }; + export { allowBuilds }; } declare function allowBuilds(options: { cwd: string; packages: string[] }): TransformFn; -declare function engineStrict(): TransformFn; export declare function commandExists(command: string): boolean; type Package = { name: string; diff --git a/packages/sv-utils/src/pnpm-internals.ts b/packages/sv-utils/src/pnpm-internals.ts index 2428859e9..63fdc9848 100644 --- a/packages/sv-utils/src/pnpm-internals.ts +++ b/packages/sv-utils/src/pnpm-internals.ts @@ -87,13 +87,3 @@ export function writeLegacy(packages: string[]): TransformFn { data.set('onlyBuiltDependencies', items); }); } - -export function writeEngineStrict(): TransformFn { - return transforms.yaml(({ data }) => { - const existing = data.get('engineStrict'); - // if not set, set to true - if (existing === undefined) { - data.set('engineStrict', true); - } - }); -} diff --git a/packages/sv-utils/src/pnpm.ts b/packages/sv-utils/src/pnpm.ts index 2677df100..62a276d9b 100644 --- a/packages/sv-utils/src/pnpm.ts +++ b/packages/sv-utils/src/pnpm.ts @@ -1,9 +1,4 @@ -import { - detectPnpmMajor, - writeAllowBuilds, - writeEngineStrict, - writeLegacy -} from './pnpm-internals.ts'; +import { detectPnpmMajor, writeAllowBuilds, writeLegacy } from './pnpm-internals.ts'; import { type TransformFn } from './tooling/transforms.ts'; /** @@ -31,11 +26,3 @@ export function allowBuilds(options: { if (major !== undefined && major < 11) return writeLegacy(packages); return writeAllowBuilds(packages); } - -/** - * Returns a TransformFn for `pnpm-workspace.yaml` that adds `engineStrict` config - * if not defined already - */ -export function engineStrict(): TransformFn { - return writeEngineStrict(); -} diff --git a/packages/sv-utils/src/tests/pnpm.ts b/packages/sv-utils/src/tests/pnpm.ts index 6955b5888..7296805a7 100644 --- a/packages/sv-utils/src/tests/pnpm.ts +++ b/packages/sv-utils/src/tests/pnpm.ts @@ -1,12 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { describe, expect, it } from 'vitest'; -import { - detectPnpmMajor, - writeAllowBuilds, - writeEngineStrict, - writeLegacy -} from '../pnpm-internals.ts'; +import { detectPnpmMajor, writeAllowBuilds, writeLegacy } from '../pnpm-internals.ts'; import { allowBuilds } from '../pnpm.ts'; import { coerceVersion } from '../semver.ts'; @@ -126,19 +121,3 @@ describe('allowBuilds version detection', () => { } ); }); - -describe('writes engineStrict', () => { - const transform = () => writeEngineStrict(); - - it('adds `engineStrict` in empty file', () => { - expect(transform()('')).toBe('engineStrict: true\n'); - }); - - it.each([{ value: true }, { value: false }])( - 'does not overwrite when already set to $value', - ({ value }) => { - const input = `engineStrict: ${value}\n`; - expect(transform()(input)).toBe(input); - } - ); -}); diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index a494f44ca..d3488a29f 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -9,7 +9,9 @@ import { color, detect, pnpm, - resolveCommand + resolveCommand, + transforms, + type TransformFn } from '@sveltejs/sv-utils'; import { Option } from 'commander'; import * as find from 'empathic/find'; @@ -148,9 +150,8 @@ export function addAllowBuildsIfPnpm(options: { } /** - * `pnpm.engineStrict` only transforms content. Add-ons get the read/write for free through - * `sv.file`, but the CLI itself runs outside that pipeline, so it locates (or creates) - * `pnpm-workspace.yaml` by hand. + * adds `engineStrict` to `pnpm-workspace.yaml` if not set. + * creates the file if it does not exist. */ export function addEngineStrictIfPnpm(options: { cwd: string; @@ -162,6 +163,18 @@ export function addEngineStrictIfPnpm(options: { 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.engineStrict()(content); + + function writeEngineStrict(): TransformFn { + return transforms.yaml(({ data }) => { + const existing = data.get('engineStrict'); + // if not set, set to true + if (existing === undefined) { + data.set('engineStrict', true); + } + }); + } + + const newContent = writeEngineStrict()(content); + if (newContent && newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); } From 1c3007c5b108564643e5d35848abb19c2e0b5c90 Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Mon, 5 Oct 2026 10:33:51 +0200 Subject: [PATCH 07/20] Apply batched suggestions from code review Co-authored-by: Scott Wu --- .changeset/early-kiwis-drum.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.changeset/early-kiwis-drum.md b/.changeset/early-kiwis-drum.md index 90c6c4e0d..e5e85af48 100644 --- a/.changeset/early-kiwis-drum.md +++ b/.changeset/early-kiwis-drum.md @@ -1,7 +1,6 @@ --- -"@sveltejs/sv-utils": patch "sv": patch --- -Adding `engineStrict` to `pnpm-workspace.yaml` file when package manager is `pnpm` +fix(sv): templates set `engineStrict` in `pnpm-workspace.yaml` when package manager is `pnpm` \ No newline at end of file From 0c4add067c6917439dae8452018cd27e18d1795f Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Wed, 7 Oct 2026 09:53:21 +0200 Subject: [PATCH 08/20] wrap engine strict for pnpm in a more generic handler ref #1341 --- packages/sv/src/cli/create.ts | 4 ++-- packages/sv/src/core/package-manager.ts | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/sv/src/cli/create.ts b/packages/sv/src/cli/create.ts index ac84fca67..b7b6b0841 100644 --- a/packages/sv/src/cli/create.ts +++ b/packages/sv/src/cli/create.ts @@ -17,7 +17,7 @@ import { formatFiles } from '../core/formatFiles.ts'; import { AGENT_NAMES, addAllowBuildsIfPnpm, - addEngineStrictIfPnpm, + addEngineStrict, detectPackageManager, installDependencies, installOption, @@ -417,7 +417,7 @@ export async function createProject(cwd: ProjectPath, options: Options) { const addOnNextSteps = getNextSteps(addOnSuccessfulAddons, workspace, answers, addonSetupResults); - addEngineStrictIfPnpm({ cwd: projectPath, packageManager }); + addEngineStrict({ cwd: projectPath, packageManager }); addAllowBuildsIfPnpm({ cwd: projectPath, packageManager, packages: ['esbuild'] }); let depsInstalled = false; diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index d3488a29f..273ca0662 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -149,11 +149,26 @@ export function addAllowBuildsIfPnpm(options: { if (newContent && newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); } +/** + * add engine strict configuration according to selected package manager + * + * handled package managers are: pnpm + */ +export function addEngineStrict(options: { + cwd: string; + packageManager: AgentName | null | undefined; +}) { + const { packageManager } = options; + if (packageManager === 'pnpm') { + addEngineStrictForPnpm(options); + } +} + /** * adds `engineStrict` to `pnpm-workspace.yaml` if not set. * creates the file if it does not exist. */ -export function addEngineStrictIfPnpm(options: { +export function addEngineStrictForPnpm(options: { cwd: string; packageManager: AgentName | null | undefined; }): void { From fda61acdaeed251c15758f55e83ee6b98379e8a1 Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Wed, 7 Oct 2026 09:58:04 +0200 Subject: [PATCH 09/20] remove `.npmrc` files from templates --- packages/sv/src/create/templates/demo/.npmrc | 1 - packages/sv/src/create/templates/library/.npmrc | 1 - packages/sv/src/create/templates/minimal/.npmrc | 1 - 3 files changed, 3 deletions(-) delete mode 100644 packages/sv/src/create/templates/demo/.npmrc delete mode 100644 packages/sv/src/create/templates/library/.npmrc delete mode 100644 packages/sv/src/create/templates/minimal/.npmrc diff --git a/packages/sv/src/create/templates/demo/.npmrc b/packages/sv/src/create/templates/demo/.npmrc deleted file mode 100644 index b6f27f135..000000000 --- a/packages/sv/src/create/templates/demo/.npmrc +++ /dev/null @@ -1 +0,0 @@ -engine-strict=true diff --git a/packages/sv/src/create/templates/library/.npmrc b/packages/sv/src/create/templates/library/.npmrc deleted file mode 100644 index b6f27f135..000000000 --- a/packages/sv/src/create/templates/library/.npmrc +++ /dev/null @@ -1 +0,0 @@ -engine-strict=true diff --git a/packages/sv/src/create/templates/minimal/.npmrc b/packages/sv/src/create/templates/minimal/.npmrc deleted file mode 100644 index b6f27f135..000000000 --- a/packages/sv/src/create/templates/minimal/.npmrc +++ /dev/null @@ -1 +0,0 @@ -engine-strict=true From 155c194d0c9639d061e7e284a50a087cee828a63 Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Wed, 7 Oct 2026 10:41:09 +0200 Subject: [PATCH 10/20] add engine-strict to .npmrc as default handler ref #1341 --- packages/sv/src/core/package-manager.ts | 49 +++++++++++++++++++++---- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index 273ca0662..a43749012 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -151,16 +151,18 @@ export function addAllowBuildsIfPnpm(options: { /** * add engine strict configuration according to selected package manager - * - * handled package managers are: pnpm */ export function addEngineStrict(options: { cwd: string; packageManager: AgentName | null | undefined; }) { - const { packageManager } = options; - if (packageManager === 'pnpm') { - addEngineStrictForPnpm(options); + switch (options.packageManager) { + case 'pnpm': + addEngineStrictForPnpm(options); + break; + default: + addEngineStrictForNpm(options); + break; } } @@ -182,10 +184,41 @@ export function addEngineStrictForPnpm(options: { function writeEngineStrict(): TransformFn { return transforms.yaml(({ data }) => { const existing = data.get('engineStrict'); - // if not set, set to true - if (existing === undefined) { - data.set('engineStrict', true); + if (existing !== undefined) { + return false; } + data.set('engineStrict', true); + }); + } + + const newContent = writeEngineStrict()(content); + + if (newContent && newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); +} + +/** + * adds `engine-strict` to `.npmrc` if not set. + * creates the file if it does not exist. + */ +export function addEngineStrictForNpm(options: { + cwd: string; + packageManager: AgentName | null | undefined; +}): void { + const { cwd, packageManager } = options; + if (packageManager === 'pnpm') return; + + const found = find.up('.npmrc', { cwd }); + const filePath = found ?? path.join(cwd, '.npmrc'); + const content = found ? fs.readFileSync(found, 'utf-8') : ''; + + function writeEngineStrict(): TransformFn { + return transforms.text(({ content, text }) => { + const existing = content.includes('engine-strict'); + if (existing) { + return false; + } + console.log('adding engine-strict=true'); + return text.upsert(content, 'engine-strict', { value: 'true' }); }); } From 01cb233fdbb3ad4922634e74fbe626448897a219 Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Wed, 7 Oct 2026 10:50:57 +0200 Subject: [PATCH 11/20] update cli test snapshot for @my-org/sv (adds `.npmrc`) ref #1341 --- packages/sv/src/cli/tests/snapshots/@my-org/sv/.npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/sv/src/cli/tests/snapshots/@my-org/sv/.npmrc diff --git a/packages/sv/src/cli/tests/snapshots/@my-org/sv/.npmrc b/packages/sv/src/cli/tests/snapshots/@my-org/sv/.npmrc new file mode 100644 index 000000000..b6f27f135 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/@my-org/sv/.npmrc @@ -0,0 +1 @@ +engine-strict=true From 287407bf899102e3ab83f568ca3d292f16274354 Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Thu, 8 Oct 2026 07:45:59 +0200 Subject: [PATCH 12/20] update changeset ref #1341 --- .changeset/early-kiwis-drum.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/early-kiwis-drum.md b/.changeset/early-kiwis-drum.md index e5e85af48..fab3dbce0 100644 --- a/.changeset/early-kiwis-drum.md +++ b/.changeset/early-kiwis-drum.md @@ -3,4 +3,5 @@ --- fix(sv): templates set `engineStrict` in `pnpm-workspace.yaml` when package manager is `pnpm` - \ No newline at end of file +ref(sv): handle `.npmrc` file programatically + From 8cf1fddfee2be1180a623972f63299694b683f63 Mon Sep 17 00:00:00 2001 From: Pascal Luxain Date: Thu, 8 Oct 2026 17:24:25 +0200 Subject: [PATCH 13/20] remove console.log ref #1341 --- packages/sv/src/core/package-manager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index a43749012..a154cce40 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -217,7 +217,6 @@ export function addEngineStrictForNpm(options: { if (existing) { return false; } - console.log('adding engine-strict=true'); return text.upsert(content, 'engine-strict', { value: 'true' }); }); } From 9702be81d892744187cd93696bf0222f5044c4b5 Mon Sep 17 00:00:00 2001 From: jycouet Date: Fri, 9 Oct 2026 09:37:06 +0200 Subject: [PATCH 14/20] only touch config files inside the new project find.up() can walk past the project and edit e.g. ~/.npmrc or a parent workspace. A fresh scaffold owns its root, so path.join(cwd, ...) is enough. --- packages/sv/src/core/package-manager.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index a154cce40..bd396a543 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -177,9 +177,8 @@ export function addEngineStrictForPnpm(options: { const { cwd, packageManager } = options; if (packageManager !== 'pnpm') return; - 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 filePath = path.join(cwd, 'pnpm-workspace.yaml'); + const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''; function writeEngineStrict(): TransformFn { return transforms.yaml(({ data }) => { @@ -207,9 +206,8 @@ export function addEngineStrictForNpm(options: { const { cwd, packageManager } = options; if (packageManager === 'pnpm') return; - const found = find.up('.npmrc', { cwd }); - const filePath = found ?? path.join(cwd, '.npmrc'); - const content = found ? fs.readFileSync(found, 'utf-8') : ''; + const filePath = path.join(cwd, '.npmrc'); + const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''; function writeEngineStrict(): TransformFn { return transforms.text(({ content, text }) => { From 75e023cb694a7f274355a2d26b007415cb08cbbc Mon Sep 17 00:00:00 2001 From: jycouet Date: Fri, 9 Oct 2026 09:37:17 +0200 Subject: [PATCH 15/20] call transforms directly instead of wrapping them A function that returns a transform and is called right away adds a layer without adding meaning. `transforms.x(fn)(content)` reads top to bottom. --- packages/sv/src/core/package-manager.ts | 33 +++++++------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index bd396a543..7911db410 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -10,8 +10,7 @@ import { detect, pnpm, resolveCommand, - transforms, - type TransformFn + transforms } from '@sveltejs/sv-utils'; import { Option } from 'commander'; import * as find from 'empathic/find'; @@ -180,17 +179,10 @@ export function addEngineStrictForPnpm(options: { const filePath = path.join(cwd, 'pnpm-workspace.yaml'); const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''; - function writeEngineStrict(): TransformFn { - return transforms.yaml(({ data }) => { - const existing = data.get('engineStrict'); - if (existing !== undefined) { - return false; - } - data.set('engineStrict', true); - }); - } - - const newContent = writeEngineStrict()(content); + const newContent = transforms.yaml(({ data }) => { + if (data.get('engineStrict') !== undefined) return false; + data.set('engineStrict', true); + })(content); if (newContent && newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); } @@ -209,17 +201,10 @@ export function addEngineStrictForNpm(options: { const filePath = path.join(cwd, '.npmrc'); const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''; - function writeEngineStrict(): TransformFn { - return transforms.text(({ content, text }) => { - const existing = content.includes('engine-strict'); - if (existing) { - return false; - } - return text.upsert(content, 'engine-strict', { value: 'true' }); - }); - } - - const newContent = writeEngineStrict()(content); + const newContent = transforms.text(({ content, text }) => { + if (content.includes('engine-strict')) return false; + return text.upsert(content, 'engine-strict', { value: 'true' }); + })(content); if (newContent && newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); } From 9f560b5bf9368ea099892deab5b0cdaf544a1254 Mon Sep 17 00:00:00 2001 From: jycouet Date: Fri, 9 Oct 2026 09:37:17 +0200 Subject: [PATCH 16/20] let text.upsert handle existing keys upsert already skips `key=` when present. The includes() check was redundant and would also match a commented-out line. --- packages/sv/src/core/package-manager.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index 7911db410..4b9ebcbec 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -201,10 +201,9 @@ export function addEngineStrictForNpm(options: { const filePath = path.join(cwd, '.npmrc'); const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''; - const newContent = transforms.text(({ content, text }) => { - if (content.includes('engine-strict')) return false; - return text.upsert(content, 'engine-strict', { value: 'true' }); - })(content); + const newContent = transforms.text(({ content, text }) => + text.upsert(content, 'engine-strict', { value: 'true' }) + )(content); if (newContent && newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); } From 905ac84d295cdede28ba9ef24e6a9192044f375e Mon Sep 17 00:00:00 2001 From: jycouet Date: Fri, 9 Oct 2026 09:37:25 +0200 Subject: [PATCH 17/20] decide the package manager once, keep helpers private addEngineStrict already branches on the package manager, so the helpers don't need to re-check it. Only the entry point is used outside this file. --- packages/sv/src/core/package-manager.ts | 26 ++++--------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index 4b9ebcbec..6800128bb 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -155,27 +155,15 @@ export function addEngineStrict(options: { cwd: string; packageManager: AgentName | null | undefined; }) { - switch (options.packageManager) { - case 'pnpm': - addEngineStrictForPnpm(options); - break; - default: - addEngineStrictForNpm(options); - break; - } + if (options.packageManager === 'pnpm') addEngineStrictForPnpm(options.cwd); + else addEngineStrictForNpm(options.cwd); } /** * adds `engineStrict` to `pnpm-workspace.yaml` if not set. * creates the file if it does not exist. */ -export function addEngineStrictForPnpm(options: { - cwd: string; - packageManager: AgentName | null | undefined; -}): void { - const { cwd, packageManager } = options; - if (packageManager !== 'pnpm') return; - +function addEngineStrictForPnpm(cwd: string): void { const filePath = path.join(cwd, 'pnpm-workspace.yaml'); const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''; @@ -191,13 +179,7 @@ export function addEngineStrictForPnpm(options: { * adds `engine-strict` to `.npmrc` if not set. * creates the file if it does not exist. */ -export function addEngineStrictForNpm(options: { - cwd: string; - packageManager: AgentName | null | undefined; -}): void { - const { cwd, packageManager } = options; - if (packageManager === 'pnpm') return; - +function addEngineStrictForNpm(cwd: string): void { const filePath = path.join(cwd, '.npmrc'); const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''; From 179be450e2da063bb0123d684becc642341f9e7e Mon Sep 17 00:00:00 2001 From: jycouet Date: Fri, 9 Oct 2026 09:37:37 +0200 Subject: [PATCH 18/20] extract read/transform/write into transformFile() The same 3 lines appeared in every helper. One place to get it right, and each helper now only says *which* file and *what* change. --- packages/sv/src/core/package-manager.ts | 41 ++++++++++++------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index 6800128bb..d61f3be68 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -10,7 +10,8 @@ import { detect, pnpm, resolveCommand, - transforms + transforms, + type TransformFn } from '@sveltejs/sv-utils'; import { Option } from 'commander'; import * as find from 'empathic/find'; @@ -141,11 +142,8 @@ export function addAllowBuildsIfPnpm(options: { const { cwd, packageManager, packages } = options; if (packageManager !== 'pnpm') return; - 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({ cwd, packages })(content); - if (newContent && newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); + const filePath = find.up('pnpm-workspace.yaml', { cwd }) ?? path.join(cwd, 'pnpm-workspace.yaml'); + transformFile(filePath, pnpm.allowBuilds({ cwd, packages })); } /** @@ -164,15 +162,13 @@ export function addEngineStrict(options: { * creates the file if it does not exist. */ function addEngineStrictForPnpm(cwd: string): void { - const filePath = path.join(cwd, 'pnpm-workspace.yaml'); - const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''; - - const newContent = transforms.yaml(({ data }) => { - if (data.get('engineStrict') !== undefined) return false; - data.set('engineStrict', true); - })(content); - - if (newContent && newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); + transformFile( + path.join(cwd, 'pnpm-workspace.yaml'), + transforms.yaml(({ data }) => { + if (data.get('engineStrict') !== undefined) return false; + data.set('engineStrict', true); + }) + ); } /** @@ -180,12 +176,15 @@ function addEngineStrictForPnpm(cwd: string): void { * creates the file if it does not exist. */ function addEngineStrictForNpm(cwd: string): void { - const filePath = path.join(cwd, '.npmrc'); - const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''; - - const newContent = transforms.text(({ content, text }) => - text.upsert(content, 'engine-strict', { value: 'true' }) - )(content); + transformFile( + path.join(cwd, '.npmrc'), + transforms.text(({ content, text }) => text.upsert(content, 'engine-strict', { value: 'true' })) + ); +} +/** Missing file is treated as empty, so the transform can create it. */ +function transformFile(filePath: string, transform: TransformFn): void { + const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''; + const newContent = transform(content); if (newContent && newContent !== content) fs.writeFileSync(filePath, newContent, 'utf-8'); } From a9d9418f6b84f5075d27486a9326cc228288c1e8 Mon Sep 17 00:00:00 2001 From: jycouet Date: Fri, 9 Oct 2026 09:38:03 +0200 Subject: [PATCH 19/20] share one options type, keep only the comment that explains why The JSDoc restated the function names. The one useful fact is why pnpm gets a different file, so that's the comment that stays. --- packages/sv/src/core/package-manager.ts | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/packages/sv/src/core/package-manager.ts b/packages/sv/src/core/package-manager.ts index d61f3be68..b2c56b5e2 100644 --- a/packages/sv/src/core/package-manager.ts +++ b/packages/sv/src/core/package-manager.ts @@ -134,11 +134,7 @@ function isInstalled(agent: AgentName): boolean { * `sv.file`, but the CLI itself runs outside that pipeline, so it locates (or creates) * `pnpm-workspace.yaml` by hand. */ -export function addAllowBuildsIfPnpm(options: { - cwd: string; - packageManager: AgentName | null | undefined; - packages: string[]; -}): void { +export function addAllowBuildsIfPnpm(options: ProjectOptions & { packages: string[] }): void { const { cwd, packageManager, packages } = options; if (packageManager !== 'pnpm') return; @@ -146,21 +142,17 @@ export function addAllowBuildsIfPnpm(options: { transformFile(filePath, pnpm.allowBuilds({ cwd, packages })); } -/** - * add engine strict configuration according to selected package manager - */ -export function addEngineStrict(options: { +type ProjectOptions = { cwd: string; packageManager: AgentName | null | undefined; -}) { +}; + +/** pnpm reads its settings from `pnpm-workspace.yaml`, others from `.npmrc`. */ +export function addEngineStrict(options: ProjectOptions): void { if (options.packageManager === 'pnpm') addEngineStrictForPnpm(options.cwd); else addEngineStrictForNpm(options.cwd); } -/** - * adds `engineStrict` to `pnpm-workspace.yaml` if not set. - * creates the file if it does not exist. - */ function addEngineStrictForPnpm(cwd: string): void { transformFile( path.join(cwd, 'pnpm-workspace.yaml'), @@ -171,10 +163,6 @@ function addEngineStrictForPnpm(cwd: string): void { ); } -/** - * adds `engine-strict` to `.npmrc` if not set. - * creates the file if it does not exist. - */ function addEngineStrictForNpm(cwd: string): void { transformFile( path.join(cwd, '.npmrc'), From b56f99d0e80d066180a011756645ae12a994075a Mon Sep 17 00:00:00 2001 From: jycouet Date: Fri, 9 Oct 2026 09:47:50 +0200 Subject: [PATCH 20/20] merge the changeset into one line Users care about the result (which file gets the setting), not that .npmrc is now written by code instead of copied from the template. --- .changeset/early-kiwis-drum.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.changeset/early-kiwis-drum.md b/.changeset/early-kiwis-drum.md index fab3dbce0..807173989 100644 --- a/.changeset/early-kiwis-drum.md +++ b/.changeset/early-kiwis-drum.md @@ -2,6 +2,4 @@ "sv": patch --- -fix(sv): templates set `engineStrict` in `pnpm-workspace.yaml` when package manager is `pnpm` -ref(sv): handle `.npmrc` file programatically - +fix(sv): set `engineStrict` in `pnpm-workspace.yaml` for pnpm projects, `engine-strict` in `.npmrc` otherwise