@gjsify/cli 0.4.22 → 0.4.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.gjs.mjs CHANGED
@@ -796,9 +796,9 @@ jobs:
796
796
  --- gjsify post-install checks ---`);let n=runMinimalChecks().filter(n=>!n.found&&n.severity===`required`);if(n.length>0){Fe.warn(`Missing required system dependencies:
797
797
  `);for(let a of n)Fe.warn(` ✗ ${a.name}`);let a=buildInstallCommand(detectPackageManager$2(),n);a&&Fe.warn(`\nInstall with:\n ${a}`)}else Fe.log(`System dependencies OK.`);let a=detectNativePackages(process.cwd());if(a.length>0){Fe.log(`\nDetected ${a.length} @gjsify/* package(s) with native prebuilds:`);for(let n of a)Fe.log(` • ${n.name}`);Fe.log("\nUse `gjsify run <bundle>` to launch with LD_LIBRARY_PATH/GI_TYPELIB_PATH set.")}}Ie(),Px(),xp();const ZE={command:`foreach [script] [args..]`,description:"Run a workspace script across all (or filtered) workspaces. Drop-in for `yarn workspaces foreach`: -A/--all, -p/--parallel, -t/--topological, --include, --exclude, --no-private. Pass --exec to run an arbitrary command instead of a script.",builder:n=>n.positional(`script`,{description:"Script name to run in each workspace (`run <name>`-equivalent). With --exec, the command to run instead.",type:`string`}).positional(`args`,{description:`Extra arguments forwarded to each child invocation.`,type:`string`,array:!0}).option(`all`,{description:"Include workspaces declared as `private: true`.",type:`boolean`,alias:`A`,default:!1}).option(`parallel`,{description:`Run workspaces in parallel (capped by --jobs).`,type:`boolean`,alias:`p`,default:!1}).option(`topological`,{description:`Wait for each workspace's deps to finish before starting it (production deps only).`,type:`boolean`,alias:`t`,default:!1}).option(`topological-dev`,{description:`Like --topological but also respects devDependencies (often cyclic — use sparingly).`,type:`boolean`,default:!1}).option(`include`,{description:`Glob pattern to include workspaces by name (repeatable).`,type:`string`,array:!0}).option(`exclude`,{description:`Glob pattern to exclude workspaces by name (repeatable).`,type:`string`,array:!0}).option(`private`,{description:`Include private workspaces (default true). Pass --no-private to skip them.`,type:`boolean`,default:!0}).option(`verbose`,{description:`Echo every spawned command before running it.`,type:`boolean`,alias:`v`,default:!1}).option(`jobs`,{description:`Maximum concurrent workspaces in --parallel mode (default: cpu count).`,type:`number`,alias:`j`}).option(`exec`,{description:"Treat <script> [args..] as an arbitrary command (yarn `workspaces foreach exec`-equivalent) instead of a package.json script lookup. Workspace filtering by script presence is skipped. Use `-- <cmd> <args...>` to pass flags to the command without yargs intercepting them.",type:`boolean`,default:!1}).parserConfiguration({"populate--":!0}),handler:async n=>{let a=discoverWorkspaces(findWorkspaceRoot(process.cwd())??process.cwd()),S=n.exec===!0,C=n.script,N=n.args??[];if(S){let a=(n[`--`]??[]).filter(n=>typeof n==`string`);a.length>0&&(C?N=[...N,...a]:(C=a[0],N=[...N,...a.slice(1)])),C||(Fe.error("gjsify foreach --exec: missing command. Pass it after `--`, e.g. `gjsify foreach --exec -- npm publish --tag latest`."),process.exit(1))}let F=filterWorkspaces(a,{include:n.include,exclude:n.exclude,noPrivate:n.private===!1});if(!S){C||(Fe.error(`gjsify foreach: missing <script> positional. Pass --exec to run an arbitrary command instead.`),process.exit(1));let n=C;F=F.filter(a=>typeof(a.manifest.scripts??{})[n]==`string`)}if(F.length===0){Fe.log(`gjsify foreach: no workspaces match (${S?`exec`:`script`}="${C}", include=${JSON.stringify(n.include??[])}, exclude=${JSON.stringify(n.exclude??[])})`);return}(n.topological||n[`topological-dev`])&&(F=topologicalSort(buildDependencyGraph(F,{includeDev:n[`topological-dev`]===!0})));let I=n.verbose===!0,H=C;try{if(n.parallel&&!n.topological&&!n[`topological-dev`]){let a=n.jobs&&n.jobs>0?n.jobs:cpus().length;await runParallel(F,H,N,a,I,S)}else if(n.parallel){let a=n.jobs&&n.jobs>0?n.jobs:cpus().length;await runTopologicalParallel(F,H,N,a,I,n[`topological-dev`]===!0,S)}else await runSequential(F,H,N,I,S)}catch(n){Fe.error(n.message),process.exit(1)}process.exit(0)}};async function runSequential(n,a,S,C,N){for(let F of n)await runOne(F,a,S,!1,C,N)}async function runParallel(n,a,S,C,N,F){let I=0,H=[];for(let W=0;W<C;W++)H.push((async()=>{for(;I<n.length;)await runOne(n[I++],a,S,!0,N,F)})());await Promise.all(H)}async function runTopologicalParallel(n,a,S,C,N,F,I){let H=new Set(n.map(n=>n.name)),W=new Map;for(let a of n){let n=new Set,S=a.manifest;for(let a of[S.dependencies,F?S.devDependencies:void 0,S.optionalDependencies])if(a)for(let[S,C]of Object.entries(a))typeof C==`string`&&C.startsWith(`workspace:`)&&H.has(S)&&n.add(S);W.set(a.name,n)}let K=new Map(n.map(n=>[n.name,n])),q=new Set,Y=0;return new Promise((n,F)=>{let H=null,pump=()=>{if(!H){for(;Y<C;){let C=[...W.entries()].filter(([,n])=>[...n].every(n=>q.has(n))).map(([n])=>n);if(C.length===0)break;let X=C.sort()[0];W.delete(X),Y++,runOne(K.get(X),a,S,!0,N,I).then(()=>{if(Y--,q.add(X),W.size===0&&Y===0){n();return}pump()}).catch(n=>{H=n instanceof Error?n:Error(String(n)),Y===0&&F(H)})}W.size>0&&Y===0&&!H&&F(Error(`gjsify foreach --topological: stuck — workspaces ${[...W.keys()].join(`, `)} have unsatisfied deps in the selected set`))}};pump()})}async function runOne(n,a,S,C,N,F){if(F){N&&Fe.error(`[${n.name}] $ ${a} ${S.join(` `)}`),await spawnPrefixed(a,S,n.location,C?`[${n.name}] `:null);return}let I=detectPackageManager$1(),H=I===`gjsify`?[`run`,a,...S]:[`run`,a,...S.length>0?[`--`,...S]:[]];N&&Fe.error(`[${n.name}] $ ${I} ${H.join(` `)}`),await spawnPrefixed(I,H,n.location,C?`[${n.name}] `:null)}function detectPackageManager$1(){let n=process.env.npm_config_user_agent??``;return n.startsWith(`yarn/`)?`yarn`:n.startsWith(`gjsify/`)?`gjsify`:`npm`}__name$1(detectPackageManager$1,`detectPackageManager`);function spawnPrefixed(n,a,S,C){let N=process.env.FORCE_COLOR!==void 0||process.env.NO_COLOR!==void 0?{}:{FORCE_COLOR:`1`};return new Promise((F,I)=>{let H=spawn(n,a,{cwd:S,stdio:C?[`ignore`,`pipe`,`pipe`]:`inherit`,env:{...process.env,...N}});C&&H.stdout&&H.stderr&&(prefixLines(H.stdout,process.stdout,C),prefixLines(H.stderr,process.stderr,C)),H.on(`close`,S=>{S===0?F():I(Error(`${n} ${a.join(` `)} exited with code ${S}`))}),H.on(`error`,n=>I(n))})}function prefixLines(n,a,S){let C=``;n.setEncoding(`utf-8`),n.on(`data`,n=>{C+=n;let N;for(;(N=C.indexOf(`
798
798
  `))!==-1;)a.write(S+C.slice(0,N+1)),C=C.slice(N+1)}),n.on(`end`,()=>{C.length>0&&a.write(S+C+`
799
- `)})}Ie(),Px();const QE={command:`workspace <name> <script> [args..]`,description:"Run a workspace script (`yarn workspace <name> run <script>` equivalent).",builder:n=>n.positional(`name`,{description:"Workspace name (matches package.json `name` field).",type:`string`,demandOption:!0}).positional(`script`,{description:`Script name to run inside that workspace.`,type:`string`,demandOption:!0}).positional(`args`,{description:`Extra arguments forwarded to the script.`,type:`string`,array:!0}),handler:async n=>{let a=discoverWorkspaces(findWorkspaceRoot(process.cwd())??process.cwd()),S=a.find(a=>a.name===n.name);S||(Fe.error(`gjsify workspace: no workspace named "${n.name}" — discovered ${a.length} workspace(s)`),process.exit(1)),typeof(S.manifest.scripts??{})[n.script]!=`string`&&(Fe.error(`gjsify workspace: workspace "${n.name}" has no script "${n.script}"`),process.exit(1));let C=detectPackageManager(),N=C===`gjsify`?[`run`,n.script,...n.args??[]]:[`run`,n.script,...n.args&&n.args.length>0?[`--`,...n.args]:[]],F=process.env.FORCE_COLOR!==void 0||process.env.NO_COLOR!==void 0?{}:{FORCE_COLOR:`1`};await new Promise((n,a)=>{let I=spawn(C,N,{cwd:S.location,stdio:`inherit`,env:{...process.env,...F}});I.on(`close`,S=>{S===0?n():a(Error(`${C} ${N.join(` `)} exited with code ${S}`))}),I.on(`error`,a)}).catch(n=>{Fe.error(n.message),process.exit(1)}),process.exit(0)}};function detectPackageManager(){let n=process.env.npm_config_user_agent??``;return n.startsWith(`yarn/`)?`yarn`:n.startsWith(`gjsify/`)?`gjsify`:`npm`}Px(),Lo();async function runLifecycleScript(n,a,S,C={}){let N=(a.scripts??{})[S];if(typeof N!=`string`){if(C.optional!==!1)return!1;throw Error(`gjsify lifecycle-script: no "${S}" in ${n}/package.json`)}let F=findWorkspaceRoot(n),I=[Eo(n,`node_modules`,`.bin`)];F&&F!==n&&I.push(Eo(F,`node_modules`,`.bin`));let H=process.env.FORCE_COLOR!==void 0||process.env.NO_COLOR!==void 0?{}:{FORCE_COLOR:`1`},W={...process.env,...H,PATH:[...I,process.env.PATH??``].filter(Boolean).join(Fo),npm_lifecycle_event:S,npm_package_name:a.name??``,npm_package_version:a.version??``,...C.env??{}};return await new Promise((a,F)=>{let I=spawn(N,[],{cwd:n,env:W,stdio:C.stdio??`inherit`,shell:!0});I.on(`close`,C=>{C===0?a():F(Error(`gjsify lifecycle-script: "${S}" in ${n} exited with code ${C}`))}),I.on(`error`,F)}),!0}Ie(),Ts(),Va(),Lo(),HE();const $E={command:`pack [path]`,description:`Produce an npm-compatible .tgz tarball for the workspace at <path> (default: cwd). Rewrites workspace:^/~/* deps to resolved versions.`,builder:n=>n.positional(`path`,{description:`Workspace path (default: cwd).`,type:`string`}).option(`pack-destination`,{description:`Directory to write the tarball into. Default: workspace cwd.`,type:`string`}).option(`json`,{description:"Emit pack metadata as JSON on stdout (mirrors `npm pack --json`).",type:`boolean`,default:!1}).option(`dry-run`,{description:`Compute everything but do not write the .tgz.`,type:`boolean`,default:!1}).option(`ignore-scripts`,{description:"Skip the `prepack` lifecycle script before packing. Mirrors `npm pack --ignore-scripts`. Use when scripts are already run by the outer workflow.",type:`boolean`,default:!1}),handler:async n=>{let a=await packWorkspace(Co(n.path??process.cwd()),{destination:n[`pack-destination`],dryRun:n[`dry-run`]===!0,lifecycleScripts:n[`ignore-scripts`]?[]:[`prepack`]});n.json?process.stdout.write(`${JSON.stringify([a],null,2)}\n`):process.stdout.write(`${a.filename}\n`)}};async function packWorkspace(n,a={}){let S=Eo(n,`package.json`);if(!existsSync(S))throw Error(`gjsify pack: no package.json at ${n}`);let C=readFileSync(S,`utf-8`),N=JSON.parse(C),F=typeof N.name==`string`?N.name:``,I=typeof N.version==`string`?N.version:`0.0.0`;if(!F)throw Error(`gjsify pack: package.json at ${n} has no "name"`);let H=a.lifecycleScripts??[`prepack`];for(let a of H)await runLifecycleScript(n,N,a,{optional:!0});let W=readFileSync(S,`utf-8`),K=W===C?N:JSON.parse(W),q=a.skipWorkspaceRewrite?K:rewriteWorkspaceDeps(K,n),Y=JSON.stringify(q,null,indentOf(W))+`
799
+ `)})}Ie(),Px();const QE={command:`workspace <name> <script> [args..]`,description:"Run a workspace script (`yarn workspace <name> run <script>` equivalent).",builder:n=>n.positional(`name`,{description:"Workspace name (matches package.json `name` field).",type:`string`,demandOption:!0}).positional(`script`,{description:`Script name to run inside that workspace.`,type:`string`,demandOption:!0}).positional(`args`,{description:`Extra arguments forwarded to the script.`,type:`string`,array:!0}),handler:async n=>{let a=discoverWorkspaces(findWorkspaceRoot(process.cwd())??process.cwd()),S=a.find(a=>a.name===n.name);S||(Fe.error(`gjsify workspace: no workspace named "${n.name}" — discovered ${a.length} workspace(s)`),process.exit(1)),typeof(S.manifest.scripts??{})[n.script]!=`string`&&(Fe.error(`gjsify workspace: workspace "${n.name}" has no script "${n.script}"`),process.exit(1));let C=detectPackageManager(),N=C===`gjsify`?[`run`,n.script,...n.args??[]]:[`run`,n.script,...n.args&&n.args.length>0?[`--`,...n.args]:[]],F=process.env.FORCE_COLOR!==void 0||process.env.NO_COLOR!==void 0?{}:{FORCE_COLOR:`1`};await new Promise((n,a)=>{let I=spawn(C,N,{cwd:S.location,stdio:`inherit`,env:{...process.env,...F}});I.on(`close`,S=>{S===0?n():a(Error(`${C} ${N.join(` `)} exited with code ${S}`))}),I.on(`error`,a)}).catch(n=>{Fe.error(n.message),process.exit(1)}),process.exit(0)}};function detectPackageManager(){let n=process.env.npm_config_user_agent??``;return n.startsWith(`yarn/`)?`yarn`:n.startsWith(`gjsify/`)?`gjsify`:`npm`}Px(),Lo();async function runLifecycleScript(n,a,S,C={}){let N=(a.scripts??{})[S];if(typeof N!=`string`){if(C.optional!==!1)return!1;throw Error(`gjsify lifecycle-script: no "${S}" in ${n}/package.json`)}let F=findWorkspaceRoot(n),I=[Eo(n,`node_modules`,`.bin`)];F&&F!==n&&I.push(Eo(F,`node_modules`,`.bin`));let H=process.env.FORCE_COLOR!==void 0||process.env.NO_COLOR!==void 0?{}:{FORCE_COLOR:`1`},W={...process.env,...H,PATH:[...I,process.env.PATH??``].filter(Boolean).join(Fo),npm_lifecycle_event:S,npm_package_name:a.name??``,npm_package_version:a.version??``,...C.env??{}},K=C.stdio===`inherit-stderr`?[`inherit`,2,2]:C.stdio??`inherit`;return await new Promise((a,C)=>{let F=spawn(N,[],{cwd:n,env:W,stdio:K,shell:!0});F.on(`close`,N=>{N===0?a():C(Error(`gjsify lifecycle-script: "${S}" in ${n} exited with code ${N}`))}),F.on(`error`,C)}),!0}Ie(),Ts(),Va(),Lo(),HE();const $E={command:`pack [path]`,description:`Produce an npm-compatible .tgz tarball for the workspace at <path> (default: cwd). Rewrites workspace:^/~/* deps to resolved versions.`,builder:n=>n.positional(`path`,{description:`Workspace path (default: cwd).`,type:`string`}).option(`pack-destination`,{description:`Directory to write the tarball into. Default: workspace cwd.`,type:`string`}).option(`json`,{description:"Emit pack metadata as JSON on stdout (mirrors `npm pack --json`).",type:`boolean`,default:!1}).option(`dry-run`,{description:`Compute everything but do not write the .tgz.`,type:`boolean`,default:!1}).option(`ignore-scripts`,{description:"Skip the `prepack` lifecycle script before packing. Mirrors `npm pack --ignore-scripts`. Use when scripts are already run by the outer workflow.",type:`boolean`,default:!1}),handler:async n=>{let a=await packWorkspace(Co(n.path??process.cwd()),{destination:n[`pack-destination`],dryRun:n[`dry-run`]===!0,lifecycleScripts:n[`ignore-scripts`]?[]:[`prepack`],lifecycleStdio:n.json?`inherit-stderr`:`inherit`});n.json?process.stdout.write(`${JSON.stringify([a],null,2)}\n`):process.stdout.write(`${a.filename}\n`)}};async function packWorkspace(n,a={}){let S=Eo(n,`package.json`);if(!existsSync(S))throw Error(`gjsify pack: no package.json at ${n}`);let C=readFileSync(S,`utf-8`),N=JSON.parse(C),F=typeof N.name==`string`?N.name:``,I=typeof N.version==`string`?N.version:`0.0.0`;if(!F)throw Error(`gjsify pack: package.json at ${n} has no "name"`);let H=a.lifecycleScripts??[`prepack`];for(let S of H)await runLifecycleScript(n,N,S,{optional:!0,stdio:a.lifecycleStdio});let W=readFileSync(S,`utf-8`),K=W===C?N:JSON.parse(W),q=a.skipWorkspaceRewrite?K:rewriteWorkspaceDeps(K,n),Y=JSON.stringify(q,null,indentOf(W))+`
800
800
  `,X=collectFiles(n,K),te=[{name:`package/`,directory:!0,mode:493}],ne=[],re=0;for(let a of X){let S;S=a===`package.json`?new TextEncoder().encode(Y):new Uint8Array(readFileSync(Eo(n,a)));let C=statSync(Eo(n,a)).mode&511;te.push({name:`package/${a}`,body:S,mode:C,mtime:0}),ne.push({path:a,size:S.byteLength,mode:C}),re+=S.byteLength}let ie=await gzip(createTarball(te)),ae=`${F.startsWith(`@`)?F.slice(1).replace(`/`,`-`):F}-${I}.tgz`,oe=createHash(`sha1`).update(ie).digest(`hex`),Z=`sha512-${createHash(`sha512`).update(ie).digest(`base64`)}`,se=a.destination?Co(a.destination):n,ce=Eo(se,ae);return a.dryRun||(mkdirSync(se,{recursive:!0}),writeFileSync(ce,ie)),{filename:ae,name:F,version:I,size:ie.byteLength,unpackedSize:re,shasum:oe,integrity:Z,entryCount:ne.length,files:ne,absolutePath:a.dryRun?null:ce}}function collectFiles(n,a){let S=forceIncluded(a),C=Array.isArray(a.files)?a.files.filter(n=>typeof n==`string`):null,N;N=C?expandFilesPatterns(n,C):walkAll(n);let F=loadIgnore(n),I=new Set;for(let n of N)F(n)||I.add(n);for(let a of S)existsSync(Eo(n,a))&&I.add(a);return[...I].sort()}const eD=new Set([`.git`,`.svn`,`.hg`,`.gitignore`,`.gitattributes`,`.npmrc`,`CVS`,`.DS_Store`,`node_modules`,`.npmignore`,`package-lock.json`,`gjsify-lock.json`,`yarn.lock`,`yarn-error.log`,`.yarn`,`.pnp.cjs`,`.pnp.loader.mjs`,`tsconfig.tsbuildinfo`]);function forceIncluded(n){let a=new Set;a.add(`package.json`);for(let n of[`README`,`README.md`,`LICENSE`,`LICENSE.md`,`NOTICE`,`NOTICE.md`])a.add(n);let S=typeof n.main==`string`?n.main:null;S&&a.add(S.replace(/^\.\//,``));let C=n.bin;if(typeof C==`string`)a.add(C.replace(/^\.\//,``));else if(C&&typeof C==`object`)for(let n of Object.values(C))typeof n==`string`&&a.add(n.replace(/^\.\//,``));return[...a]}function walkAll(n,a=``){let S=[],C=a?Eo(n,a):n,N;try{N=readdirSync(C,{withFileTypes:!0})}catch{return S}for(let C of N){if(eD.has(C.name)||C.name.startsWith(`.tsbuildinfo`))continue;let N=a?`${a}/${C.name}`:C.name;C.isDirectory()?S.push(...walkAll(n,N)):C.isFile()&&S.push(N)}return S}function expandFilesPatterns(n,a){let S=new Set;for(let C of a){let a=C.replace(/^\.\//,``).replace(/\/$/,``),N=Eo(n,a);if(!existsSync(N))continue;let F=statSync(N);if(F.isDirectory())for(let C of walkAll(n,a))S.add(C);else F.isFile()&&S.add(a);!existsSync(N)&&/[*?[]/.test(C)&&Fe.warn(`gjsify pack: files entry "${C}" looks like a glob but glob expansion isn't implemented — pass literal files/dirs`)}return[...S]}function loadIgnore(n){let a=Eo(n,`.npmignore`),S=Eo(n,`.gitignore`),C=[],N=existsSync(a)?a:existsSync(S)?S:null;if(N){let n=readFileSync(N,`utf-8`).split(`
801
- `);for(let a of n){let n=a.trim();!n||n.startsWith(`#`)||n.startsWith(`!`)||C.push(globToRegex(n))}}return n=>{for(let a of C)if(a.test(n))return!0;return!1}}function globToRegex(n){let a=n.replace(/^\//,``);return a=a.replace(/[.+^${}()|[\]\\]/g,`\\$&`),a=a.replace(/\*\*/g,`__DOUBLESTAR__`).replace(/\*/g,`[^/]*`).replace(/__DOUBLESTAR__/g,`.*`),a=a.replace(/\?/g,`[^/]`),RegExp(`^${a}($|/)`)}function rewriteWorkspaceDeps(n,a){let S=findWorkspaceRoot(a);if(!S)return n;let C=new Map;for(let n of discoverWorkspaces(S))n.name&&n.version&&C.set(n.name,n.version);let N=JSON.parse(JSON.stringify(n));for(let n of[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`]){let a=N[n];if(a)for(let[n,F]of Object.entries(a)){if(typeof F!=`string`||!F.startsWith(`workspace:`))continue;let I=C.get(n);if(!I)throw Error(`gjsify pack: ${N.name} declares workspace:^ on ${n} but no sibling workspace with that name exists in the monorepo at ${S}`);let H=F.slice(10);H===`*`||H===``?a[n]=I:H===`^`||H===`~`?a[n]=`${H}${I}`:a[n]=H}}return N}function indentOf(n){let a=n.match(/\n([ \t]+)"/);return a?a[1]:` `}var OidcUnavailableError=class extends Error{reason;constructor(n,a){super(n),this.reason=a,this.name=`OidcUnavailableError`}},OidcExchangeError=class extends Error{status;body;packageName;constructor(n,a,S,C){super(n),this.status=a,this.body=S,this.packageName=C,this.name=`OidcExchangeError`}};function hasGithubOidcEnv(){return!!(process.env.ACTIONS_ID_TOKEN_REQUEST_URL&&process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN)}async function fetchGithubOidcToken(n,a){let S=process.env.ACTIONS_ID_TOKEN_REQUEST_URL,C=process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;if(!S||!C)throw new OidcUnavailableError("GitHub Actions OIDC env vars (ACTIONS_ID_TOKEN_REQUEST_{URL,TOKEN}) not set. The calling workflow needs `permissions: id-token: write`.",`no-env`);let N=new URL(S);N.searchParams.set(`audience`,n),a?.(`gjsify oidc: GET ${N.href.replace(C,`<bearer>`)}`);let F=await fetch(N.href,{method:`GET`,headers:{Accept:`application/json`,Authorization:`Bearer ${C}`}});if(!F.ok){let n=await F.text().catch(()=>`<no body>`);throw new OidcUnavailableError(`Failed to fetch GitHub OIDC id_token: ${F.status} ${F.statusText} — ${n.slice(0,200)}`,`fetch-id-token`)}let I=await F.json().catch(()=>({}));if(!I.value)throw new OidcUnavailableError("GitHub OIDC response missing `value` field",`no-id-token`);return I.value}async function exchangeOidcForNpmToken(n){let{packageName:a,registry:S,idToken:C,log:N}=n,F=`${S.endsWith(`/`)?S.slice(0,-1):S}/-/npm/v1/oidc/token/exchange/package/${a.startsWith(`@`)?(()=>{let n=a.indexOf(`/`),S=a.slice(1,n),C=a.slice(n+1);return`@${encodeURIComponent(S)}%2f${encodeURIComponent(C)}`})():encodeURIComponent(a)}`;N?.(`gjsify oidc: POST ${F}`);let I=await fetch(F,{method:`POST`,headers:{Authorization:`Bearer ${C}`,"Content-Type":`application/json`,Accept:`application/json`},body:`{}`}),H=await I.text().catch(()=>``);if(!I.ok)throw new OidcExchangeError(`npm OIDC token exchange failed for ${a}: ${I.status} ${I.statusText} — ${H.slice(0,300)}`,I.status,H,a);let W;try{W=JSON.parse(H)}catch{throw new OidcExchangeError(`npm OIDC token exchange returned non-JSON body for ${a}: ${H.slice(0,200)}`,I.status,H,a)}if(!W.token)throw new OidcExchangeError(`npm OIDC token exchange returned no \`token\` field for ${a}`,I.status,H,a);return W.token}async function getNpmTrustedToken(n){let a=`npm:${new URL(n.registry).hostname}`,S=await fetchGithubOidcToken(a,n.log);return{token:await exchangeOidcForNpmToken({...n,idToken:S}),audience:a}}Ie(),Ts(),xp(),Lo(),LE();const tD={command:`publish [path]`,description:"Pack + upload the workspace at <path> (default: cwd) to its npm registry. Drop-in for `npm publish` with workspace:^ rewrite handled automatically.",builder:n=>n.positional(`path`,{description:`Workspace path (default: cwd).`,type:`string`}).option(`tag`,{description:`Dist-tag to publish under. Default: latest.`,type:`string`,default:`latest`}).option(`access`,{description:"Package access — `public` or `restricted` (required for first publish of scoped packages on the public registry).",type:`string`}).option(`tolerate-republish`,{description:'Treat "version already published" as success — covers both classic 409 Conflict and the npm OIDC-path 403 Forbidden + `"previously published"` body shape. Matches yarn `--tolerate-republish`.',type:`boolean`,default:!1}).option(`tolerate-untrusted-new`,{description:'Skip (exit 0) when OIDC token exchange returns `package not found` AND no fallback token is configured — i.e. a never-before-published `@scope/<name>` whose Trusted Publisher entry hasn\'t been set up on npmjs.com yet. Without this flag, one un-bootstrapped new package breaks the entire serialized `gjsify foreach publish` loop. Pair with `--tolerate-republish` in CI release workflows so a fresh-merged package gracefully skips its first CI publish, leaving the manual-bootstrap step to a maintainer (see AGENTS.md "New @gjsify/* package: first-publish + Trusted Publisher bootstrap").',type:`boolean`,default:!1}).option(`provenance`,{description:`Pass-through flag — recorded in the payload but no signing happens (gjsify doesn't ship a sigstore signer yet).`,type:`boolean`,default:!1}).option(`dry-run`,{description:`Pack only, do not PUT.`,type:`boolean`,default:!1}).option(`json`,{description:`Emit publish metadata as JSON on stdout.`,type:`boolean`,default:!1}).option(`trusted`,{description:"Authenticate via npm Trusted Publishing (OIDC): exchange the GitHub Actions id-token for a short-lived npm token. Pass `--trusted` to force this mode (errors if env vars missing). Omit to auto-detect: OIDC is used iff `ACTIONS_ID_TOKEN_REQUEST_URL`+`_TOKEN` are set AND no `_authToken` is present in the resolved npmrc; otherwise the long-lived token path is used. Requires the calling workflow to declare `permissions: id-token: write` AND the target package to have a Trusted Publisher configured on npmjs.com.",type:`boolean`,default:void 0}).option(`check-trusted`,{description:"Diagnostic mode: perform the OIDC id-token request + npm token exchange, report success/failure, then exit WITHOUT publishing. Useful as a bulk-verifier (e.g. via `gjsify foreach publish --check-trusted`) to confirm Trusted Publisher config across many packages.",type:`boolean`,default:!1}),handler:async n=>{let a=Co(n.path??process.cwd()),S=n.tag??`latest`,C=n.access,N=n[`tolerate-republish`]===!0,F=n[`tolerate-untrusted-new`]===!0,I=n.provenance===!0,H=n[`dry-run`]===!0,W=n[`check-trusted`]===!0,K=n.trusted,q=!!process.env.GJSIFY_PUBLISH_DEBUG;if(I&&Fe.warn(`gjsify publish: --provenance recorded but not signed (no sigstore integration yet).`),W){let S=Eo(a,`package.json`),C=JSON.parse(readFileSync(S,`utf-8`));if(typeof C.name!=`string`&&(process.stderr.write(`gjsify publish --check-trusted: ${S} has no \`name\` field\n`),process.exit(2)),C.private===!0){let a={ok:!0,action:`check-trusted`,name:C.name,skipped:`private`};n.json?process.stdout.write(`${JSON.stringify(a)}\n`):process.stdout.write(`- ${C.name}: skipped (private package)\n`);return}let N=await loadNpmrc(a),F=process.env.npm_config_registry??registryFor(C.name,N)??IE;try{await getNpmTrustedToken({packageName:C.name,registry:F,log:q?n=>Fe.error(n):void 0});let a={ok:!0,action:`check-trusted`,name:C.name,registry:F};n.json?process.stdout.write(`${JSON.stringify(a)}\n`):process.stdout.write(`✓ ${C.name}: trusted publisher OK\n`);return}catch(a){handleOidcFailure(a,C.name,n.json===!0);return}}let Y=await packWorkspace(a,{dryRun:!0,lifecycleScripts:[`prepublishOnly`,`prepack`]}),X=await packWorkspaceToBytes(a),te=readFileSync(Eo(a,`package.json`),`utf-8`),ne=await loadRewrittenManifest(a,JSON.parse(te));if(H){let a={ok:!0,action:`dry-run`,name:Y.name,version:Y.version,filename:Y.filename,size:Y.size,shasum:Y.shasum,integrity:Y.integrity};n.json?process.stdout.write(`${JSON.stringify(a,null,2)}\n`):process.stdout.write(`+ ${Y.name}@${Y.version} (dry-run, ${Y.size} bytes, ${Y.entryCount} files)\n`);return}let re=await loadNpmrc(a),ie=process.env.npm_config_registry??registryFor(Y.name,re)??IE,ae=ie.endsWith(`/`)?ie.slice(0,-1):ie,oe=`${ae}/${Y.name.startsWith(`@`)?(()=>{let n=Y.name.indexOf(`/`),a=Y.name.slice(1,n),S=Y.name.slice(n+1);return`@${encodeURIComponent(a)}%2f${encodeURIComponent(S)}`})():encodeURIComponent(Y.name)}`,Z=`${Y.name.includes(`/`)?Y.name.slice(Y.name.indexOf(`/`)+1):Y.name}-${Y.version}.tgz`,se=buildPublishPayload({pkg:ne,tag:S,access:C,tarballBytes:X,tarballUrl:`${ae}/${Y.name}/-/${Z}`,packed:{...Y,wireFilename:Z},provenance:I}),ce=buildHeaders(oe,{npmrc:re});ce[`content-type`]=`application/json`,ce.accept=`*/*`;let le=K===!0||K===void 0&&hasGithubOidcEnv()&&!process.env.NODE_AUTH_TOKEN,ue=`token`;if(le)try{let{token:n,audience:a}=await getNpmTrustedToken({packageName:Y.name,registry:ie,log:q?n=>Fe.error(n):void 0});ce.authorization=`Bearer ${n}`,ue=`oidc`,q&&Fe.error(`gjsify publish: OIDC token obtained (audience=${a})`)}catch(a){if(a instanceof OidcExchangeError&&a.status===404&&/package not found/i.test(a.body)&&F){let a=`${Y.name}@${Y.version} (skipped — no Trusted Publisher on npm, see AGENTS.md "New @gjsify/* package: first-publish + Trusted Publisher bootstrap")`;n.json?process.stdout.write(`${JSON.stringify({ok:!0,action:`skipped-untrusted-new`,name:Y.name,version:Y.version,reason:`no-trusted-publisher`},null,2)}\n`):process.stdout.write(`~ ${a}\n`);return}if(K===!0&&(handleOidcFailure(a,Y.name,n.json===!0),process.exit(1)),q){let n=a instanceof Error?a.message:String(a);Fe.error(`gjsify publish: OIDC auto-detect failed (${n}) — falling back to token auth`)}}q&&(Fe.error(`gjsify publish: PUT ${oe} (${Y.name}@${Y.version})`),Fe.error(` auth-mode: ${ue}`),Fe.error(` authorization: ${ce.authorization?`(set)`:`(none)`}`),Fe.error(` payload size: ${JSON.stringify(se).length} bytes`));let de=await fetch(oe,{method:`PUT`,headers:ce,body:JSON.stringify(se)});if(de.ok){let a={ok:!0,name:Y.name,version:Y.version,filename:Y.filename,size:Y.size,integrity:Y.integrity,tag:S,registry:ae};n.json?process.stdout.write(`${JSON.stringify(a,null,2)}\n`):process.stdout.write(`+ ${Y.name}@${Y.version}\n`);return}let fe=await de.text().catch(()=>`<no body>`);if((de.status===409||de.status===403&&/previously published/i.test(fe))&&N){let a={ok:!0,action:`republish-tolerated`,name:Y.name,version:Y.version,status:de.status};n.json?process.stdout.write(`${JSON.stringify(a,null,2)}\n`):process.stdout.write(`= ${Y.name}@${Y.version} (already published, tolerated)\n`);return}Fe.error(`gjsify publish: ${Y.name}@${Y.version} — ${de.status} ${de.statusText}`),Fe.error(fe),process.exit(1)}};async function packWorkspaceToBytes(n){let a=`/tmp/gjsify-publish-${process.pid}-${Date.now()}`,S=await packWorkspace(n,{destination:a,dryRun:!1,lifecycleScripts:[]});if(!S.absolutePath)throw Error(`gjsify publish: pack did not produce a file`);let C=new Uint8Array(readFileSync(S.absolutePath));try{(await Promise.resolve().then(()=>(Ts(),Ss))).rmSync(S.absolutePath)}catch{}try{(await Promise.resolve().then(()=>(Ts(),Ss))).rmdirSync(a)}catch{}return C}async function loadRewrittenManifest(n,a){let S=`/tmp/gjsify-publish-manifest-${process.pid}-${Date.now()}.tgz`,C=await packWorkspace(n,{destination:S.substring(0,S.lastIndexOf(`/`)),dryRun:!1}),{rmSync:N}=await Promise.resolve().then(()=>(Ts(),Ss));if(!C.absolutePath)throw Error(`gjsify publish: pack did not produce a file`);let{gunzip:F,parseTar:I}=await Promise.resolve().then(()=>(HE(),VE)),H=new Uint8Array(readFileSync(C.absolutePath));N(C.absolutePath);let W=await F(H);for(let n of I(W))if(n.name===`package/package.json`&&n.body)return JSON.parse(new TextDecoder().decode(n.body));return a}async function loadNpmrc(n){let a=[],S=Eo(n,`.npmrc`);existsSync(S)&&a.push(readFileSync(S,`utf-8`));let C=process.env.NPM_CONFIG_USERCONFIG;if(C&&existsSync(C))a.push(readFileSync(C,`utf-8`));else{let n=Eo(homedir(),`.npmrc`);existsSync(n)&&a.push(readFileSync(n,`utf-8`))}return parseNpmrc(a.join(`
801
+ `);for(let a of n){let n=a.trim();!n||n.startsWith(`#`)||n.startsWith(`!`)||C.push(globToRegex(n))}}return n=>{for(let a of C)if(a.test(n))return!0;return!1}}function globToRegex(n){let a=n.replace(/^\//,``);return a=a.replace(/[.+^${}()|[\]\\]/g,`\\$&`),a=a.replace(/\*\*/g,`__DOUBLESTAR__`).replace(/\*/g,`[^/]*`).replace(/__DOUBLESTAR__/g,`.*`),a=a.replace(/\?/g,`[^/]`),RegExp(`^${a}($|/)`)}function rewriteWorkspaceDeps(n,a){let S=findWorkspaceRoot(a);if(!S)return n;let C=new Map;for(let n of discoverWorkspaces(S))n.name&&n.version&&C.set(n.name,n.version);let N=JSON.parse(JSON.stringify(n));for(let n of[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`]){let a=N[n];if(a)for(let[n,F]of Object.entries(a)){if(typeof F!=`string`||!F.startsWith(`workspace:`))continue;let I=C.get(n);if(!I)throw Error(`gjsify pack: ${N.name} declares workspace:^ on ${n} but no sibling workspace with that name exists in the monorepo at ${S}`);let H=F.slice(10);H===`*`||H===``?a[n]=I:H===`^`||H===`~`?a[n]=`${H}${I}`:a[n]=H}}return N}function indentOf(n){let a=n.match(/\n([ \t]+)"/);return a?a[1]:` `}var OidcUnavailableError=class extends Error{reason;constructor(n,a){super(n),this.reason=a,this.name=`OidcUnavailableError`}},OidcExchangeError=class extends Error{status;body;packageName;constructor(n,a,S,C){super(n),this.status=a,this.body=S,this.packageName=C,this.name=`OidcExchangeError`}};function hasGithubOidcEnv(){return!!(process.env.ACTIONS_ID_TOKEN_REQUEST_URL&&process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN)}async function fetchGithubOidcToken(n,a){let S=process.env.ACTIONS_ID_TOKEN_REQUEST_URL,C=process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;if(!S||!C)throw new OidcUnavailableError("GitHub Actions OIDC env vars (ACTIONS_ID_TOKEN_REQUEST_{URL,TOKEN}) not set. The calling workflow needs `permissions: id-token: write`.",`no-env`);let N=new URL(S);N.searchParams.set(`audience`,n),a?.(`gjsify oidc: GET ${N.href.replace(C,`<bearer>`)}`);let F=await fetch(N.href,{method:`GET`,headers:{Accept:`application/json`,Authorization:`Bearer ${C}`}});if(!F.ok){let n=await F.text().catch(()=>`<no body>`);throw new OidcUnavailableError(`Failed to fetch GitHub OIDC id_token: ${F.status} ${F.statusText} — ${n.slice(0,200)}`,`fetch-id-token`)}let I=await F.json().catch(()=>({}));if(!I.value)throw new OidcUnavailableError("GitHub OIDC response missing `value` field",`no-id-token`);return I.value}async function exchangeOidcForNpmToken(n){let{packageName:a,registry:S,idToken:C,log:N}=n,F=`${S.endsWith(`/`)?S.slice(0,-1):S}/-/npm/v1/oidc/token/exchange/package/${a.startsWith(`@`)?(()=>{let n=a.indexOf(`/`),S=a.slice(1,n),C=a.slice(n+1);return`@${encodeURIComponent(S)}%2f${encodeURIComponent(C)}`})():encodeURIComponent(a)}`;N?.(`gjsify oidc: POST ${F}`);let I=await fetch(F,{method:`POST`,headers:{Authorization:`Bearer ${C}`,"Content-Type":`application/json`,Accept:`application/json`},body:`{}`}),H=await I.text().catch(()=>``);if(!I.ok)throw new OidcExchangeError(`npm OIDC token exchange failed for ${a}: ${I.status} ${I.statusText} — ${H.slice(0,300)}`,I.status,H,a);let W;try{W=JSON.parse(H)}catch{throw new OidcExchangeError(`npm OIDC token exchange returned non-JSON body for ${a}: ${H.slice(0,200)}`,I.status,H,a)}if(!W.token)throw new OidcExchangeError(`npm OIDC token exchange returned no \`token\` field for ${a}`,I.status,H,a);return W.token}async function getNpmTrustedToken(n){let a=`npm:${new URL(n.registry).hostname}`,S=await fetchGithubOidcToken(a,n.log);return{token:await exchangeOidcForNpmToken({...n,idToken:S}),audience:a}}Ie(),Ts(),xp(),Lo(),LE();const tD={command:`publish [path]`,description:"Pack + upload the workspace at <path> (default: cwd) to its npm registry. Drop-in for `npm publish` with workspace:^ rewrite handled automatically.",builder:n=>n.positional(`path`,{description:`Workspace path (default: cwd).`,type:`string`}).option(`tag`,{description:`Dist-tag to publish under. Default: latest.`,type:`string`,default:`latest`}).option(`access`,{description:"Package access — `public` or `restricted` (required for first publish of scoped packages on the public registry).",type:`string`}).option(`tolerate-republish`,{description:'Treat "version already published" as success — covers both classic 409 Conflict and the npm OIDC-path 403 Forbidden + `"previously published"` body shape. Matches yarn `--tolerate-republish`.',type:`boolean`,default:!1}).option(`tolerate-untrusted-new`,{description:'Skip (exit 0) when OIDC token exchange returns `package not found` AND no fallback token is configured — i.e. a never-before-published `@scope/<name>` whose Trusted Publisher entry hasn\'t been set up on npmjs.com yet. Without this flag, one un-bootstrapped new package breaks the entire serialized `gjsify foreach publish` loop. Pair with `--tolerate-republish` in CI release workflows so a fresh-merged package gracefully skips its first CI publish, leaving the manual-bootstrap step to a maintainer (see AGENTS.md "New @gjsify/* package: first-publish + Trusted Publisher bootstrap").',type:`boolean`,default:!1}).option(`provenance`,{description:`Pass-through flag — recorded in the payload but no signing happens (gjsify doesn't ship a sigstore signer yet).`,type:`boolean`,default:!1}).option(`dry-run`,{description:`Pack only, do not PUT.`,type:`boolean`,default:!1}).option(`json`,{description:`Emit publish metadata as JSON on stdout.`,type:`boolean`,default:!1}).option(`trusted`,{description:"Authenticate via npm Trusted Publishing (OIDC): exchange the GitHub Actions id-token for a short-lived npm token. Pass `--trusted` to force this mode (errors if env vars missing). Omit to auto-detect: OIDC is used iff `ACTIONS_ID_TOKEN_REQUEST_URL`+`_TOKEN` are set AND no `_authToken` is present in the resolved npmrc; otherwise the long-lived token path is used. Requires the calling workflow to declare `permissions: id-token: write` AND the target package to have a Trusted Publisher configured on npmjs.com.",type:`boolean`,default:void 0}).option(`check-trusted`,{description:"Diagnostic mode: perform the OIDC id-token request + npm token exchange, report success/failure, then exit WITHOUT publishing. Useful as a bulk-verifier (e.g. via `gjsify foreach publish --check-trusted`) to confirm Trusted Publisher config across many packages.",type:`boolean`,default:!1}),handler:async n=>{let a=Co(n.path??process.cwd()),S=n.tag??`latest`,C=n.access,N=n[`tolerate-republish`]===!0,F=n[`tolerate-untrusted-new`]===!0,I=n.provenance===!0,H=n[`dry-run`]===!0,W=n[`check-trusted`]===!0,K=n.trusted,q=!!process.env.GJSIFY_PUBLISH_DEBUG;if(I&&Fe.warn(`gjsify publish: --provenance recorded but not signed (no sigstore integration yet).`),W){let S=Eo(a,`package.json`),C=JSON.parse(readFileSync(S,`utf-8`));if(typeof C.name!=`string`&&(process.stderr.write(`gjsify publish --check-trusted: ${S} has no \`name\` field\n`),process.exit(2)),C.private===!0){let a={ok:!0,action:`check-trusted`,name:C.name,skipped:`private`};n.json?process.stdout.write(`${JSON.stringify(a)}\n`):process.stdout.write(`- ${C.name}: skipped (private package)\n`);return}let N=await loadNpmrc(a),F=process.env.npm_config_registry??registryFor(C.name,N)??IE;try{await getNpmTrustedToken({packageName:C.name,registry:F,log:q?n=>Fe.error(n):void 0});let a={ok:!0,action:`check-trusted`,name:C.name,registry:F};n.json?process.stdout.write(`${JSON.stringify(a)}\n`):process.stdout.write(`✓ ${C.name}: trusted publisher OK\n`);return}catch(a){handleOidcFailure(a,C.name,n.json===!0);return}}let Y=await packWorkspace(a,{dryRun:!0,lifecycleScripts:[`prepublishOnly`,`prepack`],lifecycleStdio:n.json?`inherit-stderr`:`inherit`}),X=await packWorkspaceToBytes(a),te=readFileSync(Eo(a,`package.json`),`utf-8`),ne=await loadRewrittenManifest(a,JSON.parse(te));if(H){let a={ok:!0,action:`dry-run`,name:Y.name,version:Y.version,filename:Y.filename,size:Y.size,shasum:Y.shasum,integrity:Y.integrity};n.json?process.stdout.write(`${JSON.stringify(a,null,2)}\n`):process.stdout.write(`+ ${Y.name}@${Y.version} (dry-run, ${Y.size} bytes, ${Y.entryCount} files)\n`);return}let re=await loadNpmrc(a),ie=process.env.npm_config_registry??registryFor(Y.name,re)??IE,ae=ie.endsWith(`/`)?ie.slice(0,-1):ie,oe=`${ae}/${Y.name.startsWith(`@`)?(()=>{let n=Y.name.indexOf(`/`),a=Y.name.slice(1,n),S=Y.name.slice(n+1);return`@${encodeURIComponent(a)}%2f${encodeURIComponent(S)}`})():encodeURIComponent(Y.name)}`,Z=`${Y.name.includes(`/`)?Y.name.slice(Y.name.indexOf(`/`)+1):Y.name}-${Y.version}.tgz`,se=buildPublishPayload({pkg:ne,tag:S,access:C,tarballBytes:X,tarballUrl:`${ae}/${Y.name}/-/${Z}`,packed:{...Y,wireFilename:Z},provenance:I}),ce=buildHeaders(oe,{npmrc:re});ce[`content-type`]=`application/json`,ce.accept=`*/*`;let le=K===!0||K===void 0&&hasGithubOidcEnv()&&!process.env.NODE_AUTH_TOKEN,ue=`token`;if(le)try{let{token:n,audience:a}=await getNpmTrustedToken({packageName:Y.name,registry:ie,log:q?n=>Fe.error(n):void 0});ce.authorization=`Bearer ${n}`,ue=`oidc`,q&&Fe.error(`gjsify publish: OIDC token obtained (audience=${a})`)}catch(a){if(a instanceof OidcExchangeError&&a.status===404&&/package not found/i.test(a.body)&&F){let a=`${Y.name}@${Y.version} (skipped — no Trusted Publisher on npm, see AGENTS.md "New @gjsify/* package: first-publish + Trusted Publisher bootstrap")`;n.json?process.stdout.write(`${JSON.stringify({ok:!0,action:`skipped-untrusted-new`,name:Y.name,version:Y.version,reason:`no-trusted-publisher`},null,2)}\n`):process.stdout.write(`~ ${a}\n`);return}if(K===!0&&(handleOidcFailure(a,Y.name,n.json===!0),process.exit(1)),q){let n=a instanceof Error?a.message:String(a);Fe.error(`gjsify publish: OIDC auto-detect failed (${n}) — falling back to token auth`)}}q&&(Fe.error(`gjsify publish: PUT ${oe} (${Y.name}@${Y.version})`),Fe.error(` auth-mode: ${ue}`),Fe.error(` authorization: ${ce.authorization?`(set)`:`(none)`}`),Fe.error(` payload size: ${JSON.stringify(se).length} bytes`));let de=await fetch(oe,{method:`PUT`,headers:ce,body:JSON.stringify(se)});if(de.ok){let a={ok:!0,name:Y.name,version:Y.version,filename:Y.filename,size:Y.size,integrity:Y.integrity,tag:S,registry:ae};n.json?process.stdout.write(`${JSON.stringify(a,null,2)}\n`):process.stdout.write(`+ ${Y.name}@${Y.version}\n`);return}let fe=await de.text().catch(()=>`<no body>`);if((de.status===409||de.status===403&&/previously published/i.test(fe))&&N){let a={ok:!0,action:`republish-tolerated`,name:Y.name,version:Y.version,status:de.status};n.json?process.stdout.write(`${JSON.stringify(a,null,2)}\n`):process.stdout.write(`= ${Y.name}@${Y.version} (already published, tolerated)\n`);return}Fe.error(`gjsify publish: ${Y.name}@${Y.version} — ${de.status} ${de.statusText}`),Fe.error(fe),process.exit(1)}};async function packWorkspaceToBytes(n){let a=`/tmp/gjsify-publish-${process.pid}-${Date.now()}`,S=await packWorkspace(n,{destination:a,dryRun:!1,lifecycleScripts:[]});if(!S.absolutePath)throw Error(`gjsify publish: pack did not produce a file`);let C=new Uint8Array(readFileSync(S.absolutePath));try{(await Promise.resolve().then(()=>(Ts(),Ss))).rmSync(S.absolutePath)}catch{}try{(await Promise.resolve().then(()=>(Ts(),Ss))).rmdirSync(a)}catch{}return C}async function loadRewrittenManifest(n,a){let S=`/tmp/gjsify-publish-manifest-${process.pid}-${Date.now()}.tgz`,C=await packWorkspace(n,{destination:S.substring(0,S.lastIndexOf(`/`)),dryRun:!1}),{rmSync:N}=await Promise.resolve().then(()=>(Ts(),Ss));if(!C.absolutePath)throw Error(`gjsify publish: pack did not produce a file`);let{gunzip:F,parseTar:I}=await Promise.resolve().then(()=>(HE(),VE)),H=new Uint8Array(readFileSync(C.absolutePath));N(C.absolutePath);let W=await F(H);for(let n of I(W))if(n.name===`package/package.json`&&n.body)return JSON.parse(new TextDecoder().decode(n.body));return a}async function loadNpmrc(n){let a=[],S=Eo(n,`.npmrc`);existsSync(S)&&a.push(readFileSync(S,`utf-8`));let C=process.env.NPM_CONFIG_USERCONFIG;if(C&&existsSync(C))a.push(readFileSync(C,`utf-8`));else{let n=Eo(homedir(),`.npmrc`);existsSync(n)&&a.push(readFileSync(n,`utf-8`))}return parseNpmrc(a.join(`
802
802
  `).replace(/\$\{([A-Z_][A-Z0-9_]*)\}/gi,(n,a)=>process.env[a]??``))}function buildPublishPayload(n){let{pkg:a,tag:S,access:C,tarballBytes:N,tarballUrl:F,packed:I,provenance:H}=n,W={...a,_id:`${I.name}@${I.version}`,dist:{integrity:I.integrity,shasum:I.shasum,tarball:F}};H&&(W._hasShrinkwrap=!1);let K={_id:I.name,name:I.name,description:typeof a.description==`string`?a.description:``,"dist-tags":{[S]:I.version},versions:{[I.version]:W},readme:``,_attachments:{[I.wireFilename]:{content_type:`application/octet-stream`,data:base64Encode(N),length:N.byteLength}}};return C&&(K.access=C),K}function base64Encode(n){let a=``,S=32768;for(let C=0;C<n.length;C+=S)a+=String.fromCharCode(...n.subarray(C,C+S));return btoa(a)}function handleOidcFailure(n,a,S){if(n instanceof OidcUnavailableError){let C=`gjsify publish: OIDC not available — ${n.message}`;S?process.stdout.write(`${JSON.stringify({ok:!1,name:a,error:`oidc-unavailable`,reason:n.reason,message:n.message})}\n`):process.stderr.write(`${C}\n`);return}if(n instanceof OidcExchangeError){let C=n.status===401||n.status===403?`npm rejected the OIDC exchange (${n.status}) — check that ${a} has a Trusted Publisher configured at https://www.npmjs.com/package/${encodeURIComponent(a)}/access pointing at this workflow.`:n.message;S?process.stdout.write(`${JSON.stringify({ok:!1,name:a,error:`oidc-exchange`,status:n.status,body:n.body,message:n.message})}\n`):process.stderr.write(`✗ ${a}: ${C}\n`);return}let C=n instanceof Error?n.message:String(n);S?process.stdout.write(`${JSON.stringify({ok:!1,name:a,error:`unknown`,message:C})}\n`):process.stderr.write(`✗ ${a}: ${C}\n`)}Ie(),Ts(),Lo(),rn(),LE();const nD=`@gjsify/cli`,rD={command:`self-update`,description:`Update the installed ${nD} to the latest release (or pinned --tag).`,builder:n=>n.option(`check`,{description:`Only check whether a newer version is available; do not install.`,type:`boolean`,default:!1}).option(`force`,{description:`Reinstall even when the current version already matches the target tag.`,type:`boolean`,default:!1}).option(`tag`,{description:"npm dist-tag or pinned version to install (e.g. `latest`, `next`, `0.5.0`).",type:`string`,default:`latest`}),handler:async n=>{let a=defaultGlobalLayout(),S=Eo(Eo(a.prefix,`node_modules`,nD),`package.json`),C=readCurrentVersion(),N=existsSync(S);Fe.log(`Current ${nD}: v${C??`(unknown)`}`),N||Fe.warn(`\nWarning: no @gjsify/cli install found under ${a.prefix}.\nself-update only manages installs created by install.mjs or \`gjsify install -g\`.\nIf you installed via \`npm install -g\`, remove that and use:\n curl -fsSL https://github.com/gjsify/gjsify/releases/latest/download/install.mjs -o /tmp/g.mjs && gjs -m /tmp/g.mjs && rm /tmp/g.mjs`),Fe.log(`Fetching dist-tags for ${nD}@${n.tag} ...`);let F;try{F=await fetchPackument(nD)}catch(n){let a=n instanceof Error?n.message:String(n);Fe.error(`Failed to fetch packument: ${a}`),process.exit(1);return}let I=resolveTag(F,n.tag);if(!I){Fe.error(`Unknown dist-tag '${n.tag}' on ${nD}. Known tags: ${Object.keys(F[`dist-tags`]??{}).join(`, `)||`(none)`}`),process.exit(1);return}if(Fe.log(`Latest matching --tag ${n.tag}: v${I}`),C===I&&!n.force){Fe.log(`Already up to date (v${I}).`),n.check||Fe.log(`Run with --force to reinstall anyway.`);return}if(n.check){Fe.log(C?`Update available: v${C} → v${I}`:`Install required: → v${I}`),process.exit(1);return}Fe.log(`Installing ${nD}@${I} ...`),await installPackages({prefix:a.prefix,specs:[`${nD}@${I}`],verbose:!1});let H=linkGlobalBins([nD],a);if(H.length===0)Fe.warn("self-update: install completed but no bins were linked — package.json may be missing a `bin` field.");else for(let n of H)Fe.log(` • ${n.link} → ${n.target}`);Fe.log(`\nUpdated ${nD} to v${I}.`)}};function readCurrentVersion(){try{let n=ko(Co(fileURLToPath(import.meta.url)));for(let a=0;a<8&&n!==ko(n);a++){let a=Eo(n,`package.json`);if(existsSync(a)){let n=JSON.parse(readFileSync(a,`utf-8`));if(n.name===nD&&typeof n.version==`string`)return n.version}n=ko(n)}}catch{}return null}function resolveTag(n,a){let S=n[`dist-tags`]??{};return S[a]?S[a]:n.versions&&typeof n.versions==`object`&&n.versions[a]?a:null}Ie(),Ts(),Lo();function loadInstallerTemplate(){return readFileSync(new URL(`../templates/install.mjs.tmpl`,import.meta.url),`utf-8`)}const iD={command:`generate-installer [target]`,description:`Scaffold an install.mjs in the current directory for a GJS-runnable npm package.`,builder:n=>n.positional(`target`,{description:`Npm package name to install (default: current package.json name).`,type:`string`}).option(`bin-name`,{description:"Bin name produced by the installer (default: first key of `gjsify.bin` or `bin`).",type:`string`}).option(`bootstrap-url`,{description:`Override the cli.gjs.mjs bootstrap bundle URL (default: gjsify GitHub releases/latest).`,type:`string`}).option(`output`,{description:`Where to write the generated installer.`,type:`string`,default:`install.mjs`}).option(`force`,{description:`Overwrite an existing output file.`,type:`boolean`,default:!1}),handler:n=>{let a=Co(process.cwd(),n.output);if(existsSync(a)&&!n.force){Fe.error(`${n.output} already exists. Re-run with --force to overwrite.`),process.exit(1);return}let S=Co(process.cwd(),`package.json`),C=null;if(existsSync(S))try{C=JSON.parse(readFileSync(S,`utf-8`))}catch{}let N=n.target??C?.name;if(!N){Fe.error("No target package: pass `gjsify generate-installer <pkg>` or run inside a directory with a package.json."),process.exit(1);return}let F=n[`bin-name`]??pickDefaultBinName(C,N),I=n[`bootstrap-url`]??`https://github.com/gjsify/gjsify/releases/latest/download/cli.gjs.mjs`;writeFileSync(a,loadInstallerTemplate().replace(/const DEFAULT_TARGET = '[^']+';/,`const DEFAULT_TARGET = ${JSON.stringify(N)};`).replace(/const DEFAULT_BIN_NAME = '[^']+';/,`const DEFAULT_BIN_NAME = ${JSON.stringify(F)};`).replace(/const DEFAULT_BOOTSTRAP_URL =\s*'[^']+';/,`const DEFAULT_BOOTSTRAP_URL = ${JSON.stringify(I)};`),{mode:493}),Fe.log(`Wrote ${n.output} (target=${N}, bin=${F}).`),Fe.log(``),Fe.log(`Install one-liner for your README:`),Fe.log(` curl -fsSL https://github.com/<you>/<repo>/raw/main/${n.output} -o /tmp/i.mjs \\`),Fe.log(` && gjs -m /tmp/i.mjs && rm /tmp/i.mjs`)}};function pickDefaultBinName(n,a){let S=n?.gjsify?.bin;if(S&&typeof S==`object`){let n=Object.keys(S)[0];if(n)return n}let C=n?.bin;if(C&&typeof C==`object`){let n=Object.keys(C)[0];if(n)return n}return a.startsWith(`@`)?a.slice(a.indexOf(`/`)+1):a}Ie(),Ts(),Lo();const aD={command:`uninstall <packages..>`,description:"Uninstall a previously installed package. Currently only `--global` mode is supported.",builder:n=>n.positional(`packages`,{description:`Package(s) to uninstall (npm names, optionally with version).`,type:`string`,array:!0,demandOption:!0}).option(`global`,{description:`Uninstall from the user-global XDG location (the install -g target).`,type:`boolean`,alias:`g`,default:!1}).option(`dry-run`,{description:`Show what would be removed without touching the filesystem.`,type:`boolean`,default:!1}).option(`verbose`,{description:`Verbose logging.`,type:`boolean`,default:!1}),handler:n=>{if(!n.global){Fe.error("gjsify uninstall currently only supports --global. For project-local removal, edit package.json + re-run `gjsify install`."),process.exit(1);return}let a=defaultGlobalLayout(),S=n[`dry-run`]??!1,C=n.verbose??!1,N=`gjsify uninstall${S?` (dry-run)`:``} --global`;Fe.log(`${N} ← ${a.prefix}`),Fe.log(`${` `.repeat(N.length)} bins ← ${a.binDir}`);let F=!1;for(let N of n.packages){let n=specToPackageName(N),I=Eo(a.prefix,`node_modules`,n);if(!existsSync(I)){Fe.warn(` ✗ ${n} — not installed at ${I}`);continue}let H=findBinShimsForPackage(a.binDir,I,C);if(S){Fe.log(` • would remove ${I}`);for(let n of H)Fe.log(` • would remove ${n}`)}else{rmSync(I,{recursive:!0,force:!0}),Fe.log(` • removed ${I}`);for(let n of H)unlinkSync(n),Fe.log(` • removed ${n}`)}F=!0}F||(Fe.error(`
803
803
  No packages removed.`),process.exit(1))}};function findBinShimsForPackage(n,a,S){if(!existsSync(n))return[];let C=[],N;try{N=readdirSync(n)}catch{return[]}for(let F of N){let N=Eo(n,F);try{if(!statSync(N).isFile())continue;let n=readFileSync(N,`utf-8`);if(!n.startsWith(`#!/bin/sh`))continue;let S=n.split(`
804
804
  `).find(n=>/^exec (?:gjs -m )?'/.test(n));if(!S)continue;let F=S.match(/'([^']+)'/);if(!F)continue;let I=F[1];(I.startsWith(a+`/`)||I===a)&&C.push(N)}catch(n){S&&Fe.warn(` ? could not inspect ${N}: ${n.message}`)}}return C}Ie(),Ts(),Lo();const oD={command:`format [paths..]`,description:`Format source files via Biome (native binary spawn — no Node launcher).`,builder:n=>n.positional(`paths`,{description:"Files or directories to format. Default: `.`",type:`string`,array:!0}).option(`write`,{description:`Modify files in place (default: stdout / report).`,type:`boolean`,default:!1}).option(`check`,{description:`Report formatting drift without modifying files; exit non-zero if any file is unformatted. Useful for CI.`,type:`boolean`,default:!1}).option(`config-path`,{description:`Path to a biome.json. Default: walks up from cwd to find one.`,type:`string`,normalize:!0}).option(`init`,{description:`Write a recommended biome.json into cwd (skips if one exists; --force to overwrite).`,type:`boolean`,default:!1}).option(`force`,{description:`Overwrite an existing biome.json with --init.`,type:`boolean`,default:!1}).option(`verbose`,{description:`Echo the resolved biome binary + args before spawning.`,type:`boolean`,default:!1}),handler:async n=>{let a=process.cwd();if(n.init){await handleInit({cwd:a,force:n.force??!1});return}let S=n.paths?.length?n.paths:[`.`],C=[`format`];n.write&&!n.check&&C.push(`--write`);let N=n.configPath??findBiomeConfig(a)??void 0;N&&C.push(`--config-path=${Co(N,`..`)}`),C.push(...S);try{let S=await runBiome(C,{cwd:a,verbose:n.verbose});process.exitCode=S}catch(n){if(n instanceof BiomeNotFoundError){printBiomeNotFound(n),process.exitCode=1;return}throw n}}};async function handleInit({cwd:n,force:a}){let S=Co(n,`biome.json`);if(existsSync(S)&&!a){Fe.log(`[gjsify format] biome.json exists at ${S} — pass --force to overwrite.`),process.exitCode=0;return}writeFileSync(S,loadBiomeTemplate(),`utf-8`),Fe.log(`[gjsify format] wrote ${S}`),Fe.log("[gjsify format] Run `gjsify format --write .` to apply the formatter to the project.")}Lo();const sD={command:`lint [paths..]`,description:`Run Biome lint diagnostics (native binary spawn — no Node launcher).`,builder:n=>n.positional(`paths`,{description:"Files or directories to lint. Default: `.`",type:`string`,array:!0}).option(`write`,{description:`Apply safe lint fixes in place.`,type:`boolean`,default:!1}).option(`config-path`,{description:`Path to a biome.json. Default: walks up from cwd to find one.`,type:`string`,normalize:!0}).option(`verbose`,{description:`Echo the resolved biome binary + args before spawning.`,type:`boolean`,default:!1}),handler:async n=>{let a=process.cwd(),S=n.paths?.length?n.paths:[`.`],C=[`lint`];n.write&&C.push(`--write`);let N=n.configPath??findBiomeConfig(a)??void 0;N&&C.push(`--config-path=${Co(N,`..`)}`),C.push(...S);try{let S=await runBiome(C,{cwd:a,verbose:n.verbose});process.exitCode=S}catch(n){if(n instanceof BiomeNotFoundError){printBiomeNotFound(n),process.exitCode=1;return}throw n}}};Lo();const cD={command:`fix [paths..]`,description:`Run Biome check --write — format + safe-lint-fix + organize-imports in one pass.`,builder:n=>n.positional(`paths`,{description:"Files or directories to fix. Default: `.`",type:`string`,array:!0}).option(`write`,{description:`Apply fixes in place (default: true). Pass --no-write to report only.`,type:`boolean`,default:!0}).option(`config-path`,{description:`Path to a biome.json. Default: walks up from cwd to find one.`,type:`string`,normalize:!0}).option(`verbose`,{description:`Echo the resolved biome binary + args before spawning.`,type:`boolean`,default:!1}),handler:async n=>{let a=process.cwd(),S=n.paths?.length?n.paths:[`.`],C=[`check`];n.write!==!1&&C.push(`--write`);let N=n.configPath??findBiomeConfig(a)??void 0;N&&C.push(`--config-path=${Co(N,`..`)}`),C.push(...S);try{let S=await runBiome(C,{cwd:a,verbose:n.verbose});process.exitCode=S}catch(n){if(n instanceof BiomeNotFoundError){printBiomeNotFound(n),process.exitCode=1;return}throw n}}};ps();var Interface=class extends Interface$1{question(n,a){return new Promise(a=>{super.question(n,a)})}};function createInterface(n,a){return typeof n==`object`&&n&&!(`read`in n&&typeof n.read==`function`)?new Interface(n):new Interface({input:n,output:a})}Ie(),Ts(),Lo(),xp(),FE(),LE();const lD={command:`upgrade`,description:"Check the npm registry for newer versions of declared dependencies and update package.json. Interactive by default; `--latest` / `--minor` / `--patch` switch to non-interactive bulk-update mode.",builder:n=>n.option(`latest`,{description:`Non-interactive: bump every dependency to its latest version (allows major).`,type:`boolean`,default:!1}).option(`minor`,{description:`Non-interactive: bump every dependency to the latest within the same major (semver-minor + semver-patch).`,type:`boolean`,default:!1}).option(`patch`,{description:`Non-interactive: bump every dependency to the latest within the same minor (semver-patch only).`,type:`boolean`,default:!1}).option(`filter`,{description:`Only consider packages whose name matches this substring (case-insensitive). Repeatable; comma-separated values are split.`,type:`string`}).option(`dry-run`,{description:`Print the upgrade plan without writing package.json.`,type:`boolean`,default:!1}).option(`cwd`,{description:`Project directory. Default: process.cwd().`,type:`string`}).option(`yes`,{alias:`y`,description:`Interactive mode: select all without prompting.`,type:`boolean`,default:!1}).option(`verbose`,{description:`Print extra resolution details.`,type:`boolean`,default:!1}),handler:async n=>{let a=Co(n.cwd??process.cwd()),S=Eo(a,`package.json`);if(!existsSync(S))throw Error(`[gjsify upgrade] no package.json at ${S}`);let C=readFileSync(S,`utf-8`),N=JSON.parse(C),F=collectExternalDeps(N,n.filter?n.filter.split(`,`).map(n=>n.trim().toLowerCase()).filter(Boolean):[]);if(F.length===0){Fe.log(`[gjsify upgrade] no external npm dependencies to check.`);return}let I=await loadNpmrcLight(a),H=n.latest?`latest`:n.minor?`minor`:n.patch?`patch`:`interactive`;Fe.log(`[gjsify upgrade] checking ${F.length} dependencies against ${I.registry}…`);let W=await resolveCandidates(F,I,n.verbose??!1,H);if(W.length===0){Fe.log(`✅ all dependencies are up to date`);return}printTable(W);let K;if(H===`interactive`&&!n.yes?K=await promptSelection(W):(n.yes&&H===`interactive`&&Fe.log(`[gjsify upgrade] -y / --yes: selecting all`),K=W),K.length===0){Fe.log(`[gjsify upgrade] nothing selected; package.json unchanged.`);return}if(n.dryRun){Fe.log(`[gjsify upgrade] --dry-run: would update ${K.length} dependencies (no write).`);return}writePackageJson(S,C,N,K),Fe.log(`✏️ updated ${K.length} dependencies in ${S}. Run \`gjsify install\` to apply.`)}},uD=[`dependencies`,`devDependencies`,`optionalDependencies`,`peerDependencies`];function collectExternalDeps(n,a){let S=[];for(let C of uD){let N=n[C];if(!(!N||typeof N!=`object`))for(let[n,F]of Object.entries(N)){if(typeof F!=`string`||a.length&&!a.some(a=>n.toLowerCase().includes(a))||F.startsWith(`workspace:`)||F.startsWith(`file:`)||F.startsWith(`link:`)||F.startsWith(`git+`)||F.startsWith(`git:`)||F.startsWith(`http`)||F.startsWith(`npm:`)||F===`*`||F===`latest`)continue;let{prefix:N,version:I}=splitRange(F);S.push({name:n,field:C,currentRange:F,currentVersion:I,prefix:N})}}return S}function splitRange(n){let a=n.match(/^(\^|~|>=|<=|>|<|=)?\s*([0-9].*)$/);if(!a)return{prefix:``,version:null};let S=a[1]??``,C=a[2]?.split(/\s|[|&,]/)[0]??null;return{prefix:S,version:(C?parse(C):null)?.version??null}}async function resolveCandidates(n,a,S,C){let N=[],F=0;async function worker(){for(;;){let I=F++;if(I>=n.length)return;let H=n[I];try{let n=(await fetchPackument(H.name,{npmrc:a}))[`dist-tags`]?.latest;if(!n){S&&Fe.warn(` ${H.name}: no dist-tags.latest, skipping`);continue}if(!H.currentVersion){S&&Fe.warn(` ${H.name}: unable to parse current range "${H.currentRange}"`);continue}let F=classifyDiff(H.currentVersion,n);if(F===`none`||C===`minor`&&F===`major`||C===`patch`&&(F===`major`||F===`minor`))continue;N.push({...H,latestVersion:n,diff:F})}catch(n){S&&Fe.warn(` ${H.name}: fetch failed (${n.message})`)}}}return await Promise.all(Array.from({length:8},()=>worker())),N.sort((n,a)=>n.name.localeCompare(a.name)),N}function classifyDiff(n,a){let S=parse(n),C=parse(a);return!S||!C?`none`:S.major===C.major?S.minor===C.minor?S.patch===C.patch?(S.prerelease??[]).join(`.`)===(C.prerelease??[]).join(`.`)?`none`:`prerelease`:C.patch>S.patch?`patch`:`none`:C.minor>S.minor?`minor`:`none`:C.major>S.major?`major`:`none`}const dD={reset:`\x1B[0m`,bold:`\x1B[1m`,dim:`\x1B[2m`,red:`\x1B[31m`,yellow:`\x1B[33m`,green:`\x1B[32m`,cyan:`\x1B[36m`};function colorForDiff(n){switch(n){case`major`:return dD.red;case`minor`:return dD.yellow;case`patch`:return dD.green;case`prerelease`:return dD.cyan;default:return``}}function printTable(n){let a=Math.max(...n.map(n=>n.name.length),4),S=Math.max(...n.map(n=>n.currentRange.length),7),C=Math.max(...n.map(n=>n.latestVersion.length),6),N=String(n.length).length+2,F=` `.repeat(N)+dD.bold+`name`.padEnd(a)+` `+`current`.padEnd(S)+` `+`latest`.padEnd(C)+` kind`+dD.reset;Fe.log(F),Fe.log(` `.repeat(N)+dD.dim+`─`.repeat(a+S+C+12)+dD.reset);for(let F=0;F<n.length;F++){let I=n[F],H=`${F+1}.`.padEnd(N),W=colorForDiff(I.diff);Fe.log(H+I.name.padEnd(a)+` `+dD.dim+I.currentRange.padEnd(S)+dD.reset+` `+W+I.latestVersion.padEnd(C)+dD.reset+` `+W+I.diff+dD.reset)}}async function promptSelection(n){if(!process.stdin.isTTY)return Fe.log(`[gjsify upgrade] non-TTY stdin: pass --latest / --minor / --patch (or --yes for interactive-all) to upgrade non-interactively.`),[];let a=createInterface({input:process.stdin,output:process.stdout});try{Fe.log(`
@@ -46,6 +46,14 @@ export interface PackWorkspaceOptions {
46
46
  * workflow has already produced the build artifacts.
47
47
  */
48
48
  lifecycleScripts?: readonly string[];
49
+ /**
50
+ * Stdio mode for lifecycle scripts. Default `'inherit'` — child output
51
+ * appears in the parent's terminal. Pass `'inherit-stderr'` to redirect
52
+ * the child's stdout → parent's stderr; used by `gjsify pack --json`
53
+ * and `gjsify publish` so the parent's stdout stays a clean
54
+ * machine-readable JSON stream.
55
+ */
56
+ lifecycleStdio?: 'inherit' | 'inherit-stderr' | 'pipe' | 'ignore';
49
57
  }
50
58
  /**
51
59
  * Programmatic equivalent of the `pack` command — used by `gjsify publish`
@@ -65,6 +65,11 @@ export const packCommand = {
65
65
  destination: args['pack-destination'],
66
66
  dryRun: args['dry-run'] === true,
67
67
  lifecycleScripts: args['ignore-scripts'] ? [] : ['prepack'],
68
+ // When emitting machine-readable JSON on stdout, the prepack
69
+ // script's chatty log lines must NOT land on the parent's
70
+ // stdout — `JSON.parse(stdout)` callers would otherwise fail
71
+ // with `Unexpected token …`. Route lifecycle output to stderr.
72
+ lifecycleStdio: args.json ? 'inherit-stderr' : 'inherit',
68
73
  });
69
74
  if (args.json) {
70
75
  process.stdout.write(`${JSON.stringify([result], null, 2)}\n`);
@@ -99,7 +104,10 @@ export async function packWorkspace(wsDir, opts = {}) {
99
104
  // post-install. Matches `npm pack` / `npm publish` semantics.
100
105
  const lifecycleScripts = opts.lifecycleScripts ?? ['prepack'];
101
106
  for (const scriptName of lifecycleScripts) {
102
- await runLifecycleScript(wsDir, pkg, scriptName, { optional: true });
107
+ await runLifecycleScript(wsDir, pkg, scriptName, {
108
+ optional: true,
109
+ stdio: opts.lifecycleStdio,
110
+ });
103
111
  }
104
112
  // Re-read package.json AFTER lifecycle scripts in case one of them
105
113
  // mutated it (e.g. a `prepack` that injects build metadata into
@@ -159,6 +159,10 @@ export const publishCommand = {
159
159
  const packOpts = {
160
160
  dryRun: true,
161
161
  lifecycleScripts: ['prepublishOnly', 'prepack'],
162
+ // `gjsify publish --json` emits the publish summary on stdout.
163
+ // Lifecycle scripts must not pollute that stream with their
164
+ // own log lines; redirect their stdout → parent's stderr.
165
+ lifecycleStdio: args.json ? 'inherit-stderr' : 'inherit',
162
166
  };
163
167
  const packed = await packWorkspace(wsDir, packOpts);
164
168
  // We need the raw bytes — re-run with destination=null and capture.
@@ -1,8 +1,15 @@
1
1
  export interface RunLifecycleScriptOptions {
2
2
  /** When true, do not throw on missing scripts — return `false` instead. */
3
3
  optional?: boolean;
4
- /** Stdio inheritance. Defaults to `'inherit'` so output goes to the parent. */
5
- stdio?: 'inherit' | 'pipe' | 'ignore';
4
+ /**
5
+ * Stdio inheritance for the spawned script. Default `'inherit'` so output
6
+ * appears in the parent's terminal. Pass `'inherit-stderr'` to mirror
7
+ * inheritance but redirect the child's stdout → parent's stderr — used by
8
+ * `gjsify pack --json` and `gjsify publish` so the parent's stdout stays
9
+ * a clean JSON stream (script log lines would otherwise corrupt the
10
+ * machine-readable output that callers `JSON.parse`).
11
+ */
12
+ stdio?: 'inherit' | 'inherit-stderr' | 'pipe' | 'ignore';
6
13
  /** Extra environment variables layered on top of the defaults. */
7
14
  env?: Record<string, string>;
8
15
  }
@@ -54,11 +54,20 @@ export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
54
54
  npm_package_version: pkg.version ?? '',
55
55
  ...(opts.env ?? {}),
56
56
  };
57
+ // `'inherit-stderr'` is our extension on top of node's stdio modes —
58
+ // child stdin inherits, child stdout → parent's stderr (fd 2), child
59
+ // stderr → parent's stderr. Used by `gjsify pack --json` so the
60
+ // prepack's log lines don't get interleaved with the JSON we emit on
61
+ // parent stdout. `spawn`'s `stdio` accepts numeric fds in array form
62
+ // and routes the child's matching stream to that fd.
63
+ const stdioConfig = opts.stdio === 'inherit-stderr'
64
+ ? ['inherit', 2, 2]
65
+ : (opts.stdio ?? 'inherit');
57
66
  await new Promise((resolveOk, reject) => {
58
67
  const child = spawn(literal, [], {
59
68
  cwd: wsDir,
60
69
  env: env,
61
- stdio: opts.stdio ?? 'inherit',
70
+ stdio: stdioConfig,
62
71
  shell: true,
63
72
  });
64
73
  child.on('close', (code) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.4.22",
3
+ "version": "0.4.23",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -120,18 +120,18 @@
120
120
  "cli"
121
121
  ],
122
122
  "dependencies": {
123
- "@gjsify/buffer": "^0.4.22",
124
- "@gjsify/create-app": "^0.4.22",
125
- "@gjsify/node-globals": "^0.4.22",
126
- "@gjsify/node-polyfills": "^0.4.22",
127
- "@gjsify/npm-registry": "^0.4.22",
128
- "@gjsify/resolve-npm": "^0.4.22",
129
- "@gjsify/rolldown-plugin-gjsify": "^0.4.22",
130
- "@gjsify/rolldown-plugin-pnp": "^0.4.22",
131
- "@gjsify/semver": "^0.4.22",
132
- "@gjsify/tar": "^0.4.22",
133
- "@gjsify/web-polyfills": "^0.4.22",
134
- "@gjsify/workspace": "^0.4.22",
123
+ "@gjsify/buffer": "^0.4.23",
124
+ "@gjsify/create-app": "^0.4.23",
125
+ "@gjsify/node-globals": "^0.4.23",
126
+ "@gjsify/node-polyfills": "^0.4.23",
127
+ "@gjsify/npm-registry": "^0.4.23",
128
+ "@gjsify/resolve-npm": "^0.4.23",
129
+ "@gjsify/rolldown-plugin-gjsify": "^0.4.23",
130
+ "@gjsify/rolldown-plugin-pnp": "^0.4.23",
131
+ "@gjsify/semver": "^0.4.23",
132
+ "@gjsify/tar": "^0.4.23",
133
+ "@gjsify/web-polyfills": "^0.4.23",
134
+ "@gjsify/workspace": "^0.4.23",
135
135
  "cosmiconfig": "^9.0.1",
136
136
  "get-tsconfig": "^4.14.0",
137
137
  "pkg-types": "^2.3.1",
@@ -139,12 +139,12 @@
139
139
  "yargs": "^18.0.0"
140
140
  },
141
141
  "devDependencies": {
142
- "@gjsify/unit": "^0.4.22",
142
+ "@gjsify/unit": "^0.4.23",
143
143
  "@types/yargs": "^17.0.35",
144
144
  "typescript": "^6.0.3"
145
145
  },
146
146
  "peerDependencies": {
147
- "@gjsify/rolldown-native": "^0.4.22"
147
+ "@gjsify/rolldown-native": "^0.4.23"
148
148
  },
149
149
  "peerDependenciesMeta": {
150
150
  "@gjsify/rolldown-native": {