@namespacelabs/cli 0.0.453
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 +35 -0
- package/bin/cli.js +56 -0
- package/lib/install.js +253 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @namespacelabs/cli
|
|
2
|
+
|
|
3
|
+
npm wrapper for the [Namespace](https://namespace.so) CLI (`nsc`).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Run nsc commands directly with npx:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @namespacelabs/cli list
|
|
11
|
+
npx @namespacelabs/cli build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Or install globally:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g @namespacelabs/cli
|
|
18
|
+
nsc list
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Update
|
|
22
|
+
|
|
23
|
+
To update to the latest version of nsc:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx @namespacelabs/cli update
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## How it works
|
|
30
|
+
|
|
31
|
+
On first run, the package downloads the appropriate nsc binary for your platform (macOS or Linux, amd64 or arm64) and caches it locally. Subsequent runs use the cached binary.
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
Apache-2.0
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright 2022 Namespace Labs Inc; All rights reserved.
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
|
|
6
|
+
const { spawn } = require("child_process");
|
|
7
|
+
const { install, update, getBinaryPath, isInstalled } = require("../lib/install");
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
|
|
12
|
+
if (args[0] === "update") {
|
|
13
|
+
try {
|
|
14
|
+
const result = await update();
|
|
15
|
+
if (result.alreadyLatest) {
|
|
16
|
+
console.log(`nsc is already at the latest version (${result.version}).`);
|
|
17
|
+
} else {
|
|
18
|
+
console.log("nsc has been updated to the latest version.");
|
|
19
|
+
}
|
|
20
|
+
process.exit(0);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error("Failed to update nsc:", err.message);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (!isInstalled()) {
|
|
29
|
+
await install();
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error("Failed to install nsc:", err.message);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const binaryPath = getBinaryPath();
|
|
37
|
+
const child = spawn(binaryPath, args, {
|
|
38
|
+
stdio: "inherit",
|
|
39
|
+
env: {
|
|
40
|
+
...process.env,
|
|
41
|
+
NSBOOT_VERSION: JSON.stringify({ source: "@namespacelabs/cli" }),
|
|
42
|
+
NS_DO_NOT_UPDATE: "1",
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
child.on("error", (err) => {
|
|
47
|
+
console.error("Failed to run nsc:", err.message);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
child.on("close", (code) => {
|
|
52
|
+
process.exit(code || 0);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
main();
|
package/lib/install.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// Copyright 2022 Namespace Labs Inc; All rights reserved.
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
|
|
5
|
+
const https = require("https");
|
|
6
|
+
const http = require("http");
|
|
7
|
+
const crypto = require("crypto");
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const os = require("os");
|
|
11
|
+
const { Readable } = require("stream");
|
|
12
|
+
const tar = require("tar");
|
|
13
|
+
|
|
14
|
+
const TOOL_NAME = "nsc";
|
|
15
|
+
const DOCKER_CRED_HELPER_NAME = "docker-credential-nsc";
|
|
16
|
+
const BAZEL_CRED_HELPER_NAME = "bazel-credential-nsc";
|
|
17
|
+
const API_ENDPOINT = "https://get.namespace.so/nsl.versions.VersionsService/GetLatest";
|
|
18
|
+
|
|
19
|
+
function getPlatform() {
|
|
20
|
+
const platform = os.platform();
|
|
21
|
+
switch (platform) {
|
|
22
|
+
case "darwin":
|
|
23
|
+
return "darwin";
|
|
24
|
+
case "linux":
|
|
25
|
+
return "linux";
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`Unsupported platform: ${platform}. nsc is available for macOS and Linux.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getArch() {
|
|
32
|
+
const arch = os.arch();
|
|
33
|
+
switch (arch) {
|
|
34
|
+
case "x64":
|
|
35
|
+
return "amd64";
|
|
36
|
+
case "arm64":
|
|
37
|
+
return "arm64";
|
|
38
|
+
default:
|
|
39
|
+
throw new Error(`Unsupported architecture: ${arch}. nsc is available for amd64 and arm64.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getNsRoot() {
|
|
44
|
+
if (process.env.NS_ROOT) {
|
|
45
|
+
return process.env.NS_ROOT;
|
|
46
|
+
}
|
|
47
|
+
const platform = os.platform();
|
|
48
|
+
const home = os.homedir();
|
|
49
|
+
if (platform === "darwin") {
|
|
50
|
+
return path.join(home, "Library", "Application Support", "ns");
|
|
51
|
+
}
|
|
52
|
+
return path.join(home, ".ns");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getBinDir() {
|
|
56
|
+
return path.join(getNsRoot(), "bin");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getBinaryPath() {
|
|
60
|
+
return path.join(getBinDir(), TOOL_NAME);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getDockerCredHelperPath() {
|
|
64
|
+
return path.join(getBinDir(), DOCKER_CRED_HELPER_NAME);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getBazelCredHelperPath() {
|
|
68
|
+
return path.join(getBinDir(), BAZEL_CRED_HELPER_NAME);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isInstalled() {
|
|
72
|
+
return fs.existsSync(getBinaryPath());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getVersionFilePath() {
|
|
76
|
+
return path.join(getBinDir(), ".version");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getInstalledVersion() {
|
|
80
|
+
const versionFile = getVersionFilePath();
|
|
81
|
+
if (fs.existsSync(versionFile)) {
|
|
82
|
+
return fs.readFileSync(versionFile, "utf8").trim();
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function setInstalledVersion(version) {
|
|
88
|
+
fs.writeFileSync(getVersionFilePath(), version);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function httpRequest(url, options = {}) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const doRequest = (requestUrl, method, body) => {
|
|
94
|
+
const urlObj = new URL(requestUrl);
|
|
95
|
+
const protocol = urlObj.protocol === "https:" ? https : http;
|
|
96
|
+
const reqOptions = {
|
|
97
|
+
hostname: urlObj.hostname,
|
|
98
|
+
port: urlObj.port || (urlObj.protocol === "https:" ? 443 : 80),
|
|
99
|
+
path: urlObj.pathname + urlObj.search,
|
|
100
|
+
method: method,
|
|
101
|
+
headers: {
|
|
102
|
+
"User-Agent": "@namespacelabs/cli",
|
|
103
|
+
...options.headers,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const req = protocol.request(reqOptions, (response) => {
|
|
108
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
109
|
+
let redirectUrl = response.headers.location;
|
|
110
|
+
if (redirectUrl.startsWith("/")) {
|
|
111
|
+
redirectUrl = `${urlObj.protocol}//${urlObj.host}${redirectUrl}`;
|
|
112
|
+
}
|
|
113
|
+
doRequest(redirectUrl, "GET", null);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (response.statusCode !== 200) {
|
|
118
|
+
reject(new Error(`HTTP ${response.statusCode}`));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const chunks = [];
|
|
123
|
+
response.on("data", (chunk) => chunks.push(chunk));
|
|
124
|
+
response.on("end", () => resolve({ data: Buffer.concat(chunks), response }));
|
|
125
|
+
response.on("error", reject);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
req.on("error", reject);
|
|
129
|
+
|
|
130
|
+
if (body) {
|
|
131
|
+
req.write(body);
|
|
132
|
+
}
|
|
133
|
+
req.end();
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
doRequest(url, options.method || "GET", options.body || null);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function fetchVersionInfo() {
|
|
141
|
+
const platform = getPlatform().toUpperCase();
|
|
142
|
+
const arch = getArch().toUpperCase();
|
|
143
|
+
|
|
144
|
+
const { data } = await httpRequest(API_ENDPOINT, {
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: { "Content-Type": "application/json" },
|
|
147
|
+
body: JSON.stringify({ [TOOL_NAME]: {} }),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const response = JSON.parse(data.toString());
|
|
151
|
+
const tarball = response.tarballs.find(
|
|
152
|
+
(t) => t.os === platform && t.arch === arch
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (!tarball) {
|
|
156
|
+
throw new Error(`No tarball found for ${platform}/${arch}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
version: response.version,
|
|
161
|
+
buildTime: response.build_time,
|
|
162
|
+
url: tarball.url,
|
|
163
|
+
sha256: tarball.sha256,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function download(url) {
|
|
168
|
+
const { data } = await httpRequest(url);
|
|
169
|
+
return data;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function verifySha256(buffer, expectedHash) {
|
|
173
|
+
const hash = crypto.createHash("sha256").update(buffer).digest("hex");
|
|
174
|
+
if (hash !== expectedHash) {
|
|
175
|
+
throw new Error(`SHA256 mismatch: expected ${expectedHash}, got ${hash}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function install(options = {}) {
|
|
180
|
+
const { force = false, versionInfo = null } = options;
|
|
181
|
+
const binDir = getBinDir();
|
|
182
|
+
|
|
183
|
+
if (!force && isInstalled()) {
|
|
184
|
+
return { alreadyInstalled: true, path: getBinaryPath() };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const info = versionInfo || (await fetchVersionInfo());
|
|
188
|
+
|
|
189
|
+
if (!fs.existsSync(binDir)) {
|
|
190
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.error(`Downloading nsc ${info.version} from ${info.url}...`);
|
|
194
|
+
|
|
195
|
+
const tarGzBuffer = await download(info.url);
|
|
196
|
+
|
|
197
|
+
verifySha256(tarGzBuffer, info.sha256);
|
|
198
|
+
|
|
199
|
+
await new Promise((resolve, reject) => {
|
|
200
|
+
const stream = Readable.from(tarGzBuffer);
|
|
201
|
+
stream
|
|
202
|
+
.pipe(
|
|
203
|
+
tar.x({
|
|
204
|
+
cwd: binDir,
|
|
205
|
+
filter: (p) =>
|
|
206
|
+
p === TOOL_NAME ||
|
|
207
|
+
p === DOCKER_CRED_HELPER_NAME ||
|
|
208
|
+
p === BAZEL_CRED_HELPER_NAME,
|
|
209
|
+
gzip: true,
|
|
210
|
+
})
|
|
211
|
+
)
|
|
212
|
+
.on("finish", resolve)
|
|
213
|
+
.on("error", reject);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
fs.chmodSync(path.join(binDir, TOOL_NAME), 0o755);
|
|
217
|
+
if (fs.existsSync(path.join(binDir, DOCKER_CRED_HELPER_NAME))) {
|
|
218
|
+
fs.chmodSync(path.join(binDir, DOCKER_CRED_HELPER_NAME), 0o755);
|
|
219
|
+
}
|
|
220
|
+
if (fs.existsSync(path.join(binDir, BAZEL_CRED_HELPER_NAME))) {
|
|
221
|
+
fs.chmodSync(path.join(binDir, BAZEL_CRED_HELPER_NAME), 0o755);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
setInstalledVersion(info.version);
|
|
225
|
+
|
|
226
|
+
console.error(`nsc ${info.version} installed successfully to ${getBinaryPath()}`);
|
|
227
|
+
|
|
228
|
+
return { alreadyInstalled: false, path: getBinaryPath(), version: info.version };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function update() {
|
|
232
|
+
const installedVersion = getInstalledVersion();
|
|
233
|
+
const versionInfo = await fetchVersionInfo();
|
|
234
|
+
|
|
235
|
+
if (installedVersion && installedVersion === versionInfo.version) {
|
|
236
|
+
return { alreadyLatest: true, version: installedVersion };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
console.error("Updating nsc to the latest version...");
|
|
240
|
+
return install({ force: true, versionInfo });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = {
|
|
244
|
+
install,
|
|
245
|
+
update,
|
|
246
|
+
isInstalled,
|
|
247
|
+
getBinaryPath,
|
|
248
|
+
getDockerCredHelperPath,
|
|
249
|
+
getBazelCredHelperPath,
|
|
250
|
+
getInstalledVersion,
|
|
251
|
+
getBinDir,
|
|
252
|
+
fetchVersionInfo,
|
|
253
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@namespacelabs/cli",
|
|
3
|
+
"version": "0.0.453",
|
|
4
|
+
"description": "Namespace CLI",
|
|
5
|
+
"homepage": "https://namespace.so",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/namespacelabs/foundation.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"nsc": "node bin/cli.js",
|
|
13
|
+
"test": "jest --forceExit"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"nsc": "bin/cli.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin",
|
|
20
|
+
"lib"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=14"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"tar": "^7.4.3"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"jest": "^29.7.0"
|
|
30
|
+
}
|
|
31
|
+
}
|