@getnote/cli 1.5.8 → 1.5.10

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
@@ -44,6 +44,16 @@ allowBuilds:
44
44
 
45
45
  然后重新执行 `pnpm install`。也可以直接从 [Releases](https://github.com/iswalle/getnote-cli/releases) 下载对应平台的二进制文件并放入 `$PATH`。
46
46
 
47
+ ### 安装机制说明
48
+
49
+ 从 v1.5.10 起,npm 包**内置所有平台的二进制文件**(macOS/Linux/Windows,x64/ARM64),安装时**不再依赖 GitHub**。
50
+
51
+ 安装时由 postinstall 从 npm 包内的 prebuilt/ 目录复制当前平台二进制,不会访问 GitHub。
52
+
53
+ **中国大陆用户**:只需配置 npm Registry 镜像(例如 npmmirror),即可获取完整 npm 包;不需要额外配置二进制下载地址。
54
+
55
+ **包大小**:压缩后 ~22MB,解压后 ~55MB(包含 6 个平台二进制)。
56
+
47
57
  ---
48
58
 
49
59
  ## 使用要求
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getnote/cli",
3
- "version": "1.5.8",
3
+ "version": "1.5.10",
4
4
  "description": "CLI tool for 得到大脑(Get笔记) — manage notes and knowledge bases from the terminal",
5
5
  "keywords": [
6
6
  "getnote",
@@ -11,21 +11,24 @@
11
11
  "homepage": "https://github.com/iswalle/getnote-cli",
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "https://github.com/iswalle/getnote-cli.git"
14
+ "url": "git+https://github.com/iswalle/getnote-cli.git"
15
15
  },
16
16
  "license": "MIT",
17
17
  "bin": {
18
- "getnote": "./bin/getnote.js",
19
- "gnote": "./bin/getnote.js"
18
+ "getnote": "bin/getnote.js",
19
+ "gnote": "bin/getnote.js"
20
20
  },
21
21
  "scripts": {
22
22
  "postinstall": "node scripts/postinstall.js",
23
- "test": "node --test scripts/postinstall.test.js"
23
+ "test": "node --test scripts/postinstall.test.js",
24
+ "prepare-package": "bash scripts/prepare-package.sh",
25
+ "prepack": "bash scripts/prepare-package.sh"
24
26
  },
25
27
  "files": [
26
28
  "bin/getnote.js",
27
29
  "scripts/",
28
30
  "skills/",
31
+ "prebuilt/",
29
32
  "README.md"
30
33
  ]
31
34
  }
Binary file
Binary file
Binary file
Binary file
@@ -1,160 +1,60 @@
1
1
  #!/usr/bin/env node
2
- // postinstall.js — downloads the getnote binary for the current platform.
3
-
2
+ // Install the platform binary bundled in this npm package.
3
+ // Installation is local-only; no network request is made here.
4
4
  'use strict';
5
5
 
