@fastmoss/cli 0.1.0

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,72 @@
1
+ # @fastmoss/cli
2
+
3
+ FastMoss CLI launcher for npm and npx.
4
+
5
+ This package does not bundle the Go binary itself. On first run it downloads the matching `fastmoss` binary from GitHub Releases, stores it in a local cache directory, and then forwards all CLI arguments to that binary.
6
+
7
+ ## Usage
8
+
9
+ Run without installing:
10
+
11
+ ```bash
12
+ npx @fastmoss/cli
13
+ ```
14
+
15
+ Install globally:
16
+
17
+ ```bash
18
+ npm install -g @fastmoss/cli
19
+ fastmoss
20
+ ```
21
+
22
+ Common examples:
23
+
24
+ ```bash
25
+ fastmoss tools
26
+ fastmoss call --tool creator_search --args '{"keywords":"beauty","region":"US","page":1,"pagesize":10}'
27
+ fastmoss stdio
28
+ ```
29
+
30
+ ## Supported Platforms
31
+
32
+ - macOS `amd64`
33
+ - macOS `arm64`
34
+ - Linux `amd64`
35
+ - Linux `arm64`
36
+ - Windows `amd64`
37
+
38
+ ## Cache Directory
39
+
40
+ The downloaded binary is cached here by default:
41
+
42
+ ```text
43
+ ~/.fastmoss/bin/<version>/<platform>/
44
+ ```
45
+
46
+ You can override the cache directory:
47
+
48
+ ```bash
49
+ FASTMOSS_CACHE_DIR=/custom/cache/dir npx @fastmoss/cli
50
+ ```
51
+
52
+ ## Download Source
53
+
54
+ By default the wrapper downloads binaries from the public GitHub release repository configured in `package.json`.
55
+
56
+ For internal debugging or private release mirrors, you can override the base URL:
57
+
58
+ ```bash
59
+ FASTMOSS_DOWNLOAD_BASE_URL=https://downloads.example.com/releases npx @fastmoss/cli
60
+ ```
61
+
62
+ The wrapper will request one of these asset names depending on platform:
63
+
64
+ - `fastmoss-darwin-amd64`
65
+ - `fastmoss-darwin-arm64`
66
+ - `fastmoss-linux-amd64`
67
+ - `fastmoss-linux-arm64`
68
+ - `fastmoss-windows-amd64.exe`
69
+
70
+ ## Release Workflow
71
+
72
+ The private source repository prepares this package and the matching binaries. The public GitHub release repository then publishes the package and hosts the release assets.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+
3
+ const packageJSON = require("../package.json");
4
+ const { runCLI } = require("../lib/runtime");
5
+
6
+ runCLI({
7
+ version: packageJSON.version,
8
+ configuredDownloadBaseURL: packageJSON.fastmoss?.downloadBaseURL,
9
+ }).catch((error) => {
10
+ process.stderr.write(`fastmoss wrapper error: ${error.message}\n`);
11
+ process.exit(1);
12
+ });
package/lib/runtime.js ADDED
@@ -0,0 +1,274 @@
1
+ const fs = require("node:fs");
2
+ const os = require("node:os");
3
+ const path = require("node:path");
4
+ const http = require("node:http");
5
+ const https = require("node:https");
6
+ const { pipeline } = require("node:stream/promises");
7
+ const { spawn } = require("node:child_process");
8
+
9
+ const DEFAULT_DOWNLOAD_BASE_URL =
10
+ "https://github.com/FastMoss/cli/releases/download";
11
+
12
+ const PLATFORM_TARGETS = {
13
+ "darwin:x64": {
14
+ assetName: "fastmoss-darwin-amd64",
15
+ binaryName: "fastmoss",
16
+ cacheKey: "darwin-amd64",
17
+ },
18
+ "darwin:arm64": {
19
+ assetName: "fastmoss-darwin-arm64",
20
+ binaryName: "fastmoss",
21
+ cacheKey: "darwin-arm64",
22
+ },
23
+ "linux:x64": {
24
+ assetName: "fastmoss-linux-amd64",
25
+ binaryName: "fastmoss",
26
+ cacheKey: "linux-amd64",
27
+ },
28
+ "linux:arm64": {
29
+ assetName: "fastmoss-linux-arm64",
30
+ binaryName: "fastmoss",
31
+ cacheKey: "linux-arm64",
32
+ },
33
+ "win32:x64": {
34
+ assetName: "fastmoss-windows-amd64.exe",
35
+ binaryName: "fastmoss.exe",
36
+ cacheKey: "windows-amd64",
37
+ },
38
+ };
39
+
40
+ function resolvePlatformTarget({
41
+ platform = process.platform,
42
+ arch = process.arch,
43
+ } = {}) {
44
+ const key = `${platform}:${arch}`;
45
+ const target = PLATFORM_TARGETS[key];
46
+ if (!target) {
47
+ throw new Error(
48
+ `Unsupported platform: ${platform}/${arch}. Supported targets: ${Object.keys(
49
+ PLATFORM_TARGETS,
50
+ )
51
+ .map((value) => value.replace(":", "/"))
52
+ .join(", ")}`,
53
+ );
54
+ }
55
+ return target;
56
+ }
57
+
58
+ function resolveCacheRoot({
59
+ env = process.env,
60
+ homeDir = os.homedir(),
61
+ } = {}) {
62
+ const override = String(env.FASTMOSS_CACHE_DIR || "").trim();
63
+ if (override !== "") {
64
+ return override;
65
+ }
66
+ return path.join(homeDir, ".fastmoss", "bin");
67
+ }
68
+
69
+ function resolveBinaryPath({ cacheRoot, version, target }) {
70
+ return path.join(cacheRoot, version, target.cacheKey, target.binaryName);
71
+ }
72
+
73
+ function resolveDownloadBaseURL({
74
+ env = process.env,
75
+ configuredDownloadBaseURL = "",
76
+ } = {}) {
77
+ const envOverride = String(env.FASTMOSS_DOWNLOAD_BASE_URL || "").trim();
78
+ if (envOverride !== "") {
79
+ return envOverride.replace(/\/+$/, "");
80
+ }
81
+
82
+ const configured = String(configuredDownloadBaseURL || "").trim();
83
+ if (configured !== "") {
84
+ return configured.replace(/\/+$/, "");
85
+ }
86
+
87
+ return DEFAULT_DOWNLOAD_BASE_URL;
88
+ }
89
+
90
+ function buildDownloadURL({
91
+ version,
92
+ target,
93
+ env = process.env,
94
+ configuredDownloadBaseURL = "",
95
+ }) {
96
+ const baseURL = resolveDownloadBaseURL({ env, configuredDownloadBaseURL });
97
+ return `${baseURL}/v${version}/${target.assetName}`;
98
+ }
99
+
100
+ async function ensureBinary({
101
+ version,
102
+ env = process.env,
103
+ platform = process.platform,
104
+ arch = process.arch,
105
+ homeDir = os.homedir(),
106
+ configuredDownloadBaseURL = "",
107
+ }) {
108
+ const target = resolvePlatformTarget({ platform, arch });
109
+ const cacheRoot = resolveCacheRoot({ env, homeDir });
110
+ const binaryPath = resolveBinaryPath({ cacheRoot, version, target });
111
+
112
+ if (await fileExists(binaryPath)) {
113
+ return { binaryPath, downloaded: false };
114
+ }
115
+
116
+ await fs.promises.mkdir(path.dirname(binaryPath), { recursive: true });
117
+ const downloadURL = buildDownloadURL({
118
+ version,
119
+ target,
120
+ env,
121
+ configuredDownloadBaseURL,
122
+ });
123
+ const tempPath = `${binaryPath}.download`;
124
+
125
+ await downloadFile(downloadURL, tempPath);
126
+
127
+ if (platform !== "win32") {
128
+ await fs.promises.chmod(tempPath, 0o755);
129
+ }
130
+
131
+ await fs.promises.rename(tempPath, binaryPath);
132
+ return { binaryPath, downloaded: true, downloadURL };
133
+ }
134
+
135
+ async function fileExists(filePath) {
136
+ try {
137
+ const stat = await fs.promises.stat(filePath);
138
+ return stat.isFile() && stat.size > 0;
139
+ } catch (error) {
140
+ if (error && error.code === "ENOENT") {
141
+ return false;
142
+ }
143
+ throw error;
144
+ }
145
+ }
146
+
147
+ async function downloadFile(url, destination) {
148
+ try {
149
+ await downloadWithRedirects(url, destination, 0);
150
+ } catch (error) {
151
+ await fs.promises.rm(destination, { force: true }).catch(() => {});
152
+ throw error;
153
+ }
154
+ }
155
+
156
+ function downloadWithRedirects(url, destination, redirectCount) {
157
+ if (redirectCount > 5) {
158
+ return Promise.reject(new Error("Too many redirects while downloading fastmoss"));
159
+ }
160
+
161
+ return new Promise((resolve, reject) => {
162
+ const client = url.startsWith("https://") ? https : http;
163
+ const request = client.get(
164
+ url,
165
+ {
166
+ headers: {
167
+ "User-Agent": "fastmoss-npm-wrapper",
168
+ },
169
+ },
170
+ async (response) => {
171
+ const statusCode = response.statusCode || 0;
172
+
173
+ if (
174
+ statusCode >= 300 &&
175
+ statusCode < 400 &&
176
+ response.headers.location
177
+ ) {
178
+ response.resume();
179
+ try {
180
+ await downloadWithRedirects(
181
+ response.headers.location,
182
+ destination,
183
+ redirectCount + 1,
184
+ );
185
+ resolve();
186
+ } catch (error) {
187
+ reject(error);
188
+ }
189
+ return;
190
+ }
191
+
192
+ if (statusCode !== 200) {
193
+ response.resume();
194
+ reject(
195
+ new Error(
196
+ `Failed to download fastmoss binary: HTTP ${statusCode} from ${url}`,
197
+ ),
198
+ );
199
+ return;
200
+ }
201
+
202
+ const output = fs.createWriteStream(destination, { mode: 0o755 });
203
+ try {
204
+ await pipeline(response, output);
205
+ resolve();
206
+ } catch (error) {
207
+ reject(error);
208
+ }
209
+ },
210
+ );
211
+
212
+ request.on("error", reject);
213
+ });
214
+ }
215
+
216
+ async function runCLI({
217
+ version,
218
+ args = process.argv.slice(2),
219
+ env = process.env,
220
+ platform = process.platform,
221
+ arch = process.arch,
222
+ homeDir = os.homedir(),
223
+ stdout = process.stdout,
224
+ stderr = process.stderr,
225
+ configuredDownloadBaseURL = "",
226
+ } = {}) {
227
+ if (
228
+ args.length === 1 &&
229
+ ["--version", "-v", "version"].includes(args[0])
230
+ ) {
231
+ stdout.write(`${version}\n`);
232
+ return;
233
+ }
234
+
235
+ const { binaryPath, downloaded, downloadURL } = await ensureBinary({
236
+ version,
237
+ env,
238
+ platform,
239
+ arch,
240
+ homeDir,
241
+ configuredDownloadBaseURL,
242
+ });
243
+
244
+ if (downloaded) {
245
+ stderr.write(`Downloading fastmoss ${version} from ${downloadURL}\n`);
246
+ }
247
+
248
+ await new Promise((resolve, reject) => {
249
+ const child = spawn(binaryPath, args, {
250
+ stdio: ["inherit", stdout, stderr],
251
+ });
252
+
253
+ child.on("error", reject);
254
+ child.on("exit", (code, signal) => {
255
+ if (signal) {
256
+ reject(new Error(`fastmoss exited with signal ${signal}`));
257
+ return;
258
+ }
259
+ process.exitCode = code || 0;
260
+ resolve();
261
+ });
262
+ });
263
+ }
264
+
265
+ module.exports = {
266
+ DEFAULT_DOWNLOAD_BASE_URL,
267
+ buildDownloadURL,
268
+ ensureBinary,
269
+ resolveBinaryPath,
270
+ resolveCacheRoot,
271
+ resolveDownloadBaseURL,
272
+ resolvePlatformTarget,
273
+ runCLI,
274
+ };
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@fastmoss/cli",
3
+ "version": "0.1.0",
4
+ "description": "FastMoss CLI launcher for npm and npx",
5
+ "license": "UNLICENSED",
6
+ "private": false,
7
+ "bin": {
8
+ "fastmoss": "./bin/fastmoss.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "lib",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "test": "node --test ./test/*.test.js"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "fastmoss": {
22
+ "downloadBaseURL": "https://github.com/FastMoss/cli/releases/download"
23
+ }
24
+ }