@@ -115,7 +115,7 @@ const EMPTY_PROJECT_ALLOWED_ENTRIES = new Set([
115115 '.zencoder' ,
116116] ) ;
117117
118- const PNPM_ONLY_BUILT_DEPENDENCIES = [ 'better-sqlite3' , 'esbuild' , 'workerd' ] as const ;
118+ const PNPM_ALLOWED_BUILDS = [ 'better-sqlite3' , 'esbuild' , 'workerd' ] as const ;
119119const drizzleKitBin = join ( fileURLToPath ( import . meta. resolve ( 'drizzle-kit' ) ) , '..' , 'bin.cjs' ) ;
120120
121121const EMPTY_PROJECT_ALLOWED_PACKAGES = new Set ( [
@@ -1801,23 +1801,97 @@ function ensureGitignore(root: string): void {
18011801 log . success ( 'Updated .gitignore with Void defaults' ) ;
18021802}
18031803
1804- function ensurePnpmBuiltDependencies ( pkg : JsonObject ) : void {
1805- const pnpm = asRecord ( pkg . pnpm ) ?? { } ;
1806- pkg . pnpm = pnpm ;
1807-
1808- const onlyBuiltDependencies = Array . isArray ( pnpm . onlyBuiltDependencies )
1809- ? pnpm . onlyBuiltDependencies . filter (
1810- ( value : unknown ) : value is string => typeof value === 'string' ,
1811- )
1812- : [ ] ;
1804+ function removeTopLevelYamlBlock ( yaml : string , key : string ) : string {
1805+ const lines = yaml . split ( / \r ? \n / ) ;
1806+ const next : Array < string > = [ ] ;
1807+ for ( let i = 0 ; i < lines . length ; i += 1 ) {
1808+ const line = lines [ i ] ;
1809+ if ( ! new RegExp ( `^ ${ key } :\\s*(?:#.*)?$` ) . test ( line ) ) {
1810+ next . push ( line ) ;
1811+ continue ;
1812+ }
18131813
1814- for ( const dependency of PNPM_ONLY_BUILT_DEPENDENCIES ) {
1815- if ( ! onlyBuiltDependencies . includes ( dependency ) ) {
1816- onlyBuiltDependencies . push ( dependency ) ;
1814+ i += 1 ;
1815+ while ( i < lines . length && ( lines [ i ] . trim ( ) === '' || / ^ \s / . test ( lines [ i ] ) ) ) {
1816+ i += 1 ;
18171817 }
1818+ i -= 1 ;
1819+ }
1820+ return next . join ( '\n' ) ;
1821+ }
1822+
1823+ function pnpmAllowBuildsYaml ( ) : string {
1824+ const lines = [ 'allowBuilds:' ] ;
1825+ for ( const dependency of PNPM_ALLOWED_BUILDS ) {
1826+ lines . push ( ` ${ dependency } : true` ) ;
1827+ }
1828+ return lines . join ( '\n' ) ;
1829+ }
1830+
1831+ function insertPnpmAllowBuilds ( yaml : string ) : string {
1832+ const trimmed = yaml . trim ( ) ;
1833+ const allowBuilds = pnpmAllowBuildsYaml ( ) ;
1834+ if ( trimmed === '' ) {
1835+ return `${ allowBuilds } \n` ;
1836+ }
1837+
1838+ const lines = trimmed . split ( / \r ? \n / ) ;
1839+ const packagesIndex = lines . findIndex ( ( line ) => / ^ p a c k a g e s : \s * (?: # .* ) ? $ / . test ( line ) ) ;
1840+ if ( packagesIndex === - 1 ) {
1841+ return `${ trimmed } \n\n${ allowBuilds } \n` ;
1842+ }
1843+
1844+ let insertIndex = packagesIndex + 1 ;
1845+ while (
1846+ insertIndex < lines . length &&
1847+ ( lines [ insertIndex ] . trim ( ) === '' || / ^ \s / . test ( lines [ insertIndex ] ) )
1848+ ) {
1849+ insertIndex += 1 ;
1850+ }
1851+ lines . splice ( insertIndex , 0 , '' , ...allowBuilds . split ( '\n' ) , '' ) ;
1852+ return `${ lines . join ( '\n' ) . replace ( / \n { 3 , } / g, '\n\n' ) } \n` ;
1853+ }
1854+
1855+ function resolvePnpmConfigRoot ( root : string , pkg : JsonObject ) : string {
1856+ if (
1857+ existsSync ( join ( root , 'pnpm-workspace.yaml' ) ) ||
1858+ existsSync ( join ( root , 'pnpm-lock.yaml' ) ) ||
1859+ parsePackageManagerField ( pkg ) === 'pnpm'
1860+ ) {
1861+ return root ;
18181862 }
18191863
1820- pnpm . onlyBuiltDependencies = onlyBuiltDependencies ;
1864+ const workspaceRoot = findWorkspaceRoot ( root ) ;
1865+ if (
1866+ workspaceRoot !== undefined &&
1867+ isWorkspaceSubpackage ( workspaceRoot , root ) &&
1868+ hasAncestorPnpmMarkers ( root )
1869+ ) {
1870+ return workspaceRoot ;
1871+ }
1872+
1873+ return root ;
1874+ }
1875+
1876+ function ensurePnpmAllowBuilds ( root : string , pkg : JsonObject ) : boolean {
1877+ const configRoot = resolvePnpmConfigRoot ( root , pkg ) ;
1878+ const workspacePath = join ( configRoot , 'pnpm-workspace.yaml' ) ;
1879+ const before = existsSync ( workspacePath ) ? readFileSync ( workspacePath , 'utf-8' ) : '' ;
1880+ const withoutLegacy = removeTopLevelYamlBlock (
1881+ removeTopLevelYamlBlock ( before , 'onlyBuiltDependencies' ) ,
1882+ 'allowBuilds' ,
1883+ ) ;
1884+ const after = insertPnpmAllowBuilds ( withoutLegacy ) ;
1885+ if ( after !== before ) {
1886+ writeFileSync ( workspacePath , after ) ;
1887+ log . success (
1888+ configRoot === root
1889+ ? 'Updated pnpm-workspace.yaml with approved builds'
1890+ : `Updated ${ relative ( root , workspacePath ) } with approved builds` ,
1891+ ) ;
1892+ return true ;
1893+ }
1894+ return false ;
18211895}
18221896
18231897function installProjectDependencies (
@@ -2065,16 +2139,16 @@ function scaffoldPagesApp(
20652139 ensureDependency ( 'devDependencies' , name , version ) ;
20662140 }
20672141
2068- // Round 44: `ensurePnpmBuiltDependencies` must run for pnpm workspace
2142+ // Round 44: the pnpm build approval setup must run for workspace
20692143 // subpackages too. `detectProjectState` only checks LOCAL markers and
20702144 // returns `'npm'` for a subpackage without its own lockfile / pin,
20712145 // but `isPnpmProject(root, pkg)` walks ancestors and (after round 43)
20722146 // verifies membership. Gate the allowlist on the ancestor-aware check
2073- // so the subpackage gets `pnpm.onlyBuiltDependencies ` and its
2147+ // so the workspace gets `allowBuilds ` and its
20742148 // better-sqlite3 / esbuild / workerd postinstalls run on the next
20752149 // workspace-root `pnpm install`.
20762150 if ( isPnpmProject ( root , pkg ) ) {
2077- ensurePnpmBuiltDependencies ( pkg ) ;
2151+ ensurePnpmAllowBuilds ( root , pkg ) ;
20782152 }
20792153 // The Corepack pin is ONLY for the standalone pnpm case. In a
20802154 // workspace subpackage the pin lives on the workspace root's
@@ -2084,7 +2158,7 @@ function scaffoldPagesApp(
20842158 packageManager === 'pnpm' &&
20852159 ( ! pkg . packageManager || typeof pkg . packageManager !== 'string' )
20862160 ) {
2087- pkg . packageManager = 'pnpm@10.33.0 ' ;
2161+ pkg . packageManager = 'pnpm@11.0.8 ' ;
20882162 }
20892163
20902164 writeFileSync ( packageJsonPath , JSON . stringify ( pkg , null , 2 ) + '\n' ) ;
@@ -2188,14 +2262,7 @@ function ensureExistingProjectPackage(
21882262 dependencyAdded = true ;
21892263 }
21902264
2191- if ( isPnpmProject ( root , pkg ) ) {
2192- const before = JSON . stringify ( asRecord ( pkg . pnpm ) ?. onlyBuiltDependencies ?? null ) ;
2193- ensurePnpmBuiltDependencies ( pkg ) ;
2194- const after = JSON . stringify ( asRecord ( pkg . pnpm ) ?. onlyBuiltDependencies ?? null ) ;
2195- if ( before !== after ) {
2196- changed = true ;
2197- }
2198- }
2265+ const pnpmConfigChanged = isPnpmProject ( root , pkg ) ? ensurePnpmAllowBuilds ( root , pkg ) : false ;
21992266
22002267 if ( changed ) {
22012268 writeFileSync ( packageJsonPath , JSON . stringify ( pkg , null , 2 ) + '\n' ) ;
@@ -2210,7 +2277,7 @@ function ensureExistingProjectPackage(
22102277 ) ;
22112278 }
22122279
2213- return { dependencyAdded, packageJsonMutated : changed } ;
2280+ return { dependencyAdded, packageJsonMutated : changed || pnpmConfigChanged } ;
22142281}
22152282
22162283function insertVoidImport ( code : string ) : string {
@@ -2855,9 +2922,8 @@ export function ensureVoidDependency(root: string): boolean {
28552922 changed = true ;
28562923 }
28572924
2858- // Mirror `scaffoldPagesApp`'s pnpm built-dep allowlist. pnpm 10 ignores
2859- // build scripts unless the package is listed in
2860- // `pnpm.onlyBuiltDependencies`; without this, the freshly scaffolded
2925+ // Mirror `scaffoldPagesApp`'s pnpm build approval config. pnpm requires
2926+ // build scripts to be approved through `allowBuilds`; without this, the freshly scaffolded
28612927 // demo breaks on first local DB / dev commands because
28622928 // `better-sqlite3`/`esbuild`/`workerd` never run their postinstall
28632929 // build steps. Honor both pnpm signals via `isPnpmProject` — a committed
@@ -2866,14 +2932,7 @@ export function ensureVoidDependency(root: string): boolean {
28662932 // `detectProjectState` and `runInitCommand` already treat as pnpm; the
28672933 // demo would then run `pnpm install` against the project but skip the
28682934 // allowlist, leaving the local DB / dev setup broken on first run.
2869- if ( isPnpmProject ( root , pkg ) ) {
2870- const before = JSON . stringify ( asRecord ( pkg . pnpm ) ?. onlyBuiltDependencies ?? null ) ;
2871- ensurePnpmBuiltDependencies ( pkg ) ;
2872- const after = JSON . stringify ( asRecord ( pkg . pnpm ) ?. onlyBuiltDependencies ?? null ) ;
2873- if ( before !== after ) {
2874- changed = true ;
2875- }
2876- }
2935+ const pnpmConfigChanged = isPnpmProject ( root , pkg ) ? ensurePnpmAllowBuilds ( root , pkg ) : false ;
28772936
28782937 if ( changed ) {
28792938 writeFileSync ( packageJsonPath , JSON . stringify ( pkg , null , 2 ) + '\n' ) ;
@@ -2884,7 +2943,7 @@ export function ensureVoidDependency(root: string): boolean {
28842943 ) ;
28852944 }
28862945
2887- return changed ;
2946+ return changed || pnpmConfigChanged ;
28882947}
28892948
28902949interface AgentSetupSelection {
0 commit comments