@ghship/cli 1.0.1 → 1.0.4

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/README.md CHANGED
@@ -1,7 +1,10 @@
1
1
  # GHShip npm distribution
2
2
 
3
- Go CLI and home AI skill plus per-project integration. Package includes native
4
- binaries for macOS/Linux/Windows × amd64/arm64, with no postinstall downloads.
3
+ Go CLI and home AI skill plus per-project integration. npm installs the native
4
+ binary package matching the Node.js operating system and architecture for
5
+ macOS/Linux/Windows × x64/arm64, with no postinstall downloads. Optional
6
+ dependencies must be enabled and the matching package must be available from the
7
+ configured registry. An emulated Node.js runtime selects its own architecture.
5
8
 
6
9
  After publication: `npx @ghship/cli@latest install` installs the home skill, same-version
7
10
  persistent CLI, then enters project initialization. `--user-only` stops after user
@@ -10,12 +13,16 @@ installation. Project workflows need Git and a gh with native Issue relationship
10
13
 
11
14
  Update/uninstall via external `npx @ghship/cli@latest update|uninstall`. Project lifecycle:
12
15
  `ghship project install|status|update|uninstall`; custom `.ghship/skill.md` is preserved.
16
+ Install and update also update a verified standalone installation in place, keeping its
17
+ channel and paths. To uninstall a standalone installation, use a separate downloaded
18
+ executable with `uninstall --standalone`.
13
19
  Users: `ghship user add|update|delete|list`; list validates tokens and reports actual
14
20
  expiry when GitHub returns it, otherwise unknown. Tokens never belong in projects.
15
21
 
16
22
  For a downloaded npm tarball, use:
17
23
  `npm exec --package /absolute/ghship.tgz -- ghship install --package-source /absolute/ghship.tgz`.
18
- Add `--user-only` to install shared tools without initializing a project.
24
+ The configured registry must provide the same-version platform package. Add
25
+ `--user-only` to install shared tools without initializing a project.
19
26
  Registry commands require the selected version to be published.
20
27
 
21
28
  Detailed Chinese tutorial and AI skill: https://github.com/ghship/cli
package/checksums.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
- "bin/darwin-amd64/ghship": "81c748330990be524622d58c8dc9e47efa81f554c7004a378ef9a958e29d6f53",
3
- "bin/darwin-arm64/ghship": "93c2f21a01d0d276a1f8cacfd5e3361de048b32a66da62178e6728f700c5a9ac",
4
- "bin/linux-amd64/ghship": "9e75a33039b6568c02ca5020c26c5ae7eca5248487643a8a37578e263391e1c7",
5
- "bin/linux-arm64/ghship": "58c8390b8e8ece6164c2eb2de97947257062a413bf53daa7e329ef80970e6bfa",
6
- "bin/windows-amd64/ghship.exe": "80468fe319d059c8256671cfcc33b598e9520b3f954d2ec5b28b75b926dbab40",
7
- "bin/windows-arm64/ghship.exe": "ed332b76c8c943ba6cf5a54a2480fde4411417cc88e2a63e1cf1831a553c6825"
2
+ "darwin-arm64": "bdce86aa2ebb4bca9ee4e149cce2b29d51557dfeee7f732b2dcd6d1458f125a6",
3
+ "darwin-x64": "c0b71ad5b0d9e338cb9836b8572e9caacbad1a710965df0af88665d68dc100da",
4
+ "linux-arm64": "b3607bc943a8b01a5e8a164a21026766442cd1a7fae14b2703afcfdd1afa97f3",
5
+ "linux-x64": "2ba3458d178f81abc11478fa92128e76fcc61567b9abc4a8d1931fcaa6363f02",
6
+ "win32-arm64": "68a9bb61d9f1ee7147f2bc7eacc2c377fd25f2016b4d911b818db3d8666e2f4c",
7
+ "win32-x64": "dc1d6ebf166634222f919668f1b7803c2bf1c2c6d6df2d460c95b7bb48f2d85d"
8
8
  }
package/package.json CHANGED
@@ -1,16 +1,23 @@
1
1
  {
2
2
  "name": "@ghship/cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "description": "Go CLI and AI skill for native GitHub delivery pipelines",
5
5
  "bin": {
6
6
  "ghship": "run.cjs"
7
7
  },
8
8
  "files": [
9
9
  "run.cjs",
10
- "bin",
11
10
  "README.md",
12
11
  "checksums.json"
13
12
  ],
13
+ "optionalDependencies": {
14
+ "@ghship/cli-darwin-x64": "1.0.4",
15
+ "@ghship/cli-darwin-arm64": "1.0.4",
16
+ "@ghship/cli-linux-x64": "1.0.4",
17
+ "@ghship/cli-linux-arm64": "1.0.4",
18
+ "@ghship/cli-win32-x64": "1.0.4",
19
+ "@ghship/cli-win32-arm64": "1.0.4"
20
+ },
14
21
  "engines": {
15
22
  "node": ">=18"
16
23
  },
package/run.cjs CHANGED
@@ -4,11 +4,31 @@ const fs = require('node:fs');
4
4
  const os = require('node:os');
5
5
  const crypto = require('node:crypto');
6
6
  const { spawn, spawnSync } = require('node:child_process');
7
- function binaryPath(platform = process.platform, arch = process.arch) {
8
- const os = {darwin:'darwin', linux:'linux', win32:'windows'}[platform];
9
- const cpu = {x64:'amd64', arm64:'arm64'}[arch];
10
- if (!os || !cpu) throw new Error(`Unsupported platform: ${platform}/${arch}`);
11
- return path.join(__dirname, 'bin', `${os}-${cpu}`, os === 'windows' ? 'ghship.exe' : 'ghship');
7
+ function singlePack(value,name) {
8
+ let record;
9
+ if(Array.isArray(value)) {if(value.length!==1)throw new Error('Unexpected npm pack result');record=value[0];}
10
+ else if(value&&typeof value==='object'&&Object.keys(value).length===1&&Object.hasOwn(value,name))record=value[name];
11
+ if(!record||typeof record!=='object'||record.name!==name||typeof record.version!=='string')throw new Error('Unexpected npm pack result');
12
+ return record;
13
+ }
14
+ function platformInfo(platform = process.platform, arch = process.arch) {
15
+ if (!['darwin','linux','win32'].includes(platform) || !['x64','arm64'].includes(arch)) throw new Error(`Unsupported platform: ${platform}/${arch}`);
16
+ return {target:`${platform}-${arch}`,name:`@ghship/cli-${platform}-${arch}`,binary:platform==='win32'?'ghship.exe':'ghship'};
17
+ }
18
+ function binaryPath(platform = process.platform, arch = process.arch, root = __dirname) {
19
+ const info=platformInfo(platform,arch);
20
+ const meta=JSON.parse(fs.readFileSync(path.join(root,'package.json'),'utf8'));
21
+ let manifest;try {manifest=require.resolve(`${info.name}/package.json`,{paths:[root]});}
22
+ catch (_) {throw new Error(`Platform package ${info.name}@${meta.version} is missing; enable optional dependencies and reinstall @ghship/cli@${meta.version}`);}
23
+ const allowed=[path.join(root,'node_modules',info.name),path.join(path.dirname(path.dirname(root)),info.name)];
24
+ if(!allowed.some(dir=>path.resolve(path.dirname(manifest))===path.resolve(dir)))throw new Error('Platform package is outside the installation; preserved');
25
+ noLinks(manifest);
26
+ const platformMeta=JSON.parse(fs.readFileSync(manifest,'utf8'));
27
+ if(platformMeta.name!==info.name||platformMeta.version!==meta.version||!platformMeta.os?.includes(platform)||!platformMeta.cpu?.includes(arch))throw new Error('Platform package does not match CLI version or platform; preserved');
28
+ const executable=path.join(path.dirname(manifest),'bin',info.binary);noLinks(executable);
29
+ const checks=JSON.parse(fs.readFileSync(path.join(root,'checksums.json'),'utf8'));
30
+ if(!checks[info.target]||hashFile(executable)!==checks[info.target])throw new Error('Platform binary missing or checksum mismatch; preserved');
31
+ return executable;
12
32
  }
13
33
  function npmCommand() {
14
34
  if (process.env.npm_execpath && fs.existsSync(process.env.npm_execpath)) return [process.execPath, [process.env.npm_execpath]];
@@ -53,8 +73,10 @@ function observePackage(root,meta,existing,replacingVerifiedReceipt=false) {
53
73
  const directory=path.join(root,meta.name); noLinks(directory);
54
74
  const metadata=JSON.parse(fs.readFileSync(path.join(directory,'package.json'),'utf8'));
55
75
  if(metadata.name!==meta.name||metadata.version!==existing.version||metadata.bin?.ghship!=='run.cjs')throw new Error('Unrecognized global package; preserved');
56
- const relative=path.relative(__dirname,binaryPath());
57
- const observed={root,name:meta.name,version:metadata.version,launcher:hashFile(path.join(directory,'run.cjs')),binary:hashFile(path.join(directory,relative))};
76
+ const info=platformInfo();const legacyOS={darwin:'darwin',linux:'linux',win32:'windows'}[process.platform];const legacyArch={x64:'amd64',arm64:'arm64'}[process.arch];
77
+ const legacy=path.join(directory,'bin',`${legacyOS}-${legacyArch}`,info.binary);
78
+ const installedBinary=fs.existsSync(legacy)?legacy:binaryPath(process.platform,process.arch,directory);noLinks(installedBinary);
79
+ const observed={root,name:meta.name,version:metadata.version,launcher:hashFile(path.join(directory,'run.cjs')),binary:hashFile(installedBinary)};
58
80
  const current=observed.version===meta.version&&observed.launcher===hashFile(path.join(__dirname,'run.cjs'))&&observed.binary===hashFile(binaryPath());
59
81
  const receipt=packageReceipt(root);noLinks(receipt);
60
82
  let recorded=null;try{recorded=JSON.parse(fs.readFileSync(receipt,'utf8'));}catch(error){if(error.code!=='ENOENT')throw new Error('Invalid installation receipt; preserved');}
@@ -73,7 +95,7 @@ function lifecycle(executable,args) {
73
95
  const action=args[0], meta=require('./package.json'); let source=`${meta.name}@${meta.version}`;
74
96
  const passed=args.slice(1); const sourceIndex=passed.indexOf('--package-source');
75
97
  if(sourceIndex>=0) {if(!passed[sourceIndex+1])throw new Error('--package-source requires a local tarball');source=path.resolve(passed[sourceIndex+1]);passed.splice(sourceIndex,2);
76
- const packed=JSON.parse(npm(['pack',source,'--dry-run','--json'],true));if(packed.length!==1||packed[0].name!==meta.name||packed[0].version!==meta.version)throw new Error('Local package source must match this exact package and version');
98
+ const packed=singlePack(JSON.parse(npm(['pack',source,'--dry-run','--json'],true)),meta.name);if(packed.version!==meta.version)throw new Error('Local package source must match this exact package and version');
77
99
  }
78
100
  const lifecycleArgs=[];const initArgs=[];let userOnly=false;
79
101
  const initValues=new Set(['--project','--repo','--author','--reviewer','--admin','--acceptance-pr']);
@@ -87,8 +109,18 @@ function lifecycle(executable,args) {
87
109
  else throw new Error(`Unknown lifecycle option: ${arg}; use ghship help install`);
88
110
  }
89
111
  const preflight=go(executable,[action,'--skill-only','--check',...lifecycleArgs,'--json'],true);if(preflight.code!==0)return preflight.code;
112
+ const state=JSON.parse(preflight.stdout);
90
113
  const installed=JSON.parse(npm(['list','-g',meta.name,'--depth=0','--json'],true));
91
114
  const existing=installed.dependencies && installed.dependencies[meta.name];
115
+ if(state.standalone) {
116
+ if(action==='uninstall')throw new Error('Standalone installation exists; uninstall with a separate downloaded executable and --standalone');
117
+ if(existing)throw new Error('Standalone and npm installations both exist; inspect both before updating');
118
+ const args=[action,'--standalone',...lifecycleArgs,...(userOnly?['--user-only']:[]),...initArgs.filter(arg=>arg!=='--json'),'--json'];
119
+ const result=go(executable,args,true,action==='install'&&!userOnly);
120
+ if(action==='install'&&!userOnly) {if(result.stdout)process.stdout.write(result.stdout);return result.code;}
121
+ if(result.code!==0)return result.code;
122
+ console.log(JSON.stringify({userInstalled:true,version:meta.version,projectInitialized:false}));return 0;
123
+ }
92
124
  const root=path.resolve(npm(['root','-g'],true).trim());
93
125
  const receipt=packageReceipt(root);noLinks(receipt);
94
126
  if(!existing&&action!=='uninstall'&&fs.existsSync(receipt))throw new Error('Installation receipt exists without its package; preserved');
@@ -105,7 +137,7 @@ function lifecycle(executable,args) {
105
137
  const verified=JSON.parse(npm(['list','-g',meta.name,'--depth=0','--json'],true));
106
138
  if(!verified.dependencies||verified.dependencies[meta.name]?.version!==meta.version)throw new Error('Installed CLI version does not match skill; installation incomplete');
107
139
  const persisted=observePackage(root,meta,verified.dependencies[meta.name],!!observation);if(!persisted.current)throw new Error('Persistent CLI content does not match this package');
108
- const runtimeVersion=go(path.join(root,meta.name,path.relative(__dirname,executable)),['version'],true);
140
+ const runtimeVersion=go(binaryPath(process.platform,process.arch,path.join(root,meta.name)),['version'],true);
109
141
  if(runtimeVersion.code!==0||runtimeVersion.stdout.trim()!==meta.version)throw new Error('Persistent CLI cannot run the expected version');
110
142
  saveReceipt(root,persisted.observed);
111
143
  if(action==='update'||userOnly){console.log(JSON.stringify({userInstalled:true,version:meta.version,projectInitialized:false}));return 0;}
@@ -113,7 +145,7 @@ function lifecycle(executable,args) {
113
145
  let result;try{result=JSON.parse(init.stdout);}catch(_){result={ready:false,missing:['Initialization failed before producing a result'],next:['ghship init --json']};}
114
146
  result.userInstalled=true;result.version=meta.version;console.log(JSON.stringify(result));return init.code;
115
147
  }
116
- module.exports = {binaryPath};
148
+ module.exports = {binaryPath,platformInfo,singlePack};
117
149
  if(require.main===module) {
118
150
  try {
119
151
  const executable=binaryPath();if(!fs.existsSync(executable))throw new Error('Package missing Go binary; reinstall the package');
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file