Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ dist/
dist-extensions/
releases/

# 临时目录与日志(.tmp* 覆盖 .tmp / .tmp-verify / .tmp-check 等全部临时形态)
.tmp*/
# 临时目录与日志(.tmp* 同时覆盖 .tmp / .tmp-verify 这类目录与 .tmp-xxx.mjs 这类文件)
.tmp*
*.log

# 本地验证用的 App 数据目录(工具实跑时 ctx.dataDir 落在仓库内,会产生签名 shim 等运行期文件)
/.nonexistent-data/

# 内嵌运行时:二进制不入库,按 vendor/sources.json 声明式重建(scripts/fetch-vendor.mjs)。
# 二进制统一收在 vendor/bin/ 下,与元数据分层——所以这里只需一行,不必逐条翻案。
# 元数据在 vendor/ 根:sources.json(声明)与 hana-app-sdk/(SDK 快照,@hana/app-sdk 不在
Expand Down
119 changes: 65 additions & 54 deletions README.md

Large diffs are not rendered by default.

41 changes: 38 additions & 3 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ reach the artifact are not listed.
| react-is | 19.2.8 | MIT License | 构建期内联 |
| use-sync-external-store | 1.6.0 | MIT License | 构建期内联 |
| openpgp(OpenPGP.js) | 6.3.2 | GNU LGPL v3.0 or later | 随包独立库(运行期加载) |
| zod | 4.6.5 | MIT License | 随包独立库(运行期加载) |

## Bundled at build time

Expand Down Expand Up @@ -70,11 +71,18 @@ These are inlined into `ui/settings.bundle.js` at build time. Their original pac

### openpgp(OpenPGP.js) 6.3.2

- Purpose: 签名与验签(设置页的密钥生成、`githana(action="commit")` 的 `gpgsig`)—— 顶替原先随包的 GnuPG。
- Purpose: 签名与验签(设置页的密钥生成、`githana_git(action="commit")` 的 `gpgsig`)—— 顶替原先随包的 GnuPG。
- Form: 一份**独立、可替换的库文件** `bin/openpgp.min.mjs`(上游 npm 发行物 `dist/node/openpgp.min.mjs` 原样未修改);由 `bin/app.mjs` 与 `bin/gpg.shim.mjs` 在**运行期**以相对路径 `import` 加载。构建期由 `scripts/build-app.mjs` 从 `node_modules/openpgp/dist/node/` 投影进来,并以 sha256 断言与上游逐字节相同。npm 包自身(含其 `.map` / 类型文件)不随包。
- Source: https://github.com/openpgpjs/openpgpjs(原样未修改)
- License: GNU LGPL v3.0 or later(正文见本文件末尾)

### zod 4.6.5

- Purpose: 工具入参的单一来源(`githana_git` / `githana_pr` / `githana_api` 各动词的字段定义):运行期由它校验参数,模型面的 `parameters` 也由 `z.toJSONSchema` 从同一份定义生成。
- Form: 一份**独立、可替换的库文件** `bin/zod.min.mjs`;由 `bin/app.mjs` 在**运行期**以相对路径 `import` 加载。构建期由 `scripts/build-entries.mjs` 用 rspack 从 `node_modules/zod` 打成压缩后的单文件(发行物本体是上百个 ESM 模块的目录树,不宜以目录形态随包)。npm 包自身(含其 `src` / 类型文件)不随包。
- Source: https://github.com/colinhacks/zod
- License: MIT License(正文见本文件末尾)

## Runtime not shipped with this package

- **git**: 宿主 Hana 自带 `<HANA_ROOT>/resources/git`(Git for Windows),App 通过
Expand All @@ -86,8 +94,9 @@ These are inlined into `ui/settings.bundle.js` at build time. Their original pac
## LGPL component and this project

