pier-setup 0.2.0 → 0.2.2

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.
Files changed (3) hide show
  1. package/README.md +15 -5
  2. package/install.mjs +234 -22
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -46,24 +46,34 @@ pier ships as **two halves**, installed separately:
46
46
  No clone needed — run the npm package directly:
47
47
 
48
48
  ```sh
49
- npx pier-setup # user mode: pi install npm:pi-pier + herdr plugin install + auto-generated bootstrap config
50
- npm i -g pier-setup # or install globally, then just run pier-setup
49
+ npx pier-setup@latest # user mode: pi install npm:pi-pier + herdr plugin install
50
+ npx pier-setup@latest version # local vs npm latest (installer / pi-pier / herdr plugin)
51
+ npx pier-setup@latest update # refresh both halves in place (does not uninstall first)
52
+ npx pier-setup@latest uninstall # --purge also drops boot-config.json
53
+
54
+ npm i -g pier-setup # or install globally, then: pier-setup / version / update
51
55
  ```
52
56
 
57
+ Pin `@latest` so npx does not reuse a cached installer. In a clone, `npm install`
58
+ links `node_modules/.bin/pier-setup`, so in-repo `npx pier-setup` runs `./install.mjs`.
59
+
53
60
  Dev mode still requires cloning the repo:
54
61
 
55
62
  ```sh
56
63
  git clone https://github.com/July24/pier && cd pier
57
64
  node install.mjs install --dev # local-path pi install + herdr plugin link; code changes are live
65
+ node install.mjs version --dev
66
+ node install.mjs update --dev # rewrite boot-config only; pull the repo yourself
58
67
  ```
59
68
 
60
69
  The script verifies the environment (node / pi / herdr versions), probes pi's node
61
70
  and cli.js absolute paths, generates boot-config.json (user mode stores it in the
62
71
  herdr plugin config dir, so reinstalls don't lose it), and registers both halves.
63
72
 
64
- Other commands: `pier-setup update` (uninstall + reinstall, re-probes paths),
65
- `pier-setup uninstall --purge`. Override the distribution specs with
66
- `--pi-spec=` / `--herdr-spec=` (npm publishing or forks).
73
+ `update` re-runs `pi update npm:pi-pier` (falls back to `pi install`) and
74
+ `herdr plugin install … --yes`, then rewrites boot-config. Override sources with
75
+ `--pi-spec=` / `--herdr-spec=` (npm publishing or forks). `pier-setup --help`
76
+ lists every command.
67
77
 
68
78
  ### Manual install (equivalent steps)
69
79
 
package/install.mjs CHANGED
@@ -1,24 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * pier 一键安装/卸载/更新(跨平台:macOS / Linux / Windows)。
3
+ * pier 一键安装/卸载/更新/查版本(跨平台:macOS / Linux / Windows)。
4
4
  *
5
5
  * 用法:
6
6
  * node install.mjs install [--dev] [--hmr-dev] [--force] # 安装(默认命令,可省略 install)
7
+ * node install.mjs update [--dev] [--hmr-dev] # 原地刷新两半区到最新发行(不先卸载)
8
+ * node install.mjs version [--json] # 本地 vs npm latest
7
9
  * node install.mjs uninstall [--dev] [--purge] # 卸载;--purge 连 boot-config.json 一起删
8
- * node install.mjs update [--dev] [--hmr-dev] [--force] # 更新 = 卸载注册 + 重新安装(重探测路径)
10
+ * node install.mjs --prepare # npm prepare:hooksPath + 仓库根 bin 链接
11
+ * node install.mjs --help
9
12
  *
10
13
  * 模式:
11
- * 用户模式(默认):pi install git:github.com/July24/pier(仓库根 package.json 的 pi 字段)
14
+ * 用户模式(默认):pi install npm:pi-pier
12
15
  * + herdr plugin install July24/pier/packages/pier-workbench --yes
13
- * boot-config.json 写 herdr plugin config-dir(reinstall 不丢)。
16
+ * boot-config.json 写 herdr plugin config-dir;extPath 指向 pi 已安装的
17
+ * pi-pier(~/.pi/agent/npm/...),不是 pier-setup 包内的仓库路径。
14
18
  * --dev 开发模式:pi install <repo>/packages/pier-ext + herdr plugin link <repo>/packages/pier-workbench
15
19
  * (link 目录是活的,改码即生效;update 只重写 boot-config)。
16
- *
17
20
  * 发行规格可用 --pi-spec= / --herdr-spec= 覆盖(npm 发布或 fork 场景)。
18
21
  * 失败语义:每步给出手动等价命令;步骤失败不阻断报告(exitCode=1)。
19
22
  */
20
23
  import { execFileSync } from 'node:child_process';
21
- import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
24
+ import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
25
+ import { homedir } from 'node:os';
22
26
  import { dirname, join, resolve } from 'node:path';
23
27
  import { fileURLToPath } from 'node:url';
24
28
 
@@ -28,10 +32,38 @@ const WB_DIR = join(ROOT, 'packages', 'pier-workbench');
28
32
  const DEV_BOOT_CONFIG = join(WB_DIR, 'scripts', 'boot-config.json');
29
33
  const EXT_PATH = join(EXT_DIR, 'src', 'index.ts');
30
34
  const IS_WIN = process.platform === 'win32';
35
+ const COMMANDS = ['install', 'uninstall', 'update', 'version', 'help'];
36
+
37
+ /** Workspace root is named pier-setup but npm does not bin-link it, so
38
+ * `npx pier-setup@version` from the clone runs `sh -c pier-setup` against a
39
+ * missing node_modules/.bin/pier-setup. Published tarball has no packages/. */
40
+ function prepareRepo() {
41
+ if (existsSync(join(ROOT, '.githooks'))) {
42
+ try { execFileSync('git', ['config', 'core.hooksPath', '.githooks'], { stdio: 'ignore' }); } catch { /* not a git checkout */ }
43
+ }
44
+ if (!existsSync(EXT_PATH)) return;
45
+ const binDir = join(ROOT, 'node_modules', '.bin');
46
+ mkdirSync(binDir, { recursive: true });
47
+ const posix = join(binDir, 'pier-setup');
48
+ const cmd = join(binDir, 'pier-setup.cmd');
49
+ rmSync(posix, { force: true });
50
+ rmSync(cmd, { force: true });
51
+ if (IS_WIN) {
52
+ writeFileSync(cmd, '@echo off\r\nnode "%~dp0\\..\\..\\install.mjs" %*\r\n');
53
+ writeFileSync(posix, '#!/bin/sh\nexec node "$(dirname "$0")/../../install.mjs" "$@"\n');
54
+ try { chmodSync(posix, 0o755); } catch { /* git-bash optional */ }
55
+ } else {
56
+ symlinkSync('../../install.mjs', posix);
57
+ }
58
+ }
31
59
 
32
60
  const argv = process.argv.slice(2);
33
- const command = ['install', 'uninstall', 'update'].includes(argv[0]) ? argv.shift() : 'install';
34
- const flags = new Set(argv.filter((a) => a.startsWith('--') && !a.includes('=')));
61
+ if (argv[0] === '--prepare') {
62
+ prepareRepo();
63
+ process.exit(0);
64
+ }
65
+ const command = COMMANDS.includes(argv[0]) ? argv.shift() : null;
66
+ const flags = new Set(argv.filter((a) => (a.startsWith('--') && !a.includes('=')) || a === '-h' || a === '-v'));
35
67
  const optValue = (name) => argv.find((a) => a.startsWith(`--${name}=`))?.split('=').slice(1).join('=') ?? null;
36
68
  const dev = flags.has('--dev');
37
69
  const PI_SPEC = optValue('pi-spec') ?? 'npm:pi-pier'; // 用户模式默认 npm 发行版;--pi-spec=git:github.com/July24/pier 可跟随仓库 main
@@ -74,6 +106,34 @@ function geVersion(v, min) {
74
106
  return true;
75
107
  }
76
108
 
109
+ function cmpSemver(a, b) {
110
+ const pa = parseVersion(a);
111
+ const pb = parseVersion(b);
112
+ if (!pa || !pb) return 0;
113
+ for (let i = 0; i < 3; i++) {
114
+ if (pa[i] !== pb[i]) return pa[i] < pb[i] ? -1 : 1;
115
+ }
116
+ return 0;
117
+ }
118
+
119
+ function readPkgVersion(dir) {
120
+ try {
121
+ return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).version ?? null;
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ function npmLatest(name) {
128
+ try {
129
+ return execFileSync('npm', ['view', name, 'version'], {
130
+ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15000,
131
+ }).trim();
132
+ } catch {
133
+ return null;
134
+ }
135
+ }
136
+
77
137
  /** herdr 插件配置目录(用户模式 boot-config 落点;插件已注册才存在路径语义)。 */
78
138
  function herdrConfigDir() {
79
139
  try { return run('herdr', ['plugin', 'config-dir', 'pier.workbench']); } catch { return null; }
@@ -121,14 +181,70 @@ function probePiRuntime() {
121
181
  return null;
122
182
  }
123
183
 
184
+ function piAgentDir() {
185
+ return process.env.PI_CODING_AGENT_DIR || join(homedir(), '.pi', 'agent');
186
+ }
187
+
188
+ function npmNameFromSpec(spec) {
189
+ if (!spec.startsWith('npm:')) return null;
190
+ const rest = spec.slice(4);
191
+ if (rest.startsWith('@')) {
192
+ const slash = rest.indexOf('/');
193
+ if (slash < 0) return rest;
194
+ return `${rest.slice(0, slash)}/${rest.slice(slash + 1).split('@')[0]}`;
195
+ }
196
+ return rest.split('@')[0];
197
+ }
198
+
199
+ function extPathFromPiList() {
200
+ let out;
201
+ try { out = run('pi', ['list']); } catch { return null; }
202
+ const lines = out.split(/\r?\n/);
203
+ for (let i = 0; i < lines.length; i++) {
204
+ const spec = lines[i].trim();
205
+ const loc = lines[i + 1]?.trim();
206
+ if (!loc) continue;
207
+ if (!/pi-pier|pier-ext|july24\/pier/i.test(spec)) continue;
208
+ for (const c of [join(loc, 'src', 'index.ts'), join(loc, 'packages', 'pier-ext', 'src', 'index.ts')]) {
209
+ if (existsSync(c)) return c;
210
+ }
211
+ }
212
+ return null;
213
+ }
214
+
215
+ /** User-mode extPath: pi's installed package, never the pier-setup npx cache. */
216
+ function resolveUserExtPath() {
217
+ const candidates = [];
218
+ const npmName = npmNameFromSpec(PI_SPEC);
219
+ if (npmName) candidates.push(join(piAgentDir(), 'npm', 'node_modules', npmName, 'src', 'index.ts'));
220
+ if (PI_SPEC.startsWith('git:')) {
221
+ const hostPath = PI_SPEC.slice(4).replace(/^git@/, '').replace(/:/g, '/');
222
+ candidates.push(
223
+ join(piAgentDir(), 'git', hostPath, 'packages', 'pier-ext', 'src', 'index.ts'),
224
+ join(piAgentDir(), 'git', hostPath, 'src', 'index.ts'),
225
+ );
226
+ }
227
+ candidates.push(
228
+ join(piAgentDir(), 'npm', 'node_modules', 'pi-pier', 'src', 'index.ts'),
229
+ join(piAgentDir(), 'git', 'github.com', 'July24', 'pier', 'packages', 'pier-ext', 'src', 'index.ts'),
230
+ );
231
+ for (const p of candidates) {
232
+ if (p && existsSync(p)) return p;
233
+ }
234
+ return extPathFromPiList();
235
+ }
236
+
124
237
  function writeBootConfig(hmrDev, force) {
125
238
  const probed = probePiRuntime();
126
239
  if (!probed) die('cannot locate pi cli.js (tried: pi shim realpath, npm root -g). Fill boot-config.json manually.');
127
- if (!existsSync(EXT_PATH)) die(`pier-ext entry missing: ${EXT_PATH} (repo layout broken?)`);
128
- // 用户模式 extPath 必须指向 herdr 管理的 checkout 内的扩展(安装 herdr 半区后才知道);
129
- // git 安装布局与本地同构:packages/pier-ext/src/index.ts。
130
- const extPath = dev ? EXT_PATH
131
- : join(herdrManagedRootGuess(), 'packages', 'pier-ext', 'src', 'index.ts');
240
+ let extPath;
241
+ if (dev) {
242
+ if (!existsSync(EXT_PATH)) die(`pier-ext entry missing: ${EXT_PATH} (repo layout broken?)`);
243
+ extPath = EXT_PATH;
244
+ } else {
245
+ extPath = resolveUserExtPath();
246
+ if (!extPath) die(`cannot locate installed pier-ext (npm:pi-pier / git clone). Did \`pi install ${piSource()}\` succeed?`);
247
+ }
132
248
 
133
249
  const config = {
134
250
  mainTabLabel: 'main',
@@ -160,15 +276,109 @@ function writeBootConfig(hmrDev, force) {
160
276
  log(` extPath = ${config.extPath}`);
161
277
  }
162
278
 
163
- /** herdr 管理的插件 checkout 根(GitHub 安装布局:…/plugins/<owner>-<repo>/…)。 */
164
- function herdrManagedRootGuess() {
165
- const dir = herdrConfigDir();
166
- if (!dir) return '';
167
- // config-dir 形如 <plugins-root>/<plugin-id>/config → checkout 在 <plugins-root>/<plugin-id>/
168
- return resolve(dir, '..');
279
+ function pkgDirFromExtPath(extPath) {
280
+ let d = dirname(extPath);
281
+ for (let i = 0; i < 5; i++) {
282
+ if (existsSync(join(d, 'package.json'))) return d;
283
+ const parent = dirname(d);
284
+ if (parent === d) break;
285
+ d = parent;
286
+ }
287
+ return null;
288
+ }
289
+
290
+ function herdrPluginInfo() {
291
+ let out;
292
+ try { out = run('herdr', ['plugin', 'list']); } catch { return { installed: false, spec: null, sha: null }; }
293
+ const line = out.split(/\r?\n/).find((l) => l.includes('pier.workbench'));
294
+ if (!line) return { installed: false, spec: null, sha: null };
295
+ const bracket = line.match(/\[([^\]]+)\]/);
296
+ const spec = bracket ? bracket[1] : line.trim();
297
+ const at = spec.lastIndexOf('@');
298
+ const fullSha = at >= 0 && !spec.startsWith('local:') ? spec.slice(at + 1) : null;
299
+ return { installed: true, spec, sha: fullSha ? fullSha.slice(0, 7) : null };
300
+ }
301
+
302
+ function collectStatus() {
303
+ const installer = readPkgVersion(ROOT);
304
+ const extPath = dev ? (existsSync(EXT_PATH) ? EXT_PATH : null) : resolveUserExtPath();
305
+ const extDir = extPath ? pkgDirFromExtPath(extPath) : null;
306
+ return {
307
+ product: (extDir ? readPkgVersion(extDir) : null) || installer,
308
+ mode: dev ? 'dev' : 'user',
309
+ installer: { name: 'pier-setup', version: installer },
310
+ piExt: { spec: piSource(), version: extDir ? readPkgVersion(extDir) : null, path: extDir },
311
+ herdr: herdrPluginInfo(),
312
+ latest: { 'pier-setup': npmLatest('pier-setup'), 'pi-pier': npmLatest('pi-pier') },
313
+ };
314
+ }
315
+
316
+ /* ── 命令 ────────────────────────────────────────────────────────── */
317
+ function usage() {
318
+ log(`pier-setup — install / update / inspect pier (pi-pier + pier.workbench)
319
+
320
+ Usage:
321
+ pier-setup [install] [--dev] [--hmr-dev] [--force]
322
+ pier-setup update [--dev] [--hmr-dev]
323
+ pier-setup version [--json]
324
+ pier-setup uninstall [--dev] [--purge]
325
+
326
+ npx pier-setup@latest # recommended user install
327
+ npx pier-setup@latest update # refresh both halves to newest release
328
+ npx pier-setup@latest version # local vs npm latest
329
+
330
+ --pi-spec= / --herdr-spec= override distribution sources
331
+ --dev local link (clone); update only rewrites boot-config`);
332
+ }
333
+
334
+ function version() {
335
+ const s = collectStatus();
336
+ if (flags.has('--json')) {
337
+ log(JSON.stringify(s, null, 2));
338
+ return;
339
+ }
340
+ log(`pier ${s.product ?? 'not installed'}${s.mode === 'dev' ? ' (dev, local link)' : ''}`);
341
+ log('');
342
+ log(` installer pier-setup ${s.installer.version ?? 'unknown'} (this CLI)`);
343
+ log(` pi ext ${s.piExt.spec} ${s.piExt.version ?? 'not installed'} ${s.piExt.path ?? ''}`);
344
+ const herdrVer = s.herdr.installed ? (s.herdr.sha ?? s.herdr.spec) : 'not installed';
345
+ log(` herdr pier.workbench ${herdrVer} ${s.herdr.spec ?? ''}`);
346
+ log('');
347
+ const ls = s.latest['pier-setup'] ?? 'unavailable (offline?)';
348
+ const lp = s.latest['pi-pier'] ?? 'unavailable (offline?)';
349
+ log(` npm latest pier-setup ${ls} pi-pier ${lp}`);
350
+ if (s.installer.version && s.piExt.version && s.installer.version !== s.piExt.version) {
351
+ log(`\n⚠ mismatch: installer ${s.installer.version} ≠ pi-pier ${s.piExt.version}`);
352
+ }
353
+ const target = s.latest['pi-pier'];
354
+ if (s.piExt.version && target && cmpSemver(s.piExt.version, target) < 0) {
355
+ log(`\nupdate available → ${target} npx pier-setup@latest update`);
356
+ }
357
+ }
358
+
359
+ function update() {
360
+ checkEnv();
361
+ const me = readPkgVersion(ROOT);
362
+ const latestSetup = npmLatest('pier-setup');
363
+ if (latestSetup && me && cmpSemver(me, latestSetup) < 0) {
364
+ log(`⚠ installer ${me} < latest ${latestSetup} — re-run: npx pier-setup@latest update`);
365
+ }
366
+ const hmrDev = flags.has('--hmr-dev');
367
+ if (dev) {
368
+ log('dev mode: local link is live; pull the repo yourself. Refreshing boot-config only.');
369
+ writeBootConfig(hmrDev, true);
370
+ } else {
371
+ if (tryRun('pi', ['update', piSource()])) log(`✓ pi extension updated (${piSource()})`);
372
+ else if (tryRun('pi', ['install', piSource()])) log(`✓ pi extension installed (${piSource()})`);
373
+ else { console.error('✗ pi update/install failed'); log(` manual: pi update ${piSource()}`); process.exitCode = 1; }
374
+ if (tryRun('herdr', ['plugin', 'install', HERDR_SPEC, '--yes'])) log(`✓ herdr plugin updated (${HERDR_SPEC})`);
375
+ else { console.error('✗ herdr plugin update failed'); log(` manual: herdr plugin install ${HERDR_SPEC} --yes`); process.exitCode = 1; }
376
+ writeBootConfig(hmrDev, true);
377
+ }
378
+ log('');
379
+ version();
169
380
  }
170
381
 
171
- /* ── 三命令 ──────────────────────────────────────────────────────── */
172
382
  function install() {
173
383
  const hmrDev = flags.has('--hmr-dev');
174
384
  const force = flags.has('--force');
@@ -215,6 +425,8 @@ function uninstall() {
215
425
  log(`\npier uninstalled.${purge ? '' : ' (boot-config.json kept; use --purge to remove)'}`);
216
426
  }
217
427
 
218
- if (command === 'uninstall') uninstall();
219
- else if (command === 'update') { log('== uninstall =='); uninstall(); log('\n== install =='); install(); }
428
+ if (command === 'help' || flags.has('--help') || flags.has('-h')) usage();
429
+ else if (command === 'version' || flags.has('--version') || flags.has('-v')) version();
430
+ else if (command === 'uninstall') uninstall();
431
+ else if (command === 'update') update();
220
432
  else install();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pier-setup",
3
- "version": "0.2.0",
4
- "description": "One-shot installer for pier: registers the pi-pier extension with pi and the pier.workbench plugin with herdr (install / uninstall / update)",
3
+ "version": "0.2.2",
4
+ "description": "One-shot installer for pier: install / update / version / uninstall for pi-pier + pier.workbench",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
@@ -33,7 +33,7 @@
33
33
  "scripts": {
34
34
  "test": "node --test \"packages/*/test/*.test.ts\"",
35
35
  "test:mutation": "stryker run",
36
- "prepare": "git config core.hooksPath .githooks"
36
+ "prepare": "node ./install.mjs --prepare"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@deepseek-ai/cordis-plugin-group": "^1.0.1",