feat: publish Curios directly through the Git source
This commit is contained in:
@@ -5,5 +5,6 @@ target/
|
|||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
*.curio
|
*.curio
|
||||||
|
!/source/packages/**/*.curio
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/curios/*/backend/
|
/curios/*/backend/
|
||||||
|
|||||||
+3
-3
@@ -38,7 +38,7 @@ pnpm run serve
|
|||||||
|
|
||||||
完整步骤、覆盖矩阵与当前限制见 [测试指南](testing.md)。
|
完整步骤、覆盖矩阵与当前限制见 [测试指南](testing.md)。
|
||||||
|
|
||||||
插件各自通过 `curio.json.version` 管理版本;公开服务契约版本与底座兼容范围分别维护。历史发布索引只追加版本、禁止覆盖同版本产物,详见 [版本与发布](versioning.md)。`pnpm run release:prepare --base-url <HTTPS 分发目录>` 可准备保留历史的发布材料,`pnpm run test:release` 验证这些约束。
|
插件各自通过 `curio.json.version` 管理版本;公开服务契约版本与底座兼容范围分别维护。历史发布索引只追加版本、禁止覆盖同版本产物,详见 [版本与发布](versioning.md)。`pnpm run release:prepare` 将安装包与索引准备到 `source/`,提交并推送后即可通过 Git 仓库源安装。`pnpm run test:release` 验证版本约束。
|
||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ pnpm run serve
|
|||||||
|
|
||||||
1. 各插件独立声明版本、平台、依赖与权限。
|
1. 各插件独立声明版本、平台、依赖与权限。
|
||||||
2. 构建并执行 trove curio check / pack。macOS 原生后端使用 Mac Runner 构建。
|
2. 构建并执行 trove curio check / pack。macOS 原生后端使用 Mac Runner 构建。
|
||||||
3. 先上传不可变的版本包,再更新索引中的下载 URL、大小和 SHA-256。
|
3. 执行 `pnpm run release:prepare`,检查 `source/index.json` 的新增版本与 `source/packages/` 中的不可变版本包。
|
||||||
4. 最后原子发布 index.json。私有 Git 仓库不等于私有下载源;下载地址应符合分发对象的访问权限。
|
4. 将索引和安装包在同一提交中推送,再使用 Git 仓库地址完成远程安装与运行验证。私有仓库沿用本机 Git 认证。
|
||||||
|
|
||||||
官网:https://trove.render.ink
|
官网:https://trove.render.ink
|
||||||
|
|||||||
+5
-4
@@ -99,17 +99,18 @@ pnpm run verify
|
|||||||
|
|
||||||
## 远程软件源验收
|
## 远程软件源验收
|
||||||
|
|
||||||
发布完成后,必须使用底座实际访问的索引 URL 单独验收:
|
发布完成后,必须使用底座实际访问的 Git 仓库单独验收:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
TROVE_APP_DIR=/absolute/path/to/app pnpm run verify \
|
TROVE_APP_DIR=/absolute/path/to/app pnpm run verify \
|
||||||
--remote-source https://downloads.example/source/index.json \
|
--remote-source git@git.render.ink:trove/curios.git \
|
||||||
|
--source-kind git \
|
||||||
--source-id trove-official
|
--source-id trove-official
|
||||||
```
|
```
|
||||||
|
|
||||||
此模式不读取本地 `.curio`:在新的临时目录中通过真实 PackageManager 从远程刷新索引、下载并安装其中的每个版本,检查 UI 入口,启动安装后的原生后端,再执行 consumer → provider 调用。结果写入 `dist/remote-verification.json`;失败时非零退出,不能视为安装通过。
|
此模式不读取工作区的 `.curio`:在新的临时目录中通过真实 PackageManager 获取远程仓库、读取索引、安装该提交中的每个版本,检查 UI 入口,启动安装后的原生后端,再执行 consumer → provider 调用。结果写入 `dist/remote-verification.json`;失败时非零退出,不能视为安装通过。独立 HTTP 源可使用 `--source-kind http` 并传入索引 URL。
|
||||||
|
|
||||||
当前运行探针适用于生产源中的首批验收插件;后续新增产品插件时,应为它们补充对应的运行验收。故障源包含故意无法启动的后端,不能以全部启动成功作为标准。示例域名须替换为真实分发地址;直接读取私有 Gitea 仓库需要登录,推送源码本身不代表已发布软件源。下载可见性和历史发布管理见 [版本与发布](versioning.md)。
|
当前运行探针适用于生产源中的首批验收插件;后续新增产品插件时,应为它们补充对应的运行验收。故障源包含故意无法启动的后端,不能以全部启动成功作为标准。仓库结构、认证和历史发布管理见 [版本与发布](versioning.md)。
|
||||||
|
|
||||||
## 当前能力边界
|
## 当前能力边界
|
||||||
|
|
||||||
|
|||||||
+18
-13
@@ -32,12 +32,11 @@ packages/dev.trove.qa.self/1.0.0/x86_64-apple-darwin.curio
|
|||||||
先修改需要发布的 Manifest,执行 `pnpm run pack` 和验证。`dist/index.json` 是可反复生成的本地开发索引;不能把它当作历史发布记录。
|
先修改需要发布的 Manifest,执行 `pnpm run pack` 和验证。`dist/index.json` 是可反复生成的本地开发索引;不能把它当作历史发布记录。
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# 必须是确定的、可由底座访问的 HTTPS 分发目录;示例域名须替换。
|
pnpm run release:prepare
|
||||||
pnpm run release:prepare --base-url https://downloads.example/source/
|
|
||||||
pnpm run test:release
|
pnpm run test:release
|
||||||
```
|
```
|
||||||
|
|
||||||
命令会将本次打包的常规插件合并到生产源 `source/index.json`(源 ID 为 `trove-official`),并在 `dist/release/` 准备同目录结构的上传材料。故障注入插件和无效包不会进入生产源。未确定分发地址时不预先填入不可访问的下载地址。
|
命令会将本次打包的常规插件合并到生产源 `source/index.json`(源 ID 为 `trove-official`),并把不可变安装包复制到 `source/packages/`。索引中的包地址相对于 `source/`,索引与包在同一 Git 提交中发布。`dist/release/` 保留本次发布材料副本。故障注入插件和无效包不会进入生产源。
|
||||||
|
|
||||||
校验规则:
|
校验规则:
|
||||||
|
|
||||||
@@ -48,21 +47,27 @@ pnpm run test:release
|
|||||||
- 重复准备相同材料不会改变首次发布日期。
|
- 重复准备相同材料不会改变首次发布日期。
|
||||||
- 先完整校验本次所有条目,再复制产物和更新本地索引。
|
- 先完整校验本次所有条目,再复制产物和更新本地索引。
|
||||||
|
|
||||||
`--catalog <目录>` 可在隔离目录试运行。正式发布应将 `source/` 的历史索引提交回仓库。该脚本只准备本地材料,不表示上传或远程安装已经成功。
|
`--catalog <目录>` 可在隔离目录试运行。正式发布应将 `source/` 的历史索引与安装包提交回仓库。该脚本只准备本地材料,不表示推送或远程安装已经成功。
|
||||||
|
|
||||||
|
需要独立 HTTP 分发时,可显式传入 `--base-url <HTTPS 分发目录>` 生成绝对下载地址,并使用 `dist/release/` 上传;已有版本的 URL 仍不能覆盖。
|
||||||
|
|
||||||
## 发布顺序
|
## 发布顺序
|
||||||
|
|
||||||
1. 提交并推送源代码,记录 Git commit;需要时为各插件打独立 tag。
|
1. 更新需要发布的插件版本,构建并完成验证。
|
||||||
2. 构建和验证,准备发布索引;审阅本次新增的版本与产物。
|
2. 执行 `pnpm run release:prepare`;审阅本次新增的版本、架构、大小和 SHA-256。
|
||||||
3. 上传新安装包,拒绝覆盖已存在且内容不同的文件。
|
3. 将源码、`source/index.json` 和 `source/packages/` 在同一提交中推送;需要时为各插件打独立 tag。
|
||||||
4. 从底座实际使用的网络路径下载每个新增包,核对 SHA-256 和大小。
|
4. 从远程 Git 仓库刷新、安装并运行验证;不能用本地文件安装冒充远程源验收。
|
||||||
5. 最后原子替换远程索引,提交并推送仓库中的发布索引。
|
|
||||||
6. 从该远程源刷新、安装并运行验证;不能用本地文件安装冒充远程源验收。
|
|
||||||
|
|
||||||
发布工作串行进行;两种架构先各自构建,再将产物合并至同一版本。若更新 `shared/ui/` 或 `shared/backend/`,所有受影响且需要重新发布的插件都要增加插件版本,未受影响的插件不必修改。
|
发布工作串行进行;两种架构先各自构建,再将产物合并至同一版本。若更新 `shared/ui/` 或 `shared/backend/`,所有受影响且需要重新发布的插件都要增加插件版本,未受影响的插件不必修改。
|
||||||
|
|
||||||
## 私有源码与下载认证
|
## 添加仓库源
|
||||||
|
|
||||||
当前 `https://git.render.ink/trove/curios` 是私有源码仓库,匿名访问原始 JSON 会跳转到登录页。推送源代码本身不能使它成为可安装的软件源。
|
在 Trove「软件源 → 添加软件源」中填写:
|
||||||
|
|
||||||
Trove 当前 HTTP 软件源没有仓库登录凭据配置。生产分发可使用免登录的安装包/索引下载入口,或在底座支持私有软件源认证后使用受保护的入口;不要把访问令牌写进 Manifest、源码索引或下载 URL。源码仓库可保持私有,安装包的可见性单独决定。
|
- 显示名称:Trove 官方软件源
|
||||||
|
- 唯一 ID:`trove-official`
|
||||||
|
- 源类型:Git 仓库
|
||||||
|
- 仓库地址:`git@git.render.ink:trove/curios.git`
|
||||||
|
- 优先级:`0`
|
||||||
|
|
||||||
|
也支持 HTTPS 克隆地址 `https://git.render.ink/trove/curios.git`。私有仓库沿用本机 Git 的 SSH 密钥或 HTTPS credential helper;不要把访问令牌写进 Manifest、索引或 URL。添加后刷新,底座自动读取默认分支的 `source/index.json`,并固定该提交读取仓库内的安装包。用户不需要填写原始 JSON 地址或配置额外的静态下载站点。
|
||||||
|
|||||||
+194
-2
@@ -2,6 +2,198 @@
|
|||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"id": "trove-official",
|
"id": "trove-official",
|
||||||
"name": "Trove 官方软件源",
|
"name": "Trove 官方软件源",
|
||||||
"generatedAt": "2026-09-08T07:48:26.789Z",
|
"generatedAt": "2026-09-09T12:17:24.404Z",
|
||||||
"curios": []
|
"curios": [
|
||||||
|
{
|
||||||
|
"id": "dev.trove.qa.host",
|
||||||
|
"name": "01 · Host 能力检查",
|
||||||
|
"description": "UI、SDK 桥接、服务发现、存储、文件和桌面交互。",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"engine": {
|
||||||
|
"trove": ">=0.1.0 <1.0.0"
|
||||||
|
},
|
||||||
|
"releasedAt": "2026-09-09T12:17:24.404Z",
|
||||||
|
"artifacts": [
|
||||||
|
{
|
||||||
|
"target": "any",
|
||||||
|
"url": "packages/dev.trove.qa.host/1.0.0/any.curio",
|
||||||
|
"sha256": "15a644d1231b237092b3c4ed19ccece12e92e8f78aac681af2279150da65bad1",
|
||||||
|
"size": 20082
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dev.trove.qa.isolation",
|
||||||
|
"name": "02 · 数据隔离对照",
|
||||||
|
"description": "与 Host 检查使用相同存储键,验证插件间独立命名空间。",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"engine": {
|
||||||
|
"trove": ">=0.1.0 <1.0.0"
|
||||||
|
},
|
||||||
|
"releasedAt": "2026-09-09T12:17:24.404Z",
|
||||||
|
"artifacts": [
|
||||||
|
{
|
||||||
|
"target": "any",
|
||||||
|
"url": "packages/dev.trove.qa.isolation/1.0.0/any.curio",
|
||||||
|
"sha256": "8bfc084b75e91874b6c6c488c8703b2ccd5529a92f9f2cb56ab57f69c491e687",
|
||||||
|
"size": 20106
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dev.trove.qa.self",
|
||||||
|
"name": "03 · 原生后端检查",
|
||||||
|
"description": "UI → @self → 原生 Rust 后端 → Host;并发、取消、事件。",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"engine": {
|
||||||
|
"trove": ">=0.1.0 <1.0.0"
|
||||||
|
},
|
||||||
|
"releasedAt": "2026-09-09T12:17:24.404Z",
|
||||||
|
"artifacts": [
|
||||||
|
{
|
||||||
|
"target": "aarch64-apple-darwin",
|
||||||
|
"url": "packages/dev.trove.qa.self/1.0.0/aarch64-apple-darwin.curio",
|
||||||
|
"sha256": "328d39a3ce95d33eae8b1575c307a9aec8d3c5319e89c250b04c1389ecea5b5e",
|
||||||
|
"size": 346745
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dev.trove.qa.provider",
|
||||||
|
"name": "04 · 公共服务提供方",
|
||||||
|
"description": "无 UI,按需启动;20 秒空闲后退出。",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"engine": {
|
||||||
|
"trove": ">=0.1.0 <1.0.0"
|
||||||
|
},
|
||||||
|
"releasedAt": "2026-09-09T12:17:24.404Z",
|
||||||
|
"artifacts": [
|
||||||
|
{
|
||||||
|
"target": "aarch64-apple-darwin",
|
||||||
|
"url": "packages/dev.trove.qa.provider/1.0.0/aarch64-apple-darwin.curio",
|
||||||
|
"sha256": "726f0f47bf50294f9be067437995dbbb59b3b52017b95bff665273165ff2a46d",
|
||||||
|
"size": 327830
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dev.trove.qa.consumer",
|
||||||
|
"name": "05 · 跨插件调用",
|
||||||
|
"description": "依赖公共服务;验证发现、契约、事件与后端回调。",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"engine": {
|
||||||
|
"trove": ">=0.1.0 <1.0.0"
|
||||||
|
},
|
||||||
|
"releasedAt": "2026-09-09T12:17:24.404Z",
|
||||||
|
"artifacts": [
|
||||||
|
{
|
||||||
|
"target": "any",
|
||||||
|
"url": "packages/dev.trove.qa.consumer/1.0.0/any.curio",
|
||||||
|
"sha256": "503112dd4fc316e370c6f95fdca717f02f23ac68a02e751780a43a721dfb2bd3",
|
||||||
|
"size": 20079
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dev.trove.qa.optional",
|
||||||
|
"name": "06 · 可选依赖缺失",
|
||||||
|
"description": "缺失可选服务时 UI 仍可工作,调用返回 service.not_found。",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"engine": {
|
||||||
|
"trove": ">=0.1.0 <1.0.0"
|
||||||
|
},
|
||||||
|
"releasedAt": "2026-09-09T12:17:24.404Z",
|
||||||
|
"artifacts": [
|
||||||
|
{
|
||||||
|
"target": "any",
|
||||||
|
"url": "packages/dev.trove.qa.optional/1.0.0/any.curio",
|
||||||
|
"sha256": "ce01d0508b7b1610e835f87095b5f1e45ad1652c924ecb58d113efa4a3c47cc7",
|
||||||
|
"size": 20101
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dev.trove.qa.autostart",
|
||||||
|
"name": "07 · 随底座启动",
|
||||||
|
"description": "无 UI 的 app-start 后端;重启底座后检查状态和 PID。",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"engine": {
|
||||||
|
"trove": ">=0.1.0 <1.0.0"
|
||||||
|
},
|
||||||
|
"releasedAt": "2026-09-09T12:17:24.404Z",
|
||||||
|
"artifacts": [
|
||||||
|
{
|
||||||
|
"target": "aarch64-apple-darwin",
|
||||||
|
"url": "packages/dev.trove.qa.autostart/1.0.0/aarch64-apple-darwin.curio",
|
||||||
|
"sha256": "38605052d1c7fade9301ea4972b2668a94ab5e563058a1de63c8937b4717d5b7",
|
||||||
|
"size": 327826
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dev.trove.qa.upgrade",
|
||||||
|
"name": "08 · 升级与数据保留",
|
||||||
|
"description": "1.1.0 更新;显示版本并读取基线标记。",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.1.0",
|
||||||
|
"engine": {
|
||||||
|
"trove": ">=0.1.0 <1.0.0"
|
||||||
|
},
|
||||||
|
"releasedAt": "2026-09-09T12:17:24.404Z",
|
||||||
|
"artifacts": [
|
||||||
|
{
|
||||||
|
"target": "any",
|
||||||
|
"url": "packages/dev.trove.qa.upgrade/1.1.0/any.curio",
|
||||||
|
"sha256": "500493b4156c3f143c8bc685bad495a41e320f9d7b8346a5ea939141d037a0f2",
|
||||||
|
"size": 20033
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"engine": {
|
||||||
|
"trove": ">=0.1.0 <1.0.0"
|
||||||
|
},
|
||||||
|
"releasedAt": "2026-09-09T12:17:24.404Z",
|
||||||
|
"artifacts": [
|
||||||
|
{
|
||||||
|
"target": "any",
|
||||||
|
"url": "packages/dev.trove.qa.upgrade/1.0.0/any.curio",
|
||||||
|
"sha256": "834a1159d18b1d44b5aeb38cf9b60f5a1868df06cdc370d4d08026170e1bb19f",
|
||||||
|
"size": 20030
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10
-4
@@ -377,6 +377,7 @@ async fn main() -> Result<()> {
|
|||||||
return remote_source_check(
|
return remote_source_check(
|
||||||
args.get(2).context("remote source URL required")?,
|
args.get(2).context("remote source URL required")?,
|
||||||
args.get(3).context("remote source ID required")?,
|
args.get(3).context("remote source ID required")?,
|
||||||
|
args.get(4).map(String::as_str).unwrap_or("http"),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@@ -477,7 +478,12 @@ async fn main() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remote_source_check(url: &str, source_id: &str) -> Result<()> {
|
async fn remote_source_check(url: &str, source_id: &str, kind: &str) -> Result<()> {
|
||||||
|
let kind = match kind {
|
||||||
|
"git" => SourceKind::Git,
|
||||||
|
"http" => SourceKind::Http,
|
||||||
|
_ => anyhow::bail!("Remote source kind must be git or http"),
|
||||||
|
};
|
||||||
let sandbox = tempfile::Builder::new()
|
let sandbox = tempfile::Builder::new()
|
||||||
.prefix("trove-remote-qa-")
|
.prefix("trove-remote-qa-")
|
||||||
.tempdir_in("/tmp")?;
|
.tempdir_in("/tmp")?;
|
||||||
@@ -497,7 +503,7 @@ async fn remote_source_check(url: &str, source_id: &str) -> Result<()> {
|
|||||||
packages.add_source(SourceConfig {
|
packages.add_source(SourceConfig {
|
||||||
id: source_id.into(),
|
id: source_id.into(),
|
||||||
name: source_id.into(),
|
name: source_id.into(),
|
||||||
kind: SourceKind::Http,
|
kind,
|
||||||
index: url.into(),
|
index: url.into(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
priority: 0,
|
priority: 0,
|
||||||
@@ -599,8 +605,8 @@ async fn remote_source_check(url: &str, source_id: &str) -> Result<()> {
|
|||||||
sandbox.close()?;
|
sandbox.close()?;
|
||||||
let failed = rows.iter().filter(|row| row["status"] == "fail").count();
|
let failed = rows.iter().filter(|row| row["status"] == "fail").count();
|
||||||
emit(json!({
|
emit(json!({
|
||||||
"schemaVersion":1,"sourceUrl":url,"sourceId":source_id,"target":target(),
|
"schemaVersion":1,"sourceUrl":url,"sourceId":source_id,"sourceKind":kind,"target":target(),
|
||||||
"scope":"Remote HTTP index and packages via real PackageManager; installed native backends via real UDS. Desktop UI rendering is not included.",
|
"scope":"Remote source and packages via real PackageManager; installed native backends via real UDS. Desktop UI rendering is not included.",
|
||||||
"passed":rows.len()-failed,"failed":failed,"temporaryDataRemoved":true,"results":rows,
|
"passed":rows.len()-failed,"failed":failed,"temporaryDataRemoved":true,"results":rows,
|
||||||
}));
|
}));
|
||||||
ensure!(failed == 0, "Remote source verification failed");
|
ensure!(failed == 0, "Remote source verification failed");
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ const { values } = parseArgs({ options: {
|
|||||||
'base-url': { type: 'string' },
|
'base-url': { type: 'string' },
|
||||||
catalog: { type: 'string', default: 'source' },
|
catalog: { type: 'string', default: 'source' },
|
||||||
} });
|
} });
|
||||||
if (!values['base-url']) throw new Error('Usage: pnpm run release:prepare --base-url https://downloads.example/source/');
|
const base = values['base-url'] ? new URL(values['base-url']) : null;
|
||||||
const base = new URL(values['base-url']);
|
if (base && (base.protocol !== 'https:' || base.username || base.password || base.search || base.hash)) {
|
||||||
if (base.protocol !== 'https:' || base.username || base.password || base.search || base.hash) {
|
|
||||||
throw new Error('Publication base must be an HTTPS directory URL without credentials, query or fragment');
|
throw new Error('Publication base must be an HTTPS directory URL without credentials, query or fragment');
|
||||||
}
|
}
|
||||||
if (!base.pathname.endsWith('/')) base.pathname += '/';
|
if (base && !base.pathname.endsWith('/')) base.pathname += '/';
|
||||||
const catalogDirectory = path.resolve(root, values.catalog);
|
const catalogDirectory = path.resolve(root, values.catalog);
|
||||||
const publishDirectory = path.join(output, 'release');
|
const publishDirectory = path.join(output, 'release');
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
@@ -41,18 +40,36 @@ for (const entry of entries) {
|
|||||||
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
||||||
if (sha256 !== entry.artifact.sha256 || bytes.length !== entry.artifact.size) throw new Error(`Build metadata doesn't match ${entry.file}; run pack again`);
|
if (sha256 !== entry.artifact.sha256 || bytes.length !== entry.artifact.size) throw new Error(`Build metadata doesn't match ${entry.file}; run pack again`);
|
||||||
const relative = artifactPath(entry.manifest, entry.artifact.target);
|
const relative = artifactPath(entry.manifest, entry.artifact.target);
|
||||||
const url = new URL(relative.split('/').map(encodeURIComponent).join('/'), base).href;
|
const url = base ? new URL(relative.split('/').map(encodeURIComponent).join('/'), base).href : relative;
|
||||||
const spec = specifications[0];
|
const spec = specifications[0];
|
||||||
const artifact = { ...entry.artifact, url };
|
const artifact = { ...entry.artifact, url };
|
||||||
catalogs.set(spec.file, appendRelease(catalogs.get(spec.file), entry.manifest, artifact, timestamp));
|
catalogs.set(spec.file, appendRelease(catalogs.get(spec.file), entry.manifest, artifact, timestamp));
|
||||||
files.push({ from: entry.file, relative, sha256, size: bytes.length, url });
|
files.push({ from: entry.file, relative, sha256, size: bytes.length, url });
|
||||||
}
|
}
|
||||||
|
// Git publication keeps immutable packages alongside the canonical index.
|
||||||
|
if (!base) {
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const existing = await readFile(path.join(catalogDirectory, file.relative));
|
||||||
|
if (existing.length !== file.size || createHash('sha256').update(existing).digest('hex') !== file.sha256) {
|
||||||
|
throw new Error(`Published artifact differs: ${file.relative}; bump the plugin version`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== 'ENOENT') throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Only the current approved publication plan belongs in the generated upload directory.
|
// Only the current approved publication plan belongs in the generated upload directory.
|
||||||
await rm(publishDirectory, { recursive: true, force: true });
|
await rm(publishDirectory, { recursive: true, force: true });
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const destination = path.join(publishDirectory, file.relative);
|
const destination = path.join(publishDirectory, file.relative);
|
||||||
await mkdir(path.dirname(destination), { recursive: true });
|
await mkdir(path.dirname(destination), { recursive: true });
|
||||||
await copyFile(file.from, destination);
|
await copyFile(file.from, destination);
|
||||||
|
if (!base) {
|
||||||
|
const published = path.join(catalogDirectory, file.relative);
|
||||||
|
await mkdir(path.dirname(published), { recursive: true });
|
||||||
|
await copyFile(file.from, published);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await mkdir(catalogDirectory, { recursive: true });
|
await mkdir(catalogDirectory, { recursive: true });
|
||||||
await mkdir(publishDirectory, { recursive: true });
|
await mkdir(publishDirectory, { recursive: true });
|
||||||
@@ -63,7 +80,7 @@ for (const [file, index] of catalogs) {
|
|||||||
await writeFile(temporary, text);
|
await writeFile(temporary, text);
|
||||||
await rename(temporary, path.join(catalogDirectory, file));
|
await rename(temporary, path.join(catalogDirectory, file));
|
||||||
}
|
}
|
||||||
await writeFile(path.join(publishDirectory, 'upload-plan.json'), JSON.stringify({ baseUrl: base.href, files }, null, 2) + '\n');
|
await writeFile(path.join(publishDirectory, 'upload-plan.json'), JSON.stringify({ kind: base ? 'http' : 'git', baseUrl: base?.href, files }, null, 2) + '\n');
|
||||||
console.log(`Prepared ${files.length} immutable artifacts in ${publishDirectory}`);
|
console.log(`Prepared ${files.length} immutable artifacts in ${publishDirectory}`);
|
||||||
console.log(`Catalog history: ${catalogDirectory}`);
|
console.log(`Catalog history: ${catalogDirectory}`);
|
||||||
console.log('Next: upload package files without overwrite, verify remote bytes, then atomically publish index.json.');
|
console.log(base ? 'Next: upload package files without overwrite, verify remote bytes, then atomically publish index.json.' : 'Next: commit source/index.json and source/packages/ together, push the repository, then verify the Git source.');
|
||||||
|
|||||||
+7
-2
@@ -9,7 +9,12 @@ import { assert, equal, expectError, hostChecks, backendChecks, sleep } from '..
|
|||||||
import { serve } from './serve.mjs';
|
import { serve } from './serve.mjs';
|
||||||
|
|
||||||
// Only this optional integration adapter depends on the app checkout.
|
// Only this optional integration adapter depends on the app checkout.
|
||||||
const { values: args } = parseArgs({ options: { 'remote-source': { type: 'string' }, 'source-id': { type: 'string', default: 'trove-official' } } });
|
const { values: args } = parseArgs({ options: {
|
||||||
|
'remote-source': { type: 'string' },
|
||||||
|
'source-id': { type: 'string', default: 'trove-official' },
|
||||||
|
'source-kind': { type: 'string', default: 'http' },
|
||||||
|
} });
|
||||||
|
if (!['git', 'http'].includes(args['source-kind'])) throw new Error('--source-kind must be git or http');
|
||||||
const reportPath = path.join(output, args['remote-source'] ? 'remote-verification.json' : 'verification.json');
|
const reportPath = path.join(output, args['remote-source'] ? 'remote-verification.json' : 'verification.json');
|
||||||
await rm(reportPath, { force: true });
|
await rm(reportPath, { force: true });
|
||||||
const app = process.env.TROVE_APP_DIR;
|
const app = process.env.TROVE_APP_DIR;
|
||||||
@@ -41,7 +46,7 @@ const target = process.env.TROVE_QA_TARGET_DIR || path.join(output, 'host-target
|
|||||||
try {
|
try {
|
||||||
run('cargo', ['build', '--manifest-path', path.join(harness, 'Cargo.toml'), '--target-dir', target]);
|
run('cargo', ['build', '--manifest-path', path.join(harness, 'Cargo.toml'), '--target-dir', target]);
|
||||||
if (args['remote-source']) {
|
if (args['remote-source']) {
|
||||||
const result = spawnSync(path.join(target, 'debug/trove-qa-host-check'), ['--remote-source', args['remote-source'], args['source-id']], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], timeout: 180000 });
|
const result = spawnSync(path.join(target, 'debug/trove-qa-host-check'), ['--remote-source', args['remote-source'], args['source-id'], args['source-kind']], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], timeout: 180000 });
|
||||||
if (result.error) throw result.error;
|
if (result.error) throw result.error;
|
||||||
const report = JSON.parse(result.stdout);
|
const report = JSON.parse(result.stdout);
|
||||||
report.generatedAt = new Date().toISOString();
|
report.generatedAt = new Date().toISOString();
|
||||||
|
|||||||
Reference in New Issue
Block a user