包内只有 **OpenPGP.js**(`bin/openpgp.min.mjs`)是 LGPL-3.0-or-later。其余代码分两类:
本项目自己写的部分是 **MPL-2.0**;另有若干第三方包在**构建期被内联**进设置页产物
(Apache-2.0 与 MIT,逐项见上表),它们各按自己的条款走,本项目的 MPL-2.0 不覆盖它们。
本项目自己写的部分是 **MPL-2.0**;另有若干第三方包是 **Apache-2.0 与 MIT**(逐项见上表)——
一部分在**构建期被内联**进设置页产物,**zod** 则以独立库文件 `bin/zod.min.mjs` 随包。
它们各按自己的条款走,本项目的 MPL-2.0 不覆盖它们。
OpenPGP.js 与本项目的代码在同一个交付树里共存,但是**独立的作品**:

- 本项目的代码与 OpenPGP.js **不共享任何源文件**:它作为一份**独立、可替换的库文件**
Expand Down Expand Up @@ -360,6 +369,32 @@ SOFTWARE.

--------------------------------------------------------------------------------

MIT License (Copyright (c) 2025 Colin McDonnell)

MIT License

Copyright (c) 2025 Colin McDonnell

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

--------------------------------------------------------------------------------

GNU Lesser General Public License v3.0

GNU LESSER GENERAL PUBLIC LICENSE
Expand Down
32 changes: 20 additions & 12 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
* 形态:宿主在隔离的 AppHost 子进程里加载本文件并调用 apply(ctx)(入口契约兼容具名
* apply / default.apply / 默认函数,两种都导出)。apply 完成注册后立即返回,不等任何长活。
*
* 工具面是两个入口:githana(写面)与 githana_read(只读面)。收敛前是 7 个平铺工具,
* 其中 gh_pr 把读与写放在一起,整体只能声明 external_side_effect,读也要过审;拆成两面
* 后只读动作拿到独立免审通道(见 tools/githana-read.js 头注与随包手册)。
* 工具面按 gh 的组分成名词工具:githana_git(本地 git)/ githana_repo(仓库)/ githana_pr
* (Pull Request)/ githana_issue(Issue)/ githana_label(标签)/ githana_api(任意 REST 与
* GraphQL 端点)。收敛前是「读写两面 + 一张平表 action」——`method` 与 `body` 在平表里同键
* 不同义;按名词分家后每个工具一张自己的参数表,碰撞消失(见 spec gh-cli-parity D1 / D2 与
* tools/lib/surface.js 的装配路径)。
*
* 与 v1「full-access 插件」的差别(本次重制的核心):
* - v1:manifest 无 contributes.tools,宿主自动扫描 tools/*.js 注册;entry 只挂卡路由。
* - v2:没有目录扫描,entry 必须在 apply 里逐个 ctx.tools.register;工具名全局唯一(无前缀)。
* 因此本文件把 tools/ 下的模块装配成注册表,execute 统一转成 v2 的「单参数调用」,
* 因此本文件把 tools/ 下的入口模块装配成注册表,execute 统一转成 v2 的「单参数调用」,
* 并把 App 运行上下文(dataDir / 安装目录)以第二个参数喂给既有的 v1 工具实现,
* 让 tools/ 与 lib/ 的代码基本零改动复用(见 tools/lib/context.js 的契约)。
*
Expand All @@ -36,8 +38,12 @@
* 后端落 dataDir(lib/secret.js),经进程缓存供工具同步读;剩余有效期不足时在
* initToolContext 里自动用 refresh_token 换新(lib/oauth.js)。
*/
import * as githana from "./tools/githana.js";
import * as githanaRead from "./tools/githana-read.js";
import * as githanaGit from "./tools/githana-git.js";
import * as githanaRepo from "./tools/githana-repo.js";
import * as githanaPr from "./tools/githana-pr.js";
import * as githanaIssue from "./tools/githana-issue.js";
import * as githanaLabel from "./tools/githana-label.js";
import * as githanaApi from "./tools/githana-api.js";

// 路由模块放 lib/routes/:顶层 routes/ 是 v2 保留目录(形态互斥的另一种路由源),
// 本项目统一用 ctx.routes.register 单 bundle 形态,故顶层不放 routes/。
Expand All @@ -61,19 +67,21 @@ function resolveInstallDir() {
}

/** 工具注册表(顺序即注册顺序;name 全局唯一,v2 不加前缀)。
* 两个入口:githana(写面)与 githana_read(只读面,sessionPermission { readOnly: true })。
* action 表与路由在 tools/lib/surface.js,实现按 action 拆在 tools/actions/。
* 本批六个名词工具:githana_git(本地 git)/ githana_repo(仓库)/ githana_pr(Pull Request)
* / githana_issue(Issue)/ githana_label(标签)/ githana_api(逃生门)。
* action 表与路由在 tools/lib/surface.js,跨工具的动词索引在 tools/lib/verbs.js,
* 实现按动词拆在 tools/actions/{git,repo,pr,issue,label}/。
* 下批(1c):githana_release / githana_run / githana_workflow / githana_cache。
* GPG 的生成与公钥查看不在工具面(设置页动作,见 lib/routes/actions.js)。 */
const TOOLS = [githana, githanaRead];
const TOOLS = [githanaGit, githanaRepo, githanaPr, githanaIssue, githanaLabel, githanaApi];

/**
* 把 v1 形态的 sessionPermission 收敛成跨 RPC 边界可传的纯数据字段。
* 跨进程只认 readOnly / kind / auto / description / sideEffect,函数字段过不来;
* 跨进程只认 kind / auto / description / sideEffect 这些纯数据,函数字段过不来;
* 这里主动剥掉 describeSideEffect 等函数,避免宿主把整条声明当非法降级。
*/
function plainPermission(perm) {
if (!perm || typeof perm !== "object") return undefined;
if (perm.readOnly === true) return { readOnly: true };
const out = {};
if (typeof perm.kind === "string") out.kind = perm.kind;
if (typeof perm.auto === "string") out.auto = perm.auto;
Expand Down Expand Up @@ -106,7 +114,7 @@ function toToolResult(value) {
}

/**
* App v2 主入口。注册 2 个工具 + 三条后端路由后立即返回;返回 disposer 供卸载/重载收尾。
* App v2 主入口。注册 6 个工具 + 三条后端路由后立即返回;返回 disposer 供卸载/重载收尾。
*/
export function apply(ctx) {
const dataDir = ctx && typeof ctx.dataDir === "string" && ctx.dataDir ? ctx.dataDir : null;
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"@rspack/core": "^2.2.3",
"openpgp": "6.3.2",
"react": "19.2.4",
"react-dom": "19.2.4"
"react-dom": "19.2.4",
"zod": "4.6.5"
},
"dependencies": {
"@hana/app-sdk": "file:./vendor/hana-app-sdk/hana-app-sdk.tgz"
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 15 additions & 6 deletions scripts/build-app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
* manifest.json / LICENSE / NOTICE
* bin/ ← 入口(app.mjs)+ REST worker(rest.worker.mjs)+ 签名 shim(gpg.shim.mjs)
* + OpenPGP.js 库文件(openpgp.min.mjs,上游 dist 原样,两个入口运行期 import)
* 三个入口均为压缩后的单入口 ESM;库文件是唯一例外(合规要求原样)。
* + zod 库文件(zod.min.mjs,由 build-entries 从 node_modules/zod 打成单文件)
* 三个入口均为压缩后的单入口 ESM;库文件是唯一例外(openpgp 是上游原样,zod 是打包产物)。
* assets/
* skills/ ← 随包 SKILL 手册(skills/githana/SKILL.md,App 根目录约定)
* ui/ ← 壳页 + 设置页产物平铺(settings.html / settings.bundle.js / .css),**不含 ui/src/**
Expand Down Expand Up @@ -47,8 +48,9 @@
* 不含:scripts/(开发件)、根上的 app.js / lib/ / tools/(已 bundle 进 bin/app.mjs,
* 但两个 spawn 出去的独立入口例外:tools/lib/gpg.shim.mjs 与 tools/lib/rest.worker.mjs
* 均由 build-entries.mjs 打包为 bin/*.mjs,见下)、
* 以及 OpenPGP.js 库文件(bin/openpgp.min.mjs,从上游 npm dist 原样拷入):它不进源码树,
* 由本脚本从 node_modules/openpgp/dist/node/ 投影进来,并以 npm 包的 integrity 断言字节一致。
* 以及两份库文件(bin/openpgp.min.mjs 与 bin/zod.min.mjs):openpgp 从上游 npm dist 原样拷入,
* 由本脚本从 node_modules/openpgp/dist/node/ 投影进来,并以 npm 包的 integrity 断言字节一致;
* zod 由 build-entries.mjs 从 node_modules/zod 打成单文件,本脚本只断言它在位并报体积。
* build-deps/(SDK tarball,已迁到 vendor/hana-app-sdk/)、node_modules/、.github/、README.md、
* SPECS.md、specs/、pnpm-* 清单、releases/、dist-extensions/;以及整个 `vendor/`
* (构建期缓存与声明,运行时无消费者)。
Expand Down Expand Up @@ -155,10 +157,11 @@ function main() {
total += copyInto(src, join(DIST, name));
}

// 2a) bin/ 下的三个入口与一份库文件。入口均由构建脚本产出(见上);库文件从上游投影。
// 2a) bin/ 下的三个入口与两份库文件。入口均由构建脚本产出(见上);库文件从上游投影。
// OpenPGP.js 库文件(openpgp.min.mjs)也从这里投影:两个入口都运行期 import 它。
// zod 库文件(zod.min.mjs)由 build-entries.mjs 打包产出,这里只断言它在位。
{
for (const name of ["app.mjs", "gpg.shim.mjs", "rest.worker.mjs"]) {
for (const name of ["app.mjs", "gpg.shim.mjs", "rest.worker.mjs", "zod.min.mjs"]) {
if (!existsSync(join(DIST, "bin", name))) {
console.error(`[build-app] 缺 dist/bin/${name}(构建脚本未产出)`);
process.exit(1);
Expand Down Expand Up @@ -186,8 +189,14 @@ function main() {
process.exit(1);
}
log(`bin/${OPENPGP_LIB} 就位(sha256 ${libDigest.slice(0, 12)}…,OpenPGP.js 运行期共享库)`);

// zod 库文件:由 build-entries.mjs 用 rspack 从 node_modules/zod 打成单文件,运行期由
// app.mjs 相对 import 加载(见 build-entries 头注)。这里只报体积——它是产物、不是上游原件,
// 没有"与上游逐字节相同"可断言;可复现性由 rspack 单入口配置保证(连跑两次字节相同)。
const zodLib = join(DIST, "bin", "zod.min.mjs");
log(`bin/zod.min.mjs 就位(${(statSync(zodLib).size / 1024).toFixed(1)} KB,zod 运行期共享库)`);
}
log(`契约件与目录就位(${DELIVERY_FILES.length} 文件 + ${DELIVERY_DIRS.length} 目录)+ bin/app.mjs + bin/gpg.shim.mjs + bin/rest.worker.mjs + bin/openpgp.min.mjs`);
log(`契约件与目录就位(${DELIVERY_FILES.length} 文件 + ${DELIVERY_DIRS.length} 目录)+ bin/app.mjs + bin/gpg.shim.mjs + bin/rest.worker.mjs + bin/openpgp.min.mjs + bin/zod.min.mjs`);

// 2b) 内嵌运行时二进制:**从仓库的 vendor/bin/ 直接投影**到交付树的 bin/<platform>/。
// vendor/ 本身不进交付面(它是构建期缓存与声明),所以这里读源树、只把二进制平铺过去,
Expand Down
Loading
Loading