@niuwoai/willdeep 0.0.0-stage → 0.129.0-rc5

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.
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { spawn } = require('node:child_process');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ const binary = path.join(__dirname, process.platform === 'win32' ? 'willdeep.exe' : 'willdeep');
9
+ if (!fs.existsSync(binary)) {
10
+ process.stderr.write('willdeep 还没有安装完成。请重新执行 npm install willdeep。\n');
11
+ process.exit(1);
12
+ }
13
+
14
+ const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
15
+ child.on('error', (error) => {
16
+ process.stderr.write(`无法启动 willdeep:${error.message}\n`);
17
+ process.exit(1);
18
+ });
19
+ child.on('exit', (code, signal) => {
20
+ if (signal) {
21
+ process.kill(process.pid, signal);
22
+ return;
23
+ }
24
+ process.exit(code == null ? 1 : code);
25
+ });
@@ -0,0 +1,23 @@
1
+ {
2
+ "version": "0.129.0-rc5",
3
+ "repository": "niuwoai/willdeep-cli",
4
+ "tag": "v0.129.0-rc5",
5
+ "assets": {
6
+ "willdeep-macos-universal.tar.gz": {
7
+ "name": "willdeep-macos-universal.tar.gz",
8
+ "sha256": "42ed0848aedeac764f74f363d14cf175bbfee5d298e34500fa9b992ea9371aea"
9
+ },
10
+ "willdeep-linux-amd64.tar.gz": {
11
+ "name": "willdeep-linux-amd64.tar.gz",
12
+ "sha256": "72cffc665c05cc9eb4b4afc6d01663b0fc9ccea257192abffbd4c65c1ccf4125"
13
+ },
14
+ "willdeep-linux-arm64.tar.gz": {
15
+ "name": "willdeep-linux-arm64.tar.gz",
16
+ "sha256": "bb5ebbc242e90977ce1a9d9affead118ce608ad588324ef58c4d58d5ec46a26f"
17
+ },
18
+ "willdeep-windows-x64.zip": {
19
+ "name": "willdeep-windows-x64.zip",
20
+ "sha256": "d52b89972084c8074e5840bed01be2229b3c352a82a19d201594564439ac9ee0"
21
+ }
22
+ }
23
+ }
package/lib/install.js ADDED
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { installWilldeep } = require('./release');
6
+
7
+ async function main() {
8
+ const packageRoot = path.resolve(__dirname, '..');
9
+ const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, 'install-manifest.json'), 'utf8'));
10
+ const destination = path.join(packageRoot, 'bin', process.platform === 'win32' ? 'willdeep.exe' : 'willdeep');
11
+ const installed = await installWilldeep({
12
+ manifest,
13
+ platform: process.platform,
14
+ arch: process.arch,
15
+ destination,
16
+ packageRoot,
17
+ fetchImpl: global.fetch,
18
+ });
19
+ if (!installed.reused) {
20
+ process.stdout.write(`已安装 WillDeep ${manifest.version}(${installed.asset.name})。\n`);
21
+ }
22
+ }
23
+
24
+ if (require.main === module) {
25
+ main().catch((error) => {
26
+ process.stderr.write(`${error.stack}\n`);
27
+ process.exitCode = 1;
28
+ });
29
+ }
30
+
31
+ module.exports = { main };
package/lib/release.js ADDED
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+
3
+ const { createHash } = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+ const { execFileSync } = require('node:child_process');
8
+
9
+ const PLATFORMS = Object.freeze({
10
+ 'darwin-x64': 'willdeep-macos-universal.tar.gz',
11
+ 'darwin-arm64': 'willdeep-macos-universal.tar.gz',
12
+ 'linux-x64': 'willdeep-linux-amd64.tar.gz',
13
+ 'linux-arm64': 'willdeep-linux-arm64.tar.gz',
14
+ 'win32-x64': 'willdeep-windows-x64.zip',
15
+ });
16
+ const EXPECTED_ENTRIES = Object.freeze({
17
+ '.tar.gz': 'willdeep',
18
+ '.zip': 'willdeep.exe',
19
+ });
20
+ const MAX_ASSET_BYTES = 128 * 1024 * 1024;
21
+ const MAX_BINARY_BYTES = 128 * 1024 * 1024;
22
+ const RELEASE_HOSTS = new Set(['github.com', 'release-assets.githubusercontent.com']);
23
+
24
+ class InstallError extends Error {
25
+ constructor(message) {
26
+ super(message);
27
+ this.name = 'InstallError';
28
+ }
29
+ }
30
+
31
+ function resolveAsset(manifest, platform, arch) {
32
+ if (!manifest || manifest.version == null || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(manifest.repository || '') || !/^v[0-9][A-Za-z0-9.-]*$/.test(manifest.tag || '') || !manifest.assets) {
33
+ throw new InstallError('安装清单不完整。');
34
+ }
35
+ const name = PLATFORMS[`${platform}-${arch}`];
36
+ if (!name) throw new InstallError(`暂不支持 ${platform}/${arch}。`);
37
+ const asset = manifest.assets[name];
38
+ if (!asset || !/^[a-f0-9]{64}$/.test(asset.sha256 || '')) {
39
+ throw new InstallError(`版本 ${manifest.version} 还没有 ${name} 的校验值。`);
40
+ }
41
+ if (asset.name !== name) throw new InstallError(`安装清单里的资源名不一致:${name}。`);
42
+ return { ...asset, name };
43
+ }
44
+
45
+ function assetUrl(manifest, assetName) {
46
+ const encoded = assetName.split('/').map(encodeURIComponent).join('/');
47
+ return `https://github.com/${manifest.repository}/releases/download/${encodeURIComponent(manifest.tag)}/${encoded}`;
48
+ }
49
+
50
+ async function downloadAsset(url, expected, fetchImpl, redirectCount = 0, trustedHosts = RELEASE_HOSTS) {
51
+ if (redirectCount > 5) throw new InstallError('下载发行包时重定向次数过多。');
52
+ const response = await fetchImpl(url, { redirect: 'manual' });
53
+ if ([301, 302, 303, 307, 308].includes(response.status)) {
54
+ const location = response.headers.get('location');
55
+ if (!location) throw new InstallError('下载发行包时收到空的重定向。');
56
+ const target = new URL(location, url);
57
+ const trusted = target.protocol === 'https:' && target.port === '' && trustedHosts.has(target.hostname);
58
+ const localTest = target.protocol === 'http:' && target.hostname === '127.0.0.1' && target.port !== '' && trustedHosts.has('127.0.0.1');
59
+ if (!trusted && !localTest) throw new InstallError(`发行包重定向到不受信任的地址:${target.hostname}。`);
60
+ return downloadAsset(target.href, expected, fetchImpl, redirectCount + 1, trustedHosts);
61
+ }
62
+ if (!response.ok) throw new InstallError(`下载发行包失败:HTTP ${response.status}。`);
63
+ const declared = Number(response.headers.get('content-length'));
64
+ if (Number.isFinite(declared) && declared > MAX_ASSET_BYTES) throw new InstallError('发行包超过 128 MiB 上限。');
65
+ const bytes = new Uint8Array(await response.arrayBuffer());
66
+ if (bytes.byteLength > MAX_ASSET_BYTES) throw new InstallError('发行包超过 128 MiB 上限。');
67
+ const digest = createHash('sha256').update(bytes).digest('hex');
68
+ if (digest !== expected) throw new InstallError(`发行包 SHA256 不符:收到 ${digest}。`);
69
+ return bytes;
70
+ }
71
+
72
+ function extractBinary(archivePath, assetName, destination) {
73
+ const extension = Object.keys(EXPECTED_ENTRIES).find((item) => assetName.endsWith(item));
74
+ const expected = EXPECTED_ENTRIES[extension];
75
+ if (!expected) throw new InstallError(`不认识的发行包:${assetName}。`);
76
+ const output = fs.mkdtempSync(path.join(os.tmpdir(), 'willdeep-npm-'));
77
+ try {
78
+ if (extension === '.tar.gz') {
79
+ execFileSync('tar', ['-xzf', archivePath, '-C', output], { stdio: 'pipe' });
80
+ } else if (process.platform === 'win32') {
81
+ execFileSync('tar', ['-xf', archivePath, '-C', output], { stdio: 'pipe' });
82
+ } else {
83
+ execFileSync('unzip', ['-q', archivePath, '-d', output], { stdio: 'pipe' });
84
+ }
85
+ const extracted = fs.readdirSync(output);
86
+ if (extracted.length !== 1 || extracted[0] !== expected) {
87
+ throw new InstallError(`发行包内容异常:${extracted.join(', ') || '空包'}。`);
88
+ }
89
+ const source = path.join(output, expected);
90
+ const metadata = fs.lstatSync(source);
91
+ if (!metadata.isFile()) throw new InstallError('发行包里的程序必须是普通文件。');
92
+ if (metadata.size > MAX_BINARY_BYTES) throw new InstallError('发行包里的程序超过 128 MiB 上限。');
93
+ if (extension !== '.zip' && (metadata.mode & 0o111) === 0) {
94
+ throw new InstallError('发行包里的程序没有可执行权限。');
95
+ }
96
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
97
+ const temporary = path.join(path.dirname(destination), `.${path.basename(destination)}.${process.pid}.tmp`);
98
+ fs.copyFileSync(source, temporary);
99
+ fs.chmodSync(temporary, 0o755);
100
+ fs.renameSync(temporary, destination);
101
+ } finally {
102
+ fs.rmSync(output, { recursive: true, force: true });
103
+ }
104
+ }
105
+
106
+ async function installWilldeep(options) {
107
+ const asset = resolveAsset(options.manifest, options.platform, options.arch);
108
+ if (fs.existsSync(options.destination)) {
109
+ const marker = path.join(options.packageRoot, '.installed-asset.json');
110
+ if (fs.existsSync(marker)) {
111
+ const installed = JSON.parse(fs.readFileSync(marker, 'utf8'));
112
+ if (installed.version === options.manifest.version && installed.sha256 === asset.sha256) {
113
+ return { reused: true, asset };
114
+ }
115
+ }
116
+ }
117
+ const archive = path.join(os.tmpdir(), `willdeep-${process.pid}-${Date.now()}-${asset.name}`);
118
+ try {
119
+ const bytes = await downloadAsset(
120
+ assetUrl(options.manifest, asset.name),
121
+ asset.sha256,
122
+ options.fetchImpl,
123
+ 0,
124
+ options.trustedHosts || RELEASE_HOSTS,
125
+ );
126
+ fs.writeFileSync(archive, bytes, { flag: 'wx', mode: 0o600 });
127
+ extractBinary(archive, asset.name, options.destination);
128
+ fs.writeFileSync(path.join(options.packageRoot, '.installed-asset.json'), `${JSON.stringify({
129
+ version: options.manifest.version,
130
+ name: asset.name,
131
+ sha256: asset.sha256,
132
+ })}\n`, { mode: 0o600 });
133
+ } finally {
134
+ fs.rmSync(archive, { force: true });
135
+ }
136
+ return { reused: false, asset };
137
+ }
138
+
139
+ module.exports = {
140
+ InstallError,
141
+ resolveAsset,
142
+ assetUrl,
143
+ downloadAsset,
144
+ extractBinary,
145
+ installWilldeep,
146
+ };
package/package.json CHANGED
@@ -1,6 +1,31 @@
1
1
  {
2
2
  "name": "@niuwoai/willdeep",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.129.0-rc5",
4
+ "description": "Install the WillDeep CLI from its verified GitHub Release.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/niuwoai/willdeep-cli.git",
9
+ "directory": "npm/willdeep"
10
+ },
11
+ "homepage": "https://github.com/niuwoai/willdeep-cli",
12
+ "bugs": "https://github.com/niuwoai/willdeep-cli/issues",
13
+ "bin": {
14
+ "willdeep": "bin/willdeep.js"
15
+ },
16
+ "files": [
17
+ "bin",
18
+ "lib",
19
+ "install-manifest.json"
20
+ ],
21
+ "scripts": {
22
+ "postinstall": "node lib/install.js"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "tag": "rc"
30
+ }
31
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
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.