@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 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
+ }