@ai-12345678/reservoir-sampling-cli 0.0.0-stage → 0.1.0

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,3 +1,130 @@
1
- # Temporary Holding Version
1
+ # reservoir
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 基于 Go 的流式蓄水池抽样命令行工具。使用 Algorithm R,按输入行的位置等概率、无放回抽样;使用 `crypto/rand.Int` 生成无模偏差的随机下标。
4
+
5
+ npm 包名为 **`@ai-12345678/reservoir-sampling-cli`**(公开 scope 包),安装后的命令名为 **`reservoir`**。
6
+
7
+ ## 安装与使用
8
+
9
+ 发布到 npm 后安装:
10
+
11
+ ```bash
12
+ npm install -g @ai-12345678/reservoir-sampling-cli
13
+ reservoir -n 100 < input.txt > sample.txt
14
+ reservoir --version
15
+ ```
16
+
17
+ 临时运行:
18
+
19
+ ```bash
20
+ npx --yes --package @ai-12345678/reservoir-sampling-cli reservoir -n 100 < input.txt
21
+ ```
22
+
23
+ 首次 npm 授权尚未完成时,可以从本仓库 [Releases](https://github.com/ai-12345678/reservoir/releases) 下载 `reservoir-sampling-cli-0.1.0.tgz`,直接安装:
24
+
25
+ ```bash
26
+ npm install -g --ignore-scripts ./reservoir-sampling-cli-0.1.0.tgz
27
+ reservoir -n 100 < input.txt
28
+ ```
29
+
30
+ 也可以下载对应平台的原生 `.tar.gz`,解压后直接执行 `./reservoir -n 100 < input.txt`;原生方式不需要 Node.js。
31
+
32
+ | 平台 | npm 内部目录 | GitHub Release 原生文件 |
33
+ | --- | --- | --- |
34
+ | Linux AMD64 | `vendor/linux-x64/` | `reservoir_<版本>_linux_amd64.tar.gz` |
35
+ | macOS Apple Silicon | `vendor/darwin-arm64/` | `reservoir_<版本>_darwin_arm64.tar.gz` |
36
+ | macOS Intel | `vendor/darwin-x64/` | `reservoir_<版本>_darwin_amd64.tar.gz` |
37
+
38
+ npm 启动器需要 Node.js 22.14 或更高版本,根据当前 Node.js 进程的平台/架构选择二进制。当前发布工具链固定为 Go 1.27.3;macOS 原生二进制要求 macOS 13 或更新系统。Linux ARM64、Windows 暂不支持。
39
+
40
+ ## 输入输出约定
41
+
42
+ - 输入从 stdin 读取,`-n` 默认是 1,且必须为正整数。
43
+ - 输入不足 n 行时,输出所有输入行;空输入成功退出且不输出内容。
44
+ - 空行参与抽样;重复文本代表不同记录,结果不做去重。
45
+ - CRLF/LF 被解析为行分隔,输出统一使用 LF;最后一行没有换行符也会参与抽样。
46
+ - 输出为蓄水池槽位顺序,不保证保持原始顺序或均匀洗牌。
47
+ - 扫描缓冲上限是 10 MiB,包含换行等开销;超长行报错退出,不输出残缺抽样结果。
48
+ - 字符串仅在实际入池时复制;内存随保留样本总字节数增长,不缓存整个输入。
49
+ - stdout 只写样本或显式请求的版本;诊断写 stderr。退出码:0 成功、1 读取/随机源/输出失败、2 参数错误。
50
+
51
+ ## 本地开发
52
+
53
+ 需要 Go 1.27.3、Node.js 22.14+,无第三方运行时依赖。
54
+
55
+ ```bash
56
+ go test ./...
57
+ go vet ./...
58
+ npm run build
59
+ npm test
60
+ node bin/reservoir.cjs -n 2 < input.txt
61
+ ```
62
+
63
+ 构建和验证完整发行包:
64
+
65
+ ```bash
66
+ npm run build:all
67
+ npm run pack:verify
68
+ node scripts/smoke-package.mjs
69
+ npm run release:assets
70
+ ```
71
+
72
+ `build:all` 关闭 cgo,生成三种平台的二进制并写入包版本、Git 提交信息。`pack:verify` 检查二进制格式、平台、可执行权限及 npm 文件清单。安装时不运行下载或编译脚本;`--ignore-scripts` 可正常使用。
73
+
74
+ ## GitHub Actions 与 Release
75
+
76
+ - `ci.yml`:PR 和手动触发,执行 Go 测试、CLI 集成测试及三个原生运行环境的 npm 安装测试。
77
+ - `release.yml`:推送 `main`、推送 `v*` 标签或手动触发,执行同一套构建验证。
78
+ - `build.yml`:复用的构建工作流。Linux 上交叉编译三份二进制,打包同一个 npm tarball,然后在 Linux x64、macOS arm64、macOS x64 上安装并执行它。
79
+ - 新版本在测试全部通过后创建 GitHub Release,并上传三份原生压缩包、npm tarball 和 `SHA256SUMS`。
80
+ - `main` 中的 `package.json.version` 出现新版本时,工作流会为测试过的提交创建对应的 `v<版本>` 标签。已有 Release 及资产不会被覆盖。
81
+ - 同一版本的所有平台使用同一个源码提交。推送标签时,标签与 `package.json.version` 必须一致;当前工作流只发布稳定的 `x.y.z` 版本。
82
+
83
+ 日常发版:修改代码、增加 `package.json.version`(并同步 `package-lock.json`),提交到 `main` 即可;也可使用 `npm version patch` 后推送提交和标签。
84
+
85
+ 仓库目前是私有仓库,Releases 访问遵循 GitHub 仓库权限。npm 公开包的可见性独立于源码仓库可见性;工作流不修改仓库可见性。
86
+
87
+ ## npm 首次授权及自动发布
88
+
89
+ GitHub 账号不会自动拥有 npm 权限。完成以下一次性初始化后,后续版本可以全自动发布到 npm 公共 registry。
90
+
91
+ ### 1. 首次发布
92
+
93
+ 先注册/登录 npm 账号、验证邮箱并完成 npm 要求的双因素认证。从通过测试的 GitHub Release 下载 npm tarball,然后执行:
94
+
95
+ ```bash
96
+ npm login --registry=https://registry.npmjs.org/
97
+ npm publish ./reservoir-sampling-cli-0.1.0.tgz --access public --registry=https://registry.npmjs.org/
98
+ ```
99
+
100
+ 包名 `reservoir-sampling-cli` 必须可用,或当前 npm 账号已经拥有该包的发布权限。如果首次发布时名称被占用,请修改 `package.json.name`、锁文件及本文安装命令,并发布新版本。
101
+
102
+ ### 2. 设置 npm Trusted Publisher
103
+
104
+ 进入 npm 的 `reservoir-sampling-cli` 包设置,在 Trusted Publishing 中添加 GitHub Actions:
105
+
106
+ | 设置 | 值 |
107
+ | --- | --- |
108
+ | Organization or user | `ai-12345678` |
109
+ | Repository | `reservoir` |
110
+ | Workflow filename | `release.yml`(不含目录) |
111
+ | Environment | 留空 |
112
+ | Allowed actions | 允许直接执行 `npm publish` |
113
+
114
+ `package.json.repository.url` 已配置为本 GitHub 仓库。发布使用 GitHub 托管 runner、Node.js 24、npm 11.21.0 和 `id-token: write`,无需配置 `NPM_TOKEN`。私有源码仓库的发布不会生成 npm provenance。
115
+
116
+ ### 3. 启用自动发布
117
+
118
+ 在 GitHub 仓库 **Settings → Secrets and variables → Actions → Variables** 中添加:
119
+
120
+ ```text
121
+ NPM_PUBLISH_ENABLED=true
122
+ ```
123
+
124
+ 下一次发布新版本时,`release.yml` 会在 GitHub Release 完成后,校验并直接发布同一份 npm tarball。npm 授权尚未就绪时,这个步骤保持跳过,GitHub Release 构建和下载不受影响。
125
+
126
+ 新建 Trusted Publisher 需要在 48 小时内成功发布一次完成验证。初始化时可先手动发布 `0.1.0`,配置 Trusted Publisher 后立即将版本增加为 `0.1.1` 并推送到 main,验证自动发布。若配置过期,需要在 npm 端重新创建。
127
+
128
+ 如果某版本的 GitHub Release 已成功、npm 发布失败,修复账号设置后,可以手动运行 `release.yml`,填写已有标签重试;它会下载该 Release 原有的 npm tarball,不会覆盖原资产。已经发布成功的同名 npm 版本不能覆盖,请增加版本号。
129
+
130
+ 参考:[npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/)。
@@ -0,0 +1,35 @@
1
+ # Third-party notices
2
+
3
+ The native executables include the Go runtime and standard library.
4
+ Their license is reproduced below. This notice does not change the license
5
+ of the reservoir project itself.
6
+
7
+ ## Go
8
+
9
+ Copyright 2009 The Go Authors.
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions are
13
+ met:
14
+
15
+ * Redistributions of source code must retain the above copyright
16
+ notice, this list of conditions and the following disclaimer.
17
+ * Redistributions in binary form must reproduce the above
18
+ copyright notice, this list of conditions and the following disclaimer
19
+ in the documentation and/or other materials provided with the
20
+ distribution.
21
+ * Neither the name of Google LLC nor the names of its
22
+ contributors may be used to endorse or promote products derived from
23
+ this software without specific prior written permission.
24
+
25
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ require("../lib/cli.cjs").main();
package/lib/cli.cjs ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ const { accessSync, constants } = require("node:fs");
4
+ const { spawn } = require("node:child_process");
5
+ const { constants: osConstants } = require("node:os");
6
+ const { binaryPath } = require("./platforms.cjs");
7
+
8
+ function main(args = process.argv.slice(2)) {
9
+ let executable;
10
+ try {
11
+ executable = binaryPath();
12
+ accessSync(executable, constants.X_OK);
13
+ } catch (error) {
14
+ console.error(`reservoir: ${error.message}`);
15
+ console.error("Reinstall the npm package, or run npm run build in a source checkout.");
16
+ process.exitCode = 1;
17
+ return;
18
+ }
19
+
20
+ const child = spawn(executable, args, { stdio: "inherit", shell: false });
21
+ const handlers = new Map();
22
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
23
+ const handler = () => child.kill(signal);
24
+ handlers.set(signal, handler);
25
+ process.on(signal, handler);
26
+ }
27
+ let failed = false;
28
+ child.once("error", (error) => {
29
+ failed = true;
30
+ console.error(`reservoir: cannot run native executable: ${error.message}`);
31
+ });
32
+ child.once("close", (code, signal) => {
33
+ for (const [name, handler] of handlers) process.removeListener(name, handler);
34
+ if (failed) {
35
+ process.exitCode = 1;
36
+ } else if (signal) {
37
+ // Preserve signal termination. SIGPIPE is ignored by Node by default,
38
+ // so also set the conventional exit code as a fallback.
39
+ process.exitCode = 128 + (osConstants.signals[signal] || 1);
40
+ process.kill(process.pid, signal);
41
+ } else {
42
+ process.exitCode = code ?? 1;
43
+ }
44
+ });
45
+ }
46
+
47
+ module.exports = { main };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ const path = require("node:path");
4
+
5
+ const targets = Object.freeze({
6
+ "linux-x64": { goos: "linux", goarch: "amd64", machine: 62 },
7
+ "darwin-arm64": { goos: "darwin", goarch: "arm64", machine: 0x0100000c },
8
+ "darwin-x64": { goos: "darwin", goarch: "amd64", machine: 0x01000007 },
9
+ });
10
+
11
+ function binaryPath(platform = process.platform, arch = process.arch) {
12
+ const key = `${platform}-${arch}`;
13
+ if (!Object.hasOwn(targets, key)) {
14
+ throw new Error(`unsupported platform ${key}; supported: ${Object.keys(targets).join(", ")}`);
15
+ }
16
+ return path.join(__dirname, "..", "vendor", key, "reservoir");
17
+ }
18
+
19
+ module.exports = { targets, binaryPath };
package/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "@ai-12345678/reservoir-sampling-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Streaming reservoir sampling CLI powered by a native Go executable",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ai-12345678/reservoir.git"
9
+ },
10
+ "bin": {
11
+ "reservoir": "bin/reservoir.cjs"
12
+ },
13
+ "files": [
14
+ "bin/",
15
+ "lib/",
16
+ "vendor/",
17
+ "THIRD_PARTY_NOTICES.md"
18
+ ],
19
+ "engines": {
20
+ "node": ">=22.14.0"
21
+ },
22
+ "os": [
23
+ "linux",
24
+ "darwin"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "registry": "https://registry.npmjs.org/"
29
+ },
30
+ "scripts": {
31
+ "build": "node scripts/build.mjs",
32
+ "build:all": "node scripts/build.mjs --all",
33
+ "test": "node --test test/*.test.cjs",
34
+ "pack:verify": "node scripts/verify-package.mjs",
35
+ "release:assets": "node scripts/release-assets.mjs",
36
+ "prepack": "node scripts/check-binaries.mjs"
37
+ },
38
+ "keywords": [
39
+ "reservoir",
40
+ "sampling",
41
+ "cli",
42
+ "stream"
43
+ ]
44
+ }
Binary file
Binary file
Binary file