@gaiaworks/gaia-cli 0.0.1

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 ADDED
@@ -0,0 +1,20 @@
1
+ # @gaiaworks/gaia-cli
2
+
3
+ 这是 `gaia-cli` 的 npm 分发壳包,用于通过 `npx` 下载并运行对应版本的 Go 二进制。
4
+
5
+ ## 使用方式
6
+
7
+ 临时执行:
8
+
9
+ ```bash
10
+ npx @gaiaworks/gaia-cli version
11
+ ```
12
+
13
+ 全局安装:
14
+
15
+ ```bash
16
+ npx @gaiaworks/gaia-cli@latest install
17
+ gaia-cli version
18
+ ```
19
+
20
+ 安装时会根据 npm 包版本,从 `gaiaworks/gaia-cli-releases` 的同版本 GitHub Release 下载当前平台对应的 `gaia-cli` 二进制。
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@gaiaworks/gaia-cli",
3
+ "version": "0.0.1",
4
+ "description": "JavaScript launcher for gaia-cli.",
5
+ "license": "UNLICENSED",
6
+ "private": false,
7
+ "bin": {
8
+ "gaia-cli": "scripts/run.js"
9
+ },
10
+ "os": [
11
+ "darwin",
12
+ "linux",
13
+ "win32"
14
+ ],
15
+ "cpu": [
16
+ "x64",
17
+ "arm64"
18
+ ],
19
+ "files": [
20
+ "README.md",
21
+ "package.json",
22
+ "scripts"
23
+ ],
24
+ "scripts": {
25
+ "postinstall": "node scripts/install.js",
26
+ "test": "node --test"
27
+ },
28
+ "engines": {
29
+ "node": ">=18"
30
+ }
31
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { spawnSync } = require('child_process');
7
+
8
+ const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
9
+ const packageSpec = `${packageJson.name}@${packageJson.version}`;
10
+
11
+ const result = spawnSync('npm', ['install', '-g', packageSpec], {
12
+ stdio: 'inherit',
13
+ });
14
+
15
+ if (result.error) {
16
+ console.error(`[gaia-cli] Failed to run npm install: ${result.error.message}`);
17
+ process.exit(1);
18
+ }
19
+
20
+ if (result.status !== 0) {
21
+ process.exit(typeof result.status === 'number' ? result.status : 1);
22
+ }
23
+
24
+ console.error('[gaia-cli] Global install completed. You can now run: gaia-cli version');
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const { spawnSync } = require('child_process');
8
+
9
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
10
+ const PACKAGE_JSON = path.join(PACKAGE_ROOT, 'package.json');
11
+ const BIN_DIR = path.join(PACKAGE_ROOT, 'bin');
12
+ const RELEASE_REPO = process.env.GAIA_CLI_RELEASE_REPO || 'gaiaworks/gaia-cli-releases';
13
+ const DOWNLOAD_BASE = process.env.GAIA_CLI_DOWNLOAD_BASE || `https://github.com/${RELEASE_REPO}/releases/download`;
14
+ const ALLOWED_DOWNLOAD_HOSTS = new Set([
15
+ 'github.com',
16
+ 'objects.githubusercontent.com',
17
+ ]);
18
+
19
+ function readPackageVersion() {
20
+ const content = fs.readFileSync(PACKAGE_JSON, 'utf8');
21
+ return JSON.parse(content).version;
22
+ }
23
+
24
+ function normalizeVersion(version) {
25
+ return version.startsWith('v') ? version.slice(1) : version;
26
+ }
27
+
28
+ function platformName(platform = process.platform) {
29
+ if (platform === 'darwin') return 'darwin';
30
+ if (platform === 'linux') return 'linux';
31
+ if (platform === 'win32') return 'windows';
32
+ throw new Error(`Unsupported platform: ${platform}`);
33
+ }
34
+
35
+ function archName(arch = process.arch) {
36
+ if (arch === 'x64') return 'amd64';
37
+ if (arch === 'arm64') return 'arm64';
38
+ throw new Error(`Unsupported architecture: ${arch}`);
39
+ }
40
+
41
+ function binaryName(platform = process.platform) {
42
+ return platform === 'win32' ? 'gaia-cli.exe' : 'gaia-cli';
43
+ }
44
+
45
+ function archiveName(version, platform = process.platform, arch = process.arch) {
46
+ const cleanVersion = normalizeVersion(version);
47
+ return `gaia-cli-${cleanVersion}-${platformName(platform)}-${archName(arch)}.tar.gz`;
48
+ }
49
+
50
+ function releaseTag(version) {
51
+ const cleanVersion = normalizeVersion(version);
52
+ return `v${cleanVersion}`;
53
+ }
54
+
55
+ function downloadUrl(version, platform = process.platform, arch = process.arch) {
56
+ return `${DOWNLOAD_BASE}/${releaseTag(version)}/${archiveName(version, platform, arch)}`;
57
+ }
58
+
59
+ function assertAllowedUrl(rawUrl) {
60
+ const parsed = new URL(rawUrl);
61
+ if (parsed.protocol !== 'https:') {
62
+ throw new Error(`Refusing non-HTTPS download URL: ${rawUrl}`);
63
+ }
64
+ if (!ALLOWED_DOWNLOAD_HOSTS.has(parsed.hostname)) {
65
+ throw new Error(`Refusing download host: ${parsed.hostname}`);
66
+ }
67
+ }
68
+
69
+ function ensureDir(dir) {
70
+ fs.mkdirSync(dir, { recursive: true });
71
+ }
72
+
73
+ function run(command, args, options = {}) {
74
+ const result = spawnSync(command, args, {
75
+ stdio: options.stdio || 'inherit',
76
+ cwd: options.cwd || PACKAGE_ROOT,
77
+ encoding: 'utf8',
78
+ });
79
+ if (result.error) {
80
+ throw result.error;
81
+ }
82
+ if (result.status !== 0) {
83
+ throw new Error(`${command} ${args.join(' ')} exited with code ${result.status}`);
84
+ }
85
+ return result;
86
+ }
87
+
88
+ function downloadWithRedirects(url, destination) {
89
+ assertAllowedUrl(url);
90
+ const tmpPath = `${destination}.tmp`;
91
+ fs.rmSync(tmpPath, { force: true });
92
+
93
+ run('curl', [
94
+ '--fail',
95
+ '--location',
96
+ '--show-error',
97
+ '--silent',
98
+ '--output',
99
+ tmpPath,
100
+ url,
101
+ ]);
102
+
103
+ fs.renameSync(tmpPath, destination);
104
+ }
105
+
106
+ function extractArchive(archivePath, targetDir) {
107
+ fs.rmSync(targetDir, { recursive: true, force: true });
108
+ ensureDir(targetDir);
109
+ run('tar', ['-xzf', archivePath, '-C', targetDir]);
110
+ }
111
+
112
+ function findDownloadedBinary(targetDir, platform = process.platform) {
113
+ const expectedName = binaryName(platform);
114
+ const direct = path.join(targetDir, expectedName);
115
+ if (fs.existsSync(direct)) return direct;
116
+
117
+ const pending = [targetDir];
118
+ while (pending.length > 0) {
119
+ const currentDir = pending.pop();
120
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
121
+ for (const entry of entries) {
122
+ const entryPath = path.join(currentDir, entry.name);
123
+ if (entry.isDirectory()) {
124
+ pending.push(entryPath);
125
+ } else if (entry.isFile() && entry.name === expectedName) {
126
+ return entryPath;
127
+ }
128
+ }
129
+ }
130
+ throw new Error(`Downloaded archive does not contain ${expectedName}`);
131
+ }
132
+
133
+ function install(options = {}) {
134
+ const version = options.version || readPackageVersion();
135
+ const url = options.url || downloadUrl(version);
136
+ const archivePath = path.join(os.tmpdir(), archiveName(version));
137
+ const unpackDir = path.join(os.tmpdir(), `gaia-cli-${process.pid}-${Date.now()}`);
138
+ const finalBinary = path.join(BIN_DIR, binaryName());
139
+
140
+ console.error(`[gaia-cli] Downloading ${url}`);
141
+ ensureDir(BIN_DIR);
142
+ downloadWithRedirects(url, archivePath);
143
+ extractArchive(archivePath, unpackDir);
144
+
145
+ const downloadedBinary = findDownloadedBinary(unpackDir);
146
+ fs.copyFileSync(downloadedBinary, finalBinary);
147
+ if (process.platform !== 'win32') {
148
+ fs.chmodSync(finalBinary, 0o755);
149
+ }
150
+
151
+ fs.rmSync(archivePath, { force: true });
152
+ fs.rmSync(unpackDir, { recursive: true, force: true });
153
+ console.error(`[gaia-cli] Installed ${finalBinary}`);
154
+ }
155
+
156
+ if (require.main === module) {
157
+ try {
158
+ install();
159
+ } catch (error) {
160
+ console.error(`[gaia-cli] Failed to install binary: ${error.message}`);
161
+ console.error('[gaia-cli] Please check network access to GitHub Releases or contact the administrator.');
162
+ process.exit(1);
163
+ }
164
+ }
165
+
166
+ module.exports = {
167
+ ALLOWED_DOWNLOAD_HOSTS,
168
+ archName,
169
+ archiveName,
170
+ assertAllowedUrl,
171
+ binaryName,
172
+ downloadUrl,
173
+ findDownloadedBinary,
174
+ install,
175
+ normalizeVersion,
176
+ platformName,
177
+ releaseTag,
178
+ };
package/scripts/run.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { spawnSync } = require('child_process');
7
+
8
+ const { binaryName } = require('./install');
9
+
10
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
11
+ const BINARY_PATH = path.join(PACKAGE_ROOT, 'bin', binaryName());
12
+
13
+ function runInstallWizard(args) {
14
+ const result = spawnSync(process.execPath, [path.join(__dirname, 'install-wizard.js'), ...args], {
15
+ stdio: 'inherit',
16
+ });
17
+ process.exit(typeof result.status === 'number' ? result.status : 1);
18
+ }
19
+
20
+ function ensureBinary() {
21
+ if (fs.existsSync(BINARY_PATH)) return;
22
+ const result = spawnSync(process.execPath, [path.join(__dirname, 'install.js')], {
23
+ stdio: 'inherit',
24
+ });
25
+ if (result.status !== 0) {
26
+ process.exit(typeof result.status === 'number' ? result.status : 1);
27
+ }
28
+ }
29
+
30
+ function runBinary(args) {
31
+ ensureBinary();
32
+ const result = spawnSync(BINARY_PATH, args, {
33
+ stdio: 'inherit',
34
+ });
35
+ if (result.error) {
36
+ console.error(`[gaia-cli] Failed to run ${BINARY_PATH}: ${result.error.message}`);
37
+ process.exit(1);
38
+ }
39
+ process.exit(typeof result.status === 'number' ? result.status : 1);
40
+ }
41
+
42
+ const args = process.argv.slice(2);
43
+ if (args[0] === 'install') {
44
+ runInstallWizard(args.slice(1));
45
+ }
46
+
47
+ runBinary(args);