6
- const fs = require('fs');
7
- const path = require('path');
8
- const os = require('os');
9
- const https = require('https');
10
- const crypto = require('crypto');
11
- const { spawnSync } = require('child_process');
12
-
13
- const pkg = require('../package.json');
14
- const VERSION = pkg.version;
15
- const REPO = 'iswalle/getnote-cli';
16
-
17
- function getPlatform() {
18
- const platform = os.platform();
19
- const arch = os.arch();
6
+ const fs = require('node:fs');
7
+ const os = require('node:os');
8
+ const path = require('node:path');
9
+ const { spawnSync } = require('node:child_process');
10
+ const { version } = require('../package.json');
20
11
 
21
- const platformMap = { darwin: 'darwin', linux: 'linux', win32: 'windows' };
22
- const archMap = { x64: 'amd64', arm64: 'arm64' };
12
+ const platformNames = { darwin: 'darwin', linux: 'linux', win32: 'windows' };
13
+ const archNames = { x64: 'amd64', arm64: 'arm64' };
23
14
 
24
- const p = platformMap[platform];
25
- const a = archMap[arch];
26
- if (!p || !a) throw new Error(`Unsupported platform: ${platform}/${arch}`);
27
- return { platform: p, arch: a };
15
+ function getPlatform(platform = os.platform(), arch = os.arch()) {
16
+ const name = platformNames[platform];
17
+ const cpu = archNames[arch];
18
+ if (!name || !cpu) throw new Error(`Unsupported platform: ${platform}/${arch}`);
19
+ return { platform: name, arch: cpu };
28
20
  }
29
21
 
30
22
  function getBinaryName(platform) {
31
23
  return platform.platform === 'windows' ? 'getnote.exe' : 'getnote';
32
24
  }
33
25
 
34
- function getDownloadURL(platform) {
35
- const ext = platform.platform === 'windows' ? '.zip' : '.tar.gz';
36
- const asset = `getnote-cli_${VERSION}_${platform.platform}_${platform.arch}${ext}`;
37
- return `https://github.com/${REPO}/releases/download/v${VERSION}/${asset}`;
38
- }
39
-
40
- function getWindowsExtractArgs(archivePath, destinationPath) {
41
- return [
42
- '-NoProfile',
43
- '-Command',
44
- '& { Expand-Archive -LiteralPath $args[0] -DestinationPath $args[1] -Force }',
45
- archivePath,
46
- destinationPath,
47
- ];
26
+ function getPrebuiltPath(platform) {
27
+ return path.join(__dirname, '..', 'prebuilt', `${platform.platform}-${platform.arch}`, getBinaryName(platform));
48
28
  }
49
29
 
50
- async function installArchive({
51
- platform,
52
- binDir,
53
- binaryName,
54
- binaryPath,
55
- url,
56
- tmpFile,
57
- downloadFn = download,
58
- verifyChecksumFn = verifyChecksum,
59
- runFn = run,
60
- chmodFn = fs.chmodSync,
61
- unlinkFn = fs.unlinkSync,
62
- }) {
63
- try {
64
- await downloadFn(url, tmpFile);
65
- await verifyChecksumFn(url, path.basename(url), tmpFile);
66
- if (platform.platform === 'windows') {
67
- runFn('powershell', getWindowsExtractArgs(tmpFile, binDir));
68
- } else {
69
- runFn('tar', ['-xzf', tmpFile, '-C', binDir, binaryName]);
70
- }
71
-
72
- chmodFn(binaryPath, 0o755);
73
- console.log(`getnote installed at ${binaryPath}`);
74
- } finally {
75
- try { unlinkFn(tmpFile); } catch (_) {}
30
+ function installFromPrebuilt(platform, binDir) {
31
+ const source = getPrebuiltPath(platform);
32
+ const destination = path.join(binDir, getBinaryName(platform));
33
+ if (!fs.existsSync(source)) {
34
+ throw new Error(`Bundled binary is missing: ${path.relative(path.join(__dirname, '..'), source)}. Install a newer @getnote/cli package.`);
76
35
  }
36
+ fs.mkdirSync(binDir, { recursive: true });
37
+ fs.copyFileSync(source, destination);
38
+ if (platform.platform !== 'windows') fs.chmodSync(destination, 0o755);
39
+ console.log(`getnote v${version} installed from the npm package`);
77
40
  }
78
41
 
79
- async function main() {
42
+ function main() {
80
43
  const platform = getPlatform();
81
44
  const binDir = path.join(__dirname, '..', 'bin');
82
- const binaryName = getBinaryName(platform);
83
- const binaryPath = path.join(binDir, binaryName);
84
- const url = getDownloadURL(platform);
85
- const tmpFile = path.join(os.tmpdir(), `getnote-download-${Date.now()}`);
86
-
87
- // Skip download if binary already matches current version
45
+ const binaryPath = path.join(binDir, getBinaryName(platform));
88
46
  if (fs.existsSync(binaryPath)) {
89
- try {
90
- const result = spawnSync(binaryPath, ['version'], { encoding: 'utf8' });
91
- const fallback = result.status === 0 ? result : spawnSync(binaryPath, ['--version'], { encoding: 'utf8' });
92
- const out = (fallback.stdout || '').trim();
93
- if (out.includes(VERSION)) {
94
- console.log(`getnote v${VERSION} already installed, skipping download.`);
95
- return;
96
- }
97
- } catch (_) { /* binary exists but can't run (wrong arch etc.), re-download */ }
98
- }
99
-
100
- console.log(`Downloading getnote v${VERSION} for ${platform.platform}/${platform.arch}...`);
101
- console.log(`URL: ${url}`);
102
-
103
- fs.mkdirSync(binDir, { recursive: true });
104
-
105
- await installArchive({ platform, binDir, binaryName, binaryPath, url, tmpFile });
106
- }
107
-
108
- function run(command, args) {
109
- const result = spawnSync(command, args, { stdio: 'inherit' });
110
- if (result.error) throw result.error;
111
- if (result.status !== 0) throw new Error(`${command} exited with status ${result.status}`);
112
- }
113
-
114
- function download(url, destination, redirects = 0) {
115
- if (redirects > 5) return Promise.reject(new Error('Too many redirects'));
116
- return new Promise((resolve, reject) => {
117
- const request = https.get(url, { headers: { 'User-Agent': '@getnote/cli installer' } }, response => {
118
- if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
119
- response.resume();
120
- return resolve(download(new URL(response.headers.location, url).toString(), destination, redirects + 1));
121
- }
122
- if (response.statusCode !== 200) {
123
- response.resume();
124
- return reject(new Error(`HTTP ${response.statusCode}: ${url}`));
125
- }
126
- const output = fs.createWriteStream(destination, { mode: 0o600 });
127
- response.pipe(output);
128
- output.on('finish', () => output.close(resolve));
129
- output.on('error', reject);
130
- });
131
- request.on('error', reject);
132
- });
133
- }
134
-
135
- async function verifyChecksum(assetURL, assetName, archivePath) {
136
- const checksumPath = `${archivePath}.checksums`;
137
- try {
138
- await download(new URL('checksums.txt', assetURL).toString(), checksumPath);
139
- const line = fs.readFileSync(checksumPath, 'utf8').split(/\r?\n/).find(value => {
140
- const fields = value.trim().split(/\s+/);
141
- return fields.length === 2 && fields[1].replace(/^\*/, '') === assetName;
142
- });
143
- if (!line) throw new Error(`Checksum for ${assetName} is missing`);
144
- const expected = line.trim().split(/\s+/)[0].toLowerCase();
145
- if (!/^[a-f0-9]{64}$/.test(expected)) throw new Error(`Checksum for ${assetName} is invalid`);
146
- const actual = crypto.createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex');
147
- if (actual !== expected) throw new Error(`Checksum mismatch for ${assetName}`);
148
- } finally {
149
- try { fs.unlinkSync(checksumPath); } catch (_) {}
47
+ const result = spawnSync(binaryPath, ['version'], { encoding: 'utf8' });
48
+ if (result.status === 0 && (result.stdout || '').includes(version)) return;
150
49
  }
50
+ installFromPrebuilt(platform, binDir);
151
51
  }
152
52
 
153
53
  if (require.main === module) {
154
- main().catch(err => {
155
- console.error('Failed to install getnote:', err.message);
54
+ try { main(); } catch (error) {
55
+ console.error(`Failed to install getnote: ${error.message}`);
156
56
  process.exitCode = 1;
157
- });
57
+ }
158
58
  }
159
59
 
160
- module.exports = { getWindowsExtractArgs, installArchive };
60
+ module.exports = { getPlatform, getBinaryName, getPrebuiltPath, installFromPrebuilt };
@@ -1,37 +1,15 @@
1
1
  'use strict';
2
-
3
2
  const assert = require('node:assert/strict');
4
3
  const test = require('node:test');
4
+ const { getPlatform, getBinaryName, getPrebuiltPath } = require('./postinstall');
5
5
 
6
- const { getWindowsExtractArgs, installArchive } = require('./postinstall');
7
-
8
- test('Windows extraction invokes Expand-Archive inside a script block', () => {
9
- assert.deepEqual(getWindowsExtractArgs('C:\\Temp\\getnote.zip', 'C:\\Program Files\\getnote'), [
10
- '-NoProfile',
11
- '-Command',
12
- '& { Expand-Archive -LiteralPath $args[0] -DestinationPath $args[1] -Force }',
13
- 'C:\\Temp\\getnote.zip',
14
- 'C:\\Program Files\\getnote',
15
- ]);
6
+ test('maps Windows architectures to bundled executable paths', () => {
7
+ assert.deepEqual(getPlatform('win32', 'x64'), { platform: 'windows', arch: 'amd64' });
8
+ assert.deepEqual(getPlatform('win32', 'arm64'), { platform: 'windows', arch: 'arm64' });
9
+ assert.equal(getBinaryName({ platform: 'windows' }), 'getnote.exe');
10
+ assert.match(getPrebuiltPath({ platform: 'windows', arch: 'amd64' }), /prebuilt[\\/]windows-amd64[\\/]getnote\.exe$/);
16
11
  });
17
12
 
18
- test('temporary archive is removed when installation fails', async () => {
19
- const removed = [];
20
- await assert.rejects(
21
- installArchive({
22
- platform: { platform: 'windows', arch: 'amd64' },
23
- binDir: 'C:\\getnote\\bin',
24
- binaryName: 'getnote.exe',
25
- binaryPath: 'C:\\getnote\\bin\\getnote.exe',
26
- url: 'https://example.invalid/getnote.zip',
27
- tmpFile: 'C:\\Temp\\getnote-download.zip',
28
- downloadFn: async () => { throw new Error('download failed'); },
29
- verifyChecksumFn: async () => {},
30
- runFn: () => {},
31
- chmodFn: () => {},
32
- unlinkFn: file => removed.push(file),
33
- }),
34
- /download failed/,
35
- );
36
- assert.deepEqual(removed, ['C:\\Temp\\getnote-download.zip']);
13
+ test('uses a package-local binary path', () => {
14
+ assert.doesNotMatch(getPrebuiltPath({ platform: 'windows', arch: 'amd64' }), /^https?:/);
37
15
  });
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # Build every supported binary into the npm package's prebuilt/ directory.
3
+ # Uses npm naming convention (x64 instead of amd64) for consistency.
4
+ set -euo pipefail
5
+
6
+ VERSION="${1:-$(node -p "require('./package.json').version")}"
7
+ VERSION="${VERSION% }"
8
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
9
+ cd "$ROOT"
10
+
11
+ echo "==> Preparing package for version ${VERSION}"
12
+ echo "==> Cleaning prebuilt directory..."
13
+ rm -rf prebuilt
14
+
15
+ echo "==> Creating platform directories..."
16
+ mkdir -p prebuilt/darwin-amd64 prebuilt/darwin-arm64 prebuilt/linux-amd64 prebuilt/linux-arm64 prebuilt/windows-amd64 prebuilt/windows-arm64
17
+
18
+ LDFLAGS="-s -w -X github.com/iswalle/getnote-cli/internal/version.Version=${VERSION}"
19
+
20
+ echo "==> Building binaries..."
21
+ echo " - macOS x64..."
22
+ GOOS=darwin GOARCH=amd64 go build -ldflags "$LDFLAGS" -o prebuilt/darwin-amd64/getnote .
23
+
24
+ echo " - macOS ARM64..."
25
+ GOOS=darwin GOARCH=arm64 go build -ldflags "$LDFLAGS" -o prebuilt/darwin-arm64/getnote .
26
+
27
+ echo " - Linux x64..."
28
+ GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o prebuilt/linux-amd64/getnote .
29
+
30
+ echo " - Linux ARM64..."
31
+ GOOS=linux GOARCH=arm64 go build -ldflags "$LDFLAGS" -o prebuilt/linux-arm64/getnote .
32
+
33
+ echo " - Windows x64..."
34
+ GOOS=windows GOARCH=amd64 go build -ldflags "$LDFLAGS" -o prebuilt/windows-amd64/getnote.exe .
35
+
36
+ echo " - Windows ARM64..."
37
+ GOOS=windows GOARCH=arm64 go build -ldflags "$LDFLAGS" -o prebuilt/windows-arm64/getnote.exe .
38
+
39
+ echo "==> Verifying prebuilt binaries..."
40
+ find prebuilt -type f -exec ls -lh {} \; | awk '{print $5, $9}'
41
+
42
+ TOTAL_SIZE=$(du -sh prebuilt | awk '{print $1}')
43
+ echo "==> Total prebuilt size: ${TOTAL_SIZE}"
44
+
45
+ echo "==> Package preparation complete!"
46
+ echo ""
47
+ echo "Next steps:"
48
+ echo " 1. Test with: npm pack --dry-run"
49
+ echo " 2. Verify with: tar -tzf getnote-cli-${VERSION}.tgz | grep prebuilt"
50
+ echo " 3. Test install: npm install -g ./getnote-cli-${VERSION}.tgz"
51
+ echo " 4. Publish with: npm publish"
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ // Test local installation without network access
3
+
4
+ const { execSync } = require('child_process');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+
9
+ const VERSION = require('../package.json').version;
10
+ const tmpDir = path.join(os.tmpdir(), `getnote-test-${Date.now()}`);
11
+
12
+ console.log('==> Testing local installation...');
13
+ console.log(`Version: ${VERSION}`);
14
+ console.log(`Test directory: ${tmpDir}`);
15
+ console.log('');
16
+
17
+ // Create test environment
18
+ fs.mkdirSync(tmpDir, { recursive: true });
19
+
20
+ try {
21
+ // Pack the package
22
+ console.log('1. Creating package tarball...');
23
+ const tarball = path.resolve(`getnote-cli-${VERSION}.tgz`);
24
+
25
+ if (!fs.existsSync(tarball)) {
26
+ console.error(`✗ Tarball not found: ${tarball}`);
27
+ console.error(' Run: npm pack');
28
+ process.exit(1);
29
+ }
30
+
31
+ console.log(` ✓ Found: ${tarball}`);
32
+
33
+ // Install globally in test directory
34
+ console.log('');
35
+ console.log('2. Installing package...');
36
+
37
+ const npmPrefix = path.join(tmpDir, 'npm-global');
38
+ fs.mkdirSync(npmPrefix, { recursive: true });
39
+
40
+ execSync(`npm install --prefix "${npmPrefix}" "${tarball}"`, {
41
+ stdio: 'inherit',
42
+ env: {
43
+ ...process.env,
44
+ npm_config_offline: 'true', // Force offline mode
45
+ npm_config_prefer_offline: 'true'
46
+ }
47
+ });
48
+
49
+ console.log(' ✓ Package installed');
50
+
51
+ // Verify binary exists
52
+ console.log('');
53
+ console.log('3. Verifying installation...');
54
+
55
+ const platform = os.platform();
56
+ const binaryName = platform === 'win32' ? 'getnote.exe' : 'getnote';
57
+ const binaryPath = path.join(
58
+ npmPrefix,
59
+ 'node_modules',
60
+ '@getnote',
61
+ 'cli',
62
+ 'bin',
63
+ binaryName
64
+ );
65
+
66
+ if (!fs.existsSync(binaryPath)) {
67
+ console.error(` ✗ Binary not found: ${binaryPath}`);
68
+ process.exit(1);
69
+ }
70
+
71
+ console.log(` ✓ Binary exists: ${binaryPath}`);
72
+
73
+ // Test binary execution
74
+ const result = execSync(`"${binaryPath}" --version`, { encoding: 'utf8' });
75
+ console.log(` ✓ Binary runs: ${result.trim()}`);
76
+
77
+ if (!result.includes(VERSION)) {
78
+ console.error(` ✗ Version mismatch: expected ${VERSION}, got ${result.trim()}`);
79
+ process.exit(1);
80
+ }
81
+
82
+ console.log('');
83
+ console.log('==> ✓ All tests passed!');
84
+ console.log('');
85
+ console.log('Installation succeeded without network access.');
86
+ console.log('The package includes all platform binaries and does not depend on GitHub.');
87
+
88
+ } catch (err) {
89
+ console.error('');
90
+ console.error('==> ✗ Test failed:');
91
+ console.error(err.message);
92
+ process.exit(1);
93
+ } finally {
94
+ // Cleanup
95
+ console.log('');
96
+ console.log('Cleaning up...');
97
+ fs.rmSync(tmpDir, { recursive: true, force: true });
98
+ console.log('Done.');
99
+ }