@gjsify/cli 0.4.10 → 0.4.11
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 +2 -1
- package/lib/commands/index.d.ts +1 -0
- package/lib/commands/index.js +1 -0
- package/lib/commands/uninstall.d.ts +9 -0
- package/lib/commands/uninstall.js +145 -0
- package/lib/index.js +2 -1
- package/package.json +15 -15
package/dist/cli.gjs.mjs
CHANGED
|
@@ -792,4 +792,5 @@ jobs:
|
|
|
792
792
|
`)})}Be(),gx();const wE={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||(ze.error(`gjsify workspace: no workspace named "${n.name}" — discovered ${a.length} workspace(s)`),process.exit(1)),typeof(S.manifest.scripts??{})[n.script]!=`string`&&(ze.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]:[]];await new Promise((n,a)=>{let F=spawn(C,N,{cwd:S.location,stdio:`inherit`,env:process.env});F.on(`close`,S=>{S===0?n():a(Error(`${C} ${N.join(` `)} exited with code ${S}`))}),F.on(`error`,a)}).catch(n=>{ze.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`}Be(),Rs(),Ma(),qo(),mE();const TE={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}),handler:async n=>{let a=await packWorkspace(No(n.path??process.cwd()),{destination:n[`pack-destination`],dryRun:n[`dry-run`]===!0});n.json?process.stdout.write(`${JSON.stringify([a],null,2)}\n`):process.stdout.write(`${a.filename}\n`)}};async function packWorkspace(n,a={}){let S=Io(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.skipWorkspaceRewrite?N:rewriteWorkspaceDeps(N,n),W=JSON.stringify(H,null,indentOf(C))+`
|
|
793
793
|
`,K=collectFiles(n,N),q=[{name:`package/`,directory:!0,mode:493}],Y=[],X=0;for(let a of K){let S;S=a===`package.json`?new TextEncoder().encode(W):new Uint8Array(readFileSync(Io(n,a)));let C=statSync(Io(n,a)).mode&511;q.push({name:`package/${a}`,body:S,mode:C,mtime:0}),Y.push({path:a,size:S.byteLength,mode:C}),X+=S.byteLength}let te=await gzip(createTarball(q)),ne=`${F.startsWith(`@`)?F.slice(1).replace(`/`,`-`):F}-${I}.tgz`,re=createHash(`sha1`).update(te).digest(`hex`),ie=`sha512-${createHash(`sha512`).update(te).digest(`base64`)}`,ae=a.destination?No(a.destination):n,oe=Io(ae,ne);return a.dryRun||(mkdirSync(ae,{recursive:!0}),writeFileSync(oe,te)),{filename:ne,name:F,version:I,size:te.byteLength,unpackedSize:X,shasum:re,integrity:ie,entryCount:Y.length,files:Y,absolutePath:a.dryRun?null:oe}}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(Io(n,a))&&I.add(a);return[...I].sort()}const EE=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?Io(n,a):n,N;try{N=readdirSync(C,{withFileTypes:!0})}catch{return S}for(let C of N){if(EE.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=Io(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)&&ze.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=Io(n,`.npmignore`),S=Io(n,`.gitignore`),C=[],N=existsSync(a)?a:existsSync(S)?S:null;if(N){let n=readFileSync(N,`utf-8`).split(`
|
|
794
794
|
`);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]:` `}Be(),Rs(),op(),qo(),lE();const DE={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 a 409 conflict (version already published) as success. Matches yarn `--tolerate-republish`.",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}),handler:async n=>{let a=No(n.path??process.cwd()),S=n.tag??`latest`,C=n.access,N=n[`tolerate-republish`]===!0,F=n.provenance===!0,I=n[`dry-run`]===!0;F&&ze.warn(`gjsify publish: --provenance recorded but not signed (no sigstore integration yet).`);let H=await packWorkspace(a,{dryRun:!0}),W=await packWorkspaceToBytes(a),K=readFileSync(Io(a,`package.json`),`utf-8`),q=await loadRewrittenManifest(a,JSON.parse(K));if(I){let a={ok:!0,action:`dry-run`,name:H.name,version:H.version,filename:H.filename,size:H.size,shasum:H.shasum,integrity:H.integrity};n.json?process.stdout.write(`${JSON.stringify(a,null,2)}\n`):process.stdout.write(`+ ${H.name}@${H.version} (dry-run, ${H.size} bytes, ${H.entryCount} files)\n`);return}let Y=await loadNpmrc(a),X=process.env.npm_config_registry??registryFor(H.name,Y)??cE,te=X.endsWith(`/`)?X.slice(0,-1):X,ne=`${te}/${H.name.startsWith(`@`)?(()=>{let n=H.name.indexOf(`/`),a=H.name.slice(1,n),S=H.name.slice(n+1);return`@${encodeURIComponent(a)}%2f${encodeURIComponent(S)}`})():encodeURIComponent(H.name)}`,re=`${H.name.includes(`/`)?H.name.slice(H.name.indexOf(`/`)+1):H.name}-${H.version}.tgz`,ie=buildPublishPayload({pkg:q,tag:S,access:C,tarballBytes:W,tarballUrl:`${te}/${H.name}/-/${re}`,packed:{...H,wireFilename:re},provenance:F}),ae=buildHeaders(ne,{npmrc:Y});ae[`content-type`]=`application/json`,ae.accept=`*/*`,process.env.GJSIFY_PUBLISH_DEBUG&&(ze.error(`gjsify publish: PUT ${ne} (${H.name}@${H.version})`),ze.error(` authorization: ${ae.authorization?`(set)`:`(none)`}`),ze.error(` payload size: ${JSON.stringify(ie).length} bytes`));let oe=await fetch(ne,{method:`PUT`,headers:ae,body:JSON.stringify(ie)});if(oe.ok){let a={ok:!0,name:H.name,version:H.version,filename:H.filename,size:H.size,integrity:H.integrity,tag:S,registry:te};n.json?process.stdout.write(`${JSON.stringify(a,null,2)}\n`):process.stdout.write(`+ ${H.name}@${H.version}\n`);return}let Z=await oe.text().catch(()=>`<no body>`);if(oe.status===409&&N){let a={ok:!0,action:`republish-tolerated`,name:H.name,version:H.version,status:409};n.json?process.stdout.write(`${JSON.stringify(a,null,2)}\n`):process.stdout.write(`= ${H.name}@${H.version} (already published, tolerated)\n`);return}ze.error(`gjsify publish: ${H.name}@${H.version} — ${oe.status} ${oe.statusText}`),ze.error(Z),process.exit(1)}};async function packWorkspaceToBytes(n){let a=`/tmp/gjsify-publish-${process.pid}-${Date.now()}`,S=await packWorkspace(n,{destination:a,dryRun:!1});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(()=>(Rs(),Fs))).rmSync(S.absolutePath)}catch{}try{(await Promise.resolve().then(()=>(Rs(),Fs))).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(()=>(Rs(),Fs));if(!C.absolutePath)throw Error(`gjsify publish: pack did not produce a file`);let{gunzip:F,parseTar:I}=await Promise.resolve().then(()=>(mE(),pE)),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=Io(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=Io(homedir(),`.npmrc`);existsSync(n)&&a.push(readFileSync(n,`utf-8`))}return parseNpmrc(a.join(`
|
|
795
|
-
`).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)}Be(),Rs(),qo(),tn(),lE();const OE=`@gjsify/cli`,kE={command:`self-update`,description:`Update the installed ${OE} 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=Io(Io(a.prefix,`node_modules`,OE),`package.json`),C=readCurrentVersion(),N=existsSync(S);ze.log(`Current ${OE}: v${C??`(unknown)`}`),N||ze.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`),ze.log(`Fetching dist-tags for ${OE}@${n.tag} ...`);let F;try{F=await fetchPackument(OE)}catch(n){let a=n instanceof Error?n.message:String(n);ze.error(`Failed to fetch packument: ${a}`),process.exit(1);return}let I=resolveTag(F,n.tag);if(!I){ze.error(`Unknown dist-tag '${n.tag}' on ${OE}. Known tags: ${Object.keys(F[`dist-tags`]??{}).join(`, `)||`(none)`}`),process.exit(1);return}if(ze.log(`Latest matching --tag ${n.tag}: v${I}`),C===I&&!n.force){ze.log(`Already up to date (v${I}).`),n.check||ze.log(`Run with --force to reinstall anyway.`);return}if(n.check){ze.log(C?`Update available: v${C} → v${I}`:`Install required: → v${I}`),process.exit(1);return}ze.log(`Installing ${OE}@${I} ...`),await installPackages({prefix:a.prefix,specs:[`${OE}@${I}`],verbose:!1});let H=linkGlobalBins([OE],a);if(H.length===0)ze.warn("self-update: install completed but no bins were linked — package.json may be missing a `bin` field.");else for(let n of H)ze.log(` • ${n.link} → ${n.target}`);ze.log(`\nUpdated ${OE} to v${I}.`)}};function readCurrentVersion(){try{let n=zo(No(fileURLToPath(import.meta.url)));for(let a=0;a<8&&n!==zo(n);a++){let a=Io(n,`package.json`);if(existsSync(a)){let n=JSON.parse(readFileSync(a,`utf-8`));if(n.name===OE&&typeof n.version==`string`)return n.version}n=zo(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}Be(),Rs(),qo();function loadInstallerTemplate(){return readFileSync(new URL(`../templates/install.mjs.tmpl`,import.meta.url),`utf-8`)}const AE={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=No(process.cwd(),n.output);if(existsSync(a)&&!n.force){ze.error(`${n.output} already exists. Re-run with --force to overwrite.`),process.exit(1);return}let S=No(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){ze.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}),ze.log(`Wrote ${n.output} (target=${N}, bin=${F}).`),ze.log(``),ze.log(`Install one-liner for your README:`),ze.log(` curl -fsSL https://github.com/<you>/<repo>/raw/main/${n.output} -o /tmp/i.mjs \\`),ze.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}
|
|
795
|
+
`).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)}Be(),Rs(),qo(),tn(),lE();const OE=`@gjsify/cli`,kE={command:`self-update`,description:`Update the installed ${OE} 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=Io(Io(a.prefix,`node_modules`,OE),`package.json`),C=readCurrentVersion(),N=existsSync(S);ze.log(`Current ${OE}: v${C??`(unknown)`}`),N||ze.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`),ze.log(`Fetching dist-tags for ${OE}@${n.tag} ...`);let F;try{F=await fetchPackument(OE)}catch(n){let a=n instanceof Error?n.message:String(n);ze.error(`Failed to fetch packument: ${a}`),process.exit(1);return}let I=resolveTag(F,n.tag);if(!I){ze.error(`Unknown dist-tag '${n.tag}' on ${OE}. Known tags: ${Object.keys(F[`dist-tags`]??{}).join(`, `)||`(none)`}`),process.exit(1);return}if(ze.log(`Latest matching --tag ${n.tag}: v${I}`),C===I&&!n.force){ze.log(`Already up to date (v${I}).`),n.check||ze.log(`Run with --force to reinstall anyway.`);return}if(n.check){ze.log(C?`Update available: v${C} → v${I}`:`Install required: → v${I}`),process.exit(1);return}ze.log(`Installing ${OE}@${I} ...`),await installPackages({prefix:a.prefix,specs:[`${OE}@${I}`],verbose:!1});let H=linkGlobalBins([OE],a);if(H.length===0)ze.warn("self-update: install completed but no bins were linked — package.json may be missing a `bin` field.");else for(let n of H)ze.log(` • ${n.link} → ${n.target}`);ze.log(`\nUpdated ${OE} to v${I}.`)}};function readCurrentVersion(){try{let n=zo(No(fileURLToPath(import.meta.url)));for(let a=0;a<8&&n!==zo(n);a++){let a=Io(n,`package.json`);if(existsSync(a)){let n=JSON.parse(readFileSync(a,`utf-8`));if(n.name===OE&&typeof n.version==`string`)return n.version}n=zo(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}Be(),Rs(),qo();function loadInstallerTemplate(){return readFileSync(new URL(`../templates/install.mjs.tmpl`,import.meta.url),`utf-8`)}const AE={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=No(process.cwd(),n.output);if(existsSync(a)&&!n.force){ze.error(`${n.output} already exists. Re-run with --force to overwrite.`),process.exit(1);return}let S=No(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){ze.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}),ze.log(`Wrote ${n.output} (target=${N}, bin=${F}).`),ze.log(``),ze.log(`Install one-liner for your README:`),ze.log(` curl -fsSL https://github.com/<you>/<repo>/raw/main/${n.output} -o /tmp/i.mjs \\`),ze.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}Be(),Rs(),qo();const jE={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){ze.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`;ze.log(`${N} ← ${a.prefix}`),ze.log(`${` `.repeat(N.length)} bins ← ${a.binDir}`);let F=!1;for(let N of n.packages){let n=specToPackageName(N),I=Io(a.prefix,`node_modules`,n);if(!existsSync(I)){ze.warn(` ✗ ${n} — not installed at ${I}`);continue}let H=findBinShimsForPackage(a.binDir,I,C);if(S){ze.log(` • would remove ${I}`);for(let n of H)ze.log(` • would remove ${n}`)}else{rmSync(I,{recursive:!0,force:!0}),ze.log(` • removed ${I}`);for(let n of H)unlinkSync(n),ze.log(` • removed ${n}`)}F=!0}F||(ze.error(`
|
|
796
|
+
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=Io(n,F);try{if(!statSync(N).isFile())continue;let n=readFileSync(N,`utf-8`);if(!n.startsWith(`#!/bin/sh`))continue;let S=n.match(/'([^']+)'/);if(!S)continue;let F=S[1];(F.startsWith(a+`/`)||F===a)&&C.push(N)}catch(n){S&&ze.warn(` ? could not inspect ${N}: ${n.message}`)}}return C}await du(hideBin(process.argv)).scriptName(fu).strict().command(UT.command,UT.description,UT.builder,UT.handler).command(SE.command,SE.description,SE.builder,SE.handler).command(AT.command,AT.description,AT.builder,AT.handler).command(jT.command,jT.description,jT.builder,jT.handler).command(xE.command,xE.description,xE.builder,xE.handler).command(MT.command,MT.description,MT.builder,MT.handler).command(LT.command,LT.description,LT.builder,LT.handler).command(RT.command,RT.description,RT.builder,RT.handler).command(GT.command,GT.description,GT.builder,GT.handler).command(qT.command,qT.description,qT.builder,qT.handler).command(YT.command,YT.description,YT.builder,YT.handler).command(nE.command,nE.description,nE.builder,nE.handler).command(CE.command,CE.description,CE.builder,CE.handler).command(wE.command,wE.description,wE.builder,wE.handler).command(TE.command,TE.description,TE.builder,TE.handler).command(DE.command,DE.description,DE.builder,DE.handler).command(kE.command,kE.description,kE.builder,kE.handler).command(AE.command,AE.description,AE.builder,AE.handler).command(jE.command,jE.description,jE.builder,jE.handler).demandCommand(1).help().parseAsync();
|
package/lib/commands/index.d.ts
CHANGED
package/lib/commands/index.js
CHANGED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// `gjsify uninstall -g <pkg>` — symmetric inverse of `install -g`.
|
|
2
|
+
//
|
|
3
|
+
// Removes the installed package tree from the user-global XDG location
|
|
4
|
+
// and any bin shims under `~/.local/bin/` that point into it. Mirrors
|
|
5
|
+
// the layout decisions in install-global.ts:
|
|
6
|
+
//
|
|
7
|
+
// ~/.local/share/gjsify/global/node_modules/<pkg>/ ← deleted
|
|
8
|
+
// ~/.local/bin/<bin> ← deleted iff it
|
|
9
|
+
// execs a path
|
|
10
|
+
// inside the
|
|
11
|
+
// removed tree
|
|
12
|
+
//
|
|
13
|
+
// Scope: --global only. Project-local uninstall (mirror of `npm uninstall
|
|
14
|
+
// <pkg>` without -g) is a separate workstream — it needs to rewrite
|
|
15
|
+
// package.json + refresh the lockfile, which install -g doesn't touch.
|
|
16
|
+
import { existsSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { defaultGlobalLayout, specToPackageName } from '../utils/install-global.js';
|
|
19
|
+
export const uninstallCommand = {
|
|
20
|
+
command: 'uninstall <packages..>',
|
|
21
|
+
description: 'Uninstall a previously installed package. Currently only `--global` mode is supported.',
|
|
22
|
+
builder: (yargs) => yargs
|
|
23
|
+
.positional('packages', {
|
|
24
|
+
description: 'Package(s) to uninstall (npm names, optionally with version).',
|
|
25
|
+
type: 'string',
|
|
26
|
+
array: true,
|
|
27
|
+
demandOption: true,
|
|
28
|
+
})
|
|
29
|
+
.option('global', {
|
|
30
|
+
description: 'Uninstall from the user-global XDG location (the install -g target).',
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
alias: 'g',
|
|
33
|
+
default: false,
|
|
34
|
+
})
|
|
35
|
+
.option('dry-run', {
|
|
36
|
+
description: 'Show what would be removed without touching the filesystem.',
|
|
37
|
+
type: 'boolean',
|
|
38
|
+
default: false,
|
|
39
|
+
})
|
|
40
|
+
.option('verbose', {
|
|
41
|
+
description: 'Verbose logging.',
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
45
|
+
handler: (args) => {
|
|
46
|
+
if (!args.global) {
|
|
47
|
+
console.error('gjsify uninstall currently only supports --global. ' +
|
|
48
|
+
'For project-local removal, edit package.json + re-run `gjsify install`.');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const layout = defaultGlobalLayout();
|
|
53
|
+
const dryRun = args['dry-run'] ?? false;
|
|
54
|
+
const verbose = args.verbose ?? false;
|
|
55
|
+
const prefix = `gjsify uninstall${dryRun ? ' (dry-run)' : ''} --global`;
|
|
56
|
+
console.log(`${prefix} ← ${layout.prefix}`);
|
|
57
|
+
console.log(`${' '.repeat(prefix.length)} bins ← ${layout.binDir}`);
|
|
58
|
+
let removedAny = false;
|
|
59
|
+
for (const spec of args.packages) {
|
|
60
|
+
const pkgName = specToPackageName(spec);
|
|
61
|
+
const pkgDir = join(layout.prefix, 'node_modules', pkgName);
|
|
62
|
+
if (!existsSync(pkgDir)) {
|
|
63
|
+
console.warn(` ✗ ${pkgName} — not installed at ${pkgDir}`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Find bin shims that exec into this package's tree. The shims
|
|
67
|
+
// are POSIX sh launchers written by linkGlobalBins; we identify
|
|
68
|
+
// candidates by reading the launcher script and matching the
|
|
69
|
+
// absolute path.
|
|
70
|
+
const binsToRemove = findBinShimsForPackage(layout.binDir, pkgDir, verbose);
|
|
71
|
+
if (dryRun) {
|
|
72
|
+
console.log(` • would remove ${pkgDir}`);
|
|
73
|
+
for (const bin of binsToRemove) {
|
|
74
|
+
console.log(` • would remove ${bin}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
rmSync(pkgDir, { recursive: true, force: true });
|
|
79
|
+
console.log(` • removed ${pkgDir}`);
|
|
80
|
+
for (const bin of binsToRemove) {
|
|
81
|
+
unlinkSync(bin);
|
|
82
|
+
console.log(` • removed ${bin}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
removedAny = true;
|
|
86
|
+
}
|
|
87
|
+
if (!removedAny) {
|
|
88
|
+
console.error('\nNo packages removed.');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Scan `binDir` for POSIX `sh` launchers whose `exec` target points into
|
|
95
|
+
* `pkgDir`. The launcher shape is fixed by `linkGlobalBins` — either:
|
|
96
|
+
*
|
|
97
|
+
* #!/bin/sh
|
|
98
|
+
* exec '<absolute-path>' "$@"
|
|
99
|
+
*
|
|
100
|
+
* or (for `.gjs.mjs` / `.mjs` targets):
|
|
101
|
+
*
|
|
102
|
+
* #!/bin/sh
|
|
103
|
+
* exec gjs -m '<absolute-path>' "$@"
|
|
104
|
+
*
|
|
105
|
+
* We parse the absolute path out of the single-quoted segment and check
|
|
106
|
+
* whether it's under `pkgDir`. Non-shim files (e.g. unrelated binaries
|
|
107
|
+
* the user installed via `npm install -g`) are skipped silently.
|
|
108
|
+
*/
|
|
109
|
+
function findBinShimsForPackage(binDir, pkgDir, verbose) {
|
|
110
|
+
if (!existsSync(binDir))
|
|
111
|
+
return [];
|
|
112
|
+
const matches = [];
|
|
113
|
+
let entries;
|
|
114
|
+
try {
|
|
115
|
+
entries = readdirSync(binDir);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
for (const name of entries) {
|
|
121
|
+
const fullPath = join(binDir, name);
|
|
122
|
+
try {
|
|
123
|
+
const st = statSync(fullPath);
|
|
124
|
+
if (!st.isFile())
|
|
125
|
+
continue;
|
|
126
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
127
|
+
if (!content.startsWith('#!/bin/sh'))
|
|
128
|
+
continue;
|
|
129
|
+
// Match the first single-quoted absolute path.
|
|
130
|
+
const m = content.match(/'([^']+)'/);
|
|
131
|
+
if (!m)
|
|
132
|
+
continue;
|
|
133
|
+
const target = m[1];
|
|
134
|
+
if (target.startsWith(pkgDir + '/') || target === pkgDir) {
|
|
135
|
+
matches.push(fullPath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
if (verbose) {
|
|
140
|
+
console.warn(` ? could not inspect ${fullPath}: ${err.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return matches;
|
|
145
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import yargs from 'yargs';
|
|
3
3
|
import { hideBin } from 'yargs/helpers';
|
|
4
|
-
import { buildCommand as build, runCommand as run, infoCommand as info, checkCommand as check, showcaseCommand as showcase, createCommand as create, gresourceCommand as gresource, gettextCommand as gettext, gsettingsCommand as gsettings, flatpakCommand as flatpak, dlxCommand as dlx, installCommand as install, foreachCommand as foreach, workspaceCommand as workspace, packCommand as pack, publishCommand as publish, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, } from './commands/index.js';
|
|
4
|
+
import { buildCommand as build, runCommand as run, infoCommand as info, checkCommand as check, showcaseCommand as showcase, createCommand as create, gresourceCommand as gresource, gettextCommand as gettext, gsettingsCommand as gsettings, flatpakCommand as flatpak, dlxCommand as dlx, installCommand as install, foreachCommand as foreach, workspaceCommand as workspace, packCommand as pack, publishCommand as publish, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, uninstallCommand as uninstall, } from './commands/index.js';
|
|
5
5
|
import { APP_NAME } from './constants.js';
|
|
6
6
|
// `parseAsync()` instead of `.argv` so the top-level await keeps the
|
|
7
7
|
// process alive until command handlers complete. Under Node this is
|
|
@@ -29,6 +29,7 @@ await yargs(hideBin(process.argv))
|
|
|
29
29
|
.command(publish.command, publish.description, publish.builder, publish.handler)
|
|
30
30
|
.command(selfUpdate.command, selfUpdate.description, selfUpdate.builder, selfUpdate.handler)
|
|
31
31
|
.command(generateInstaller.command, generateInstaller.description, generateInstaller.builder, generateInstaller.handler)
|
|
32
|
+
.command(uninstall.command, uninstall.description, uninstall.builder, uninstall.handler)
|
|
32
33
|
.demandCommand(1)
|
|
33
34
|
.help()
|
|
34
35
|
.parseAsync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
"cli"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@gjsify/buffer": "^0.4.
|
|
41
|
-
"@gjsify/create-app": "^0.4.
|
|
42
|
-
"@gjsify/node-globals": "^0.4.
|
|
43
|
-
"@gjsify/node-polyfills": "^0.4.
|
|
44
|
-
"@gjsify/npm-registry": "^0.4.
|
|
45
|
-
"@gjsify/resolve-npm": "^0.4.
|
|
46
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.4.
|
|
47
|
-
"@gjsify/rolldown-plugin-pnp": "^0.4.
|
|
48
|
-
"@gjsify/semver": "^0.4.
|
|
49
|
-
"@gjsify/tar": "^0.4.
|
|
50
|
-
"@gjsify/web-polyfills": "^0.4.
|
|
51
|
-
"@gjsify/workspace": "^0.4.
|
|
40
|
+
"@gjsify/buffer": "^0.4.11",
|
|
41
|
+
"@gjsify/create-app": "^0.4.11",
|
|
42
|
+
"@gjsify/node-globals": "^0.4.11",
|
|
43
|
+
"@gjsify/node-polyfills": "^0.4.11",
|
|
44
|
+
"@gjsify/npm-registry": "^0.4.11",
|
|
45
|
+
"@gjsify/resolve-npm": "^0.4.11",
|
|
46
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.4.11",
|
|
47
|
+
"@gjsify/rolldown-plugin-pnp": "^0.4.11",
|
|
48
|
+
"@gjsify/semver": "^0.4.11",
|
|
49
|
+
"@gjsify/tar": "^0.4.11",
|
|
50
|
+
"@gjsify/web-polyfills": "^0.4.11",
|
|
51
|
+
"@gjsify/workspace": "^0.4.11",
|
|
52
52
|
"cosmiconfig": "^9.0.1",
|
|
53
53
|
"get-tsconfig": "^4.14.0",
|
|
54
54
|
"pkg-types": "^2.3.1",
|
|
@@ -56,12 +56,12 @@
|
|
|
56
56
|
"yargs": "^18.0.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@gjsify/unit": "^0.4.
|
|
59
|
+
"@gjsify/unit": "^0.4.11",
|
|
60
60
|
"@types/yargs": "^17.0.35",
|
|
61
61
|
"typescript": "^6.0.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@gjsify/rolldown-native": "^0.4.
|
|
64
|
+
"@gjsify/rolldown-native": "^0.4.11"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@gjsify/rolldown-native": {
|