glasspane-install 1.14.1 → 1.15.1

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 (2) hide show
  1. package/cli.js +70 -7
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -3,7 +3,8 @@
3
3
  * glasspane-install — 一键安装 CLI(零第三方依赖,Node >= 18)。
4
4
  *
5
5
  * 流程:terminal onboarding 横幅 → 环境预检(node/swift/git/open)→ 定位项目
6
- * 根目录 → npm install(workspaces)→ 编译 TS(kernel → mcp-shell)→ swift
6
+ * 根目录 → npm 依赖(有 package-lock.json 走 npm ci,见 npmDepsCommand)→ 编译 TS
7
+ * (kernel → mcp-shell)→ swift
7
8
  * build -c release(engine)→ make-app.sh 打包**并签名**两个 .app → 安置到
8
9
  * ~/Applications → 以 bundle 内可执行注册 launchd 自启并启动 daemon → 打开
9
10
  * GlassPane.app 设置面板进入权限引导(授权对象是 daemon,见 P1 v1.2 §11)→
@@ -69,7 +70,7 @@ export const INSTALL_SH_URL =
69
70
  /** 发布线锚点(版本真源 = 根 package.json,由 scripts/set-version.mjs 统一改写,
70
71
  * 勿手改):npx 形态下本地没有仓库时,引导 clone 的就是这个 tag,与 install.sh
71
72
  * 的 `GLASSPANE_RELEASE` 同值——两条一键入口必须拿到同一份源码。 */
72
- export const RELEASE_VERSION = '1.14.1'
73
+ export const RELEASE_VERSION = '1.15.1'
73
74
  /** 发布 ref(tag 名)。GLASSPANE_REF 环境变量可覆盖(追主干用 `main`)。 */
74
75
  export const REPO_REF = `v${RELEASE_VERSION}`
75
76
 
@@ -1369,10 +1370,62 @@ export function updateGuidanceText(update) {
1369
1370
  // 执行层
1370
1371
  // ---------------------------------------------------------------------------
1371
1372
 
1373
+ /** 装之前"这一次到底验没验签"的门(纯函数,可单测)。
1374
+ *
1375
+ * 从前这里是 `pinRef !== 'main' && repoHasRef(...)` 一条静默的 `&&`:条件不满足时整块被
1376
+ * 跳过、安装照旧跑完,而输出里没有一句"没验过"。`--repo <本地目录>` 与 `GLASSPANE_REF=main`
1377
+ * 都是文档里在用的路径,那些树本来就没有发布 tag 可验——所以这里**不拒**(拒会把受支持的
1378
+ * 离线/开发安装打断),但必须把"没验签"这件事说出来:担保没生效时,屏幕不能装作担保生效。
1379
+ *
1380
+ * 唯一真拒的是形状不安全的值:它会被原样拼进 `git verify-tag` / `git rev-parse`,以 `-`
1381
+ * 开头是选项注入的入口,含空白是 `ext::sh -c …` 那一类。`validateGitRef` 过去只守 clone
1382
+ * 那一条路,这里是第二条入口。 */
1383
+ export function tagVerifyGate({ pinRef, hasRef }) {
1384
+ const check = validateGitRef(pinRef)
1385
+ if (!check.ok) {
1386
+ return {
1387
+ action: 'refuse',
1388
+ text: `GLASSPANE_REF 形状不安全,安装中止(不是"跳过验签继续装"):${check.error}`,
1389
+ }
1390
+ }
1391
+ if (pinRef === 'main') {
1392
+ return {
1393
+ action: 'skip',
1394
+ text: '未做 GPG 验签:GLASSPANE_REF=main,分支头没有发布 tag 的签名可验。要装被签的发布物就别设这个环境变量。',
1395
+ }
1396
+ }
1397
+ if (!hasRef) {
1398
+ return {
1399
+ action: 'skip',
1400
+ text: `未做 GPG 验签:这棵树里找不到 ${pinRef}(本地 --repo 或浅取种的树本来就没有那个 tag)。`
1401
+ + '本轮安装没有任何签名担保——装的是你交给它的那棵树。',
1402
+ }
1403
+ }
1404
+ return { action: 'verify', text: null }
1405
+ }
1406
+
1407
+ /** 钉住的 tag 那棵树**依赖安装**用哪条命令:仓库里有 `package-lock.json` 就走 `npm ci`。
1408
+ *
1409
+ * WHY(不是口味问题)。CI 的根 workspace 门(`.github/workflows/ci.yml` 的 install-gate:
1410
+ * `npm ci`)、发布作业(`release.yml` 的 `npm ci --no-audit --no-fund`)与 updater 的树守卫
1411
+ * 量的都是**提交过的那份 lockfile**——"CI 绿"这句 attest 说的就是那棵树。安装器却跑
1412
+ * `npm install`,它会重新解析浮动传递依赖:装出来的 app 依赖树可以与被 attest 的那棵不同,
1413
+ * 而屏幕上仍然写着"CI 全绿的发布 tag"。判据放成纯函数是为了可测——这一段从前埋在
1414
+ * `install()` 里,没有任何一条测试能指到它(与 `tagVerifyDecision` 同一条理由)。
1415
+ * 没有 lockfile 的树(浅 clone 之外的手工目录、老 tag)仍要能装,故保留 `npm install` 退路。 */
1416
+ export function npmDepsCommand({ hasLockfile = false } = {}) {
1417
+ return hasLockfile ? ['ci', '--no-audit', '--no-fund'] : ['install']
1418
+ }
1419
+
1372
1420
  /** 前台运行命令并透传输出;非零退出码 → reject 携带清晰描述。 */
1373
1421
  export function run(cmd, args, { cwd } = {}) {
1374
1422
  return new Promise((resolve, reject) => {
1375
- const child = spawn(cmd, args, { cwd, stdio: 'inherit', shell: process.platform === 'win32' })
1423
+ // `shell: true` 在任何平台上都是"把 cmd 与 args 拼成一条 shell 命令行",而这个产品的
1424
+ // 一切输入都来自外部(仓库 URL、ref、目录)。它是树里唯一一处 shell:true,而 macOS 上
1425
+ // 一个用 `win32` 当条件的开关永远为真地不起作用——留着只会让人以为 Windows 是被支持的
1426
+ // 交付面(README 与 install.sh 都写明 macOS)。要真支持 Windows,正确做法是把
1427
+ // `npm.cmd` 解析成绝对路径后仍走 argv,而不是打开 shell 解析。
1428
+ const child = spawn(cmd, args, { cwd, stdio: 'inherit' })
1376
1429
  child.on('error', (error) => reject(new Error(`${cmd} 启动失败:${error.message}`)))
1377
1430
  child.on('close', (code) => {
1378
1431
  if (code === 0) {
@@ -2152,8 +2205,12 @@ export async function install({
2152
2205
  rootDir = plan.targetDir
2153
2206
  }
2154
2207
 
2155
- // 发布 tag GPG 验签:动作怎么落由 `tagVerifyDecision` 判(纯函数,有单测)。
2156
- if (pinRef !== 'main' && repoHasRef(rootDir, pinRef)) {
2208
+ // 发布 tag GPG 验签:先过"这次到底验不验"的门(形状/存在性),再判动作,两条判据都是纯函数。
2209
+ const gate = tagVerifyGate({ pinRef, hasRef: repoHasRef(rootDir, pinRef) })
2210
+ if (gate.action === 'refuse') throw new Error(gate.text)
2211
+ if (gate.action === 'skip') {
2212
+ printStep(paint(gate.text, 'yellow'))
2213
+ } else {
2157
2214
  const sig = verifyCloneTagGpg({ repoDir: rootDir, ref: pinRef })
2158
2215
  // 先判"签名对不对",再判"要装的是不是被签的那棵树"——两问分开,绿字才只在对得上时出现。
2159
2216
  const verdict = tagVerifyAgainstTree({
@@ -2198,8 +2255,14 @@ export async function install({
2198
2255
  printStep(`日志与 socket 目录就位:${logDir.dir}`)
2199
2256
 
2200
2257
  if (!options.skipBuild) {
2201
- printStep('安装 npm 依赖(workspaces: kernel / mcp-shell / installer)……')
2202
- await run('npm', ['install'], { cwd: rootDir })
2258
+ // 装的是钉住的 tag 那棵树:有提交过的 lockfile 就必须 `npm ci`,与被 CI/release 门
2259
+ // attest 的依赖树同一份(判据与理由见 npmDepsCommand)。
2260
+ const hasLockfile = fs.existsSync(path.join(rootDir, 'package-lock.json'))
2261
+ const depsCmd = npmDepsCommand({ hasLockfile })
2262
+ printStep(hasLockfile
2263
+ ? '安装 npm 依赖(workspaces: kernel / mcp-shell / installer)——按提交过的 package-lock.json 走 npm ci……'
2264
+ : '安装 npm 依赖(workspaces: kernel / mcp-shell / installer)——该树没有 package-lock.json,退回 npm install(依赖树未经 CI 那一份 lock 的 attest)……')
2265
+ await run('npm', depsCmd, { cwd: rootDir })
2203
2266
 
2204
2267
  printStep('编译 kernel……')
2205
2268
  await run('npm', ['run', 'build', '--workspace', '@iterate/kernel'], { cwd: rootDir })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glasspane-install",
3
- "version": "1.14.1",
3
+ "version": "1.15.1",
4
4
  "description": "GlassPane one-command installer: terminal onboarding → install deps → build TS + Swift engine → start daemon → open settings GUI for permission guide",
5
5
  "type": "module",
6
6
  "license": "MIT",