@sh-renjian/sw 0.1.0-rc.2 → 0.1.0-rc.3

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
@@ -1,6 +1,12 @@
1
1
  # @sh-renjian/sw
2
2
 
3
- `@sh-renjian/sw` 是 `sw` CLI npm 入口包。
3
+ `@sh-renjian/sw` 是 `sw` CLI 的统一 npm 入口包。
4
+
5
+ 它提供三类命令入口:
6
+
7
+ - `sw toolchain ...`:安装或检查本地官方 Worker toolchain(`wrangler`、`create-cloudflare`)。
8
+ - `sw worker ...`:委托官方 Worker 生命周期命令。
9
+ - `sw platform ...`:调用 sansi 自部署平台控制面。
4
10
 
5
11
  安装后会按当前平台自动选择对应的预编译二进制:
6
12
 
@@ -13,6 +19,12 @@ npx @sh-renjian/sw --version
13
19
  ```sh
14
20
  npm install -g @sh-renjian/sw
15
21
  sw --version
22
+ sw toolchain doctor
16
23
  ```
17
24
 
25
+ 补充说明:
26
+
27
+ - `sw toolchain ...` / `sw worker ...` 当前委托官方 `wrangler@4.x` 与 `create-cloudflare@2.x`,实测要求 `Node.js >= 22.0.0`,仓库验证版本为 `22.22.1`。
28
+ - `sw toolchain install` 会在安装后继续验证 `wrangler`、`create-cloudflare` 是否能在当前 Node 环境下启动;如果只是下载成功但不可执行,命令会直接失败。
29
+
18
30
  支持的平台以发布说明为准。
package/bin/sw.js CHANGED
@@ -1,20 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { spawnSync } from "node:child_process";
4
-
5
- import { installedBinaryPath } from "../lib/platform.mjs";
3
+ import { runCli } from "../lib/cli.mjs";
6
4
 
7
5
  try {
8
- const binaryPath = installedBinaryPath();
9
- const result = spawnSync(binaryPath, process.argv.slice(2), {
10
- stdio: "inherit"
11
- });
12
- if (result.error) {
13
- throw result.error;
14
- }
15
- process.exit(result.status ?? 1);
6
+ process.exit(runCli(process.argv.slice(2)));
16
7
  } catch (error) {
17
8
  const detail = error instanceof Error ? error.message : String(error);
18
- console.error(`Unable to locate the installed @sh-renjian/sw binary for ${process.platform}/${process.arch}: ${detail}`);
9
+ console.error(`sw CLI failed: ${detail}`);
19
10
  process.exit(1);
20
11
  }
package/lib/cli.mjs ADDED
@@ -0,0 +1,403 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+
6
+ import { installedBinaryPath } from "./platform.mjs";
7
+
8
+ const PLATFORM_ALIASES = new Set([
9
+ "login",
10
+ "auth",
11
+ "logout",
12
+ "deploy",
13
+ "workers",
14
+ "deployments",
15
+ "tail",
16
+ "d1",
17
+ "r2",
18
+ "bindings",
19
+ "version",
20
+ "--version",
21
+ "-v",
22
+ ]);
23
+
24
+ const DEFAULT_WRANGLER_VERSION = "latest";
25
+ const DEFAULT_CREATE_CLOUDFLARE_VERSION = "latest";
26
+
27
+ function commandFileName(command, platform = process.platform) {
28
+ return platform === "win32" ? `${command}.cmd` : command;
29
+ }
30
+
31
+ function parseSharedFlags(argv) {
32
+ let cwd = process.cwd();
33
+ const forwardedArgs = [];
34
+ for (let index = 0; index < argv.length; index += 1) {
35
+ const value = argv[index];
36
+ if (value === "--") {
37
+ forwardedArgs.push(...argv.slice(index + 1));
38
+ break;
39
+ }
40
+ if (value === "--cwd") {
41
+ const next = argv[index + 1];
42
+ if (next == null) {
43
+ throw new Error("--cwd requires a value");
44
+ }
45
+ cwd = path.resolve(next);
46
+ index += 1;
47
+ continue;
48
+ }
49
+ forwardedArgs.push(value);
50
+ }
51
+ return { cwd, forwardedArgs };
52
+ }
53
+
54
+ function parseToolchainFlags(argv) {
55
+ const options = {
56
+ json: false,
57
+ cwd: process.cwd(),
58
+ wranglerVersion: DEFAULT_WRANGLER_VERSION,
59
+ createCloudflareVersion: DEFAULT_CREATE_CLOUDFLARE_VERSION,
60
+ };
61
+ for (let index = 0; index < argv.length; index += 1) {
62
+ const value = argv[index];
63
+ switch (value) {
64
+ case "--json":
65
+ options.json = true;
66
+ break;
67
+ case "--cwd":
68
+ if (argv[index + 1] == null) {
69
+ throw new Error("--cwd requires a value");
70
+ }
71
+ options.cwd = path.resolve(argv[index + 1]);
72
+ index += 1;
73
+ break;
74
+ case "--wrangler-version":
75
+ if (argv[index + 1] == null) {
76
+ throw new Error("--wrangler-version requires a value");
77
+ }
78
+ options.wranglerVersion = argv[index + 1];
79
+ index += 1;
80
+ break;
81
+ case "--create-cloudflare-version":
82
+ if (argv[index + 1] == null) {
83
+ throw new Error("--create-cloudflare-version requires a value");
84
+ }
85
+ options.createCloudflareVersion = argv[index + 1];
86
+ index += 1;
87
+ break;
88
+ default:
89
+ throw new Error(`unknown flag ${value}`);
90
+ }
91
+ }
92
+ return options;
93
+ }
94
+
95
+ function isExecutable(filePath) {
96
+ try {
97
+ fs.accessSync(filePath, fs.constants.X_OK);
98
+ return true;
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ function projectBinaryPath(command, cwd, platform = process.platform) {
105
+ return path.join(cwd, "node_modules", ".bin", commandFileName(command, platform));
106
+ }
107
+
108
+ function toolchainBinaryPath(command, rootDir, platform = process.platform) {
109
+ return path.join(rootDir, "node_modules", ".bin", commandFileName(command, platform));
110
+ }
111
+
112
+ function missingToolchainExecutables(rootDir) {
113
+ const requiredCommands = ["wrangler", "create-cloudflare"];
114
+ return requiredCommands.filter((command) => !isExecutable(toolchainBinaryPath(command, rootDir)));
115
+ }
116
+
117
+ function unusableToolchainExecutables(rootDir) {
118
+ const requiredCommands = ["wrangler", "create-cloudflare"];
119
+ return requiredCommands.filter((command) => {
120
+ const executablePath = toolchainBinaryPath(command, rootDir);
121
+ return runCommand(executablePath, ["--help"], { stdio: "pipe" }) !== 0;
122
+ });
123
+ }
124
+
125
+ function resolveInstalledToolchainCommand(command, rootDir) {
126
+ const toolchainPath = toolchainBinaryPath(command, rootDir);
127
+ if (isExecutable(toolchainPath)) {
128
+ return { command: toolchainPath, source: "sw-toolchain", path: toolchainPath };
129
+ }
130
+ return { command: null, source: "missing", path: toolchainPath };
131
+ }
132
+
133
+ function resolveToolCommand(command, cwd, rootDir) {
134
+ const projectPath = projectBinaryPath(command, cwd);
135
+ if (isExecutable(projectPath)) {
136
+ return { command: projectPath, source: "project-local", path: projectPath };
137
+ }
138
+ const toolchainPath = toolchainBinaryPath(command, rootDir);
139
+ if (isExecutable(toolchainPath)) {
140
+ return { command: toolchainPath, source: "sw-toolchain", path: toolchainPath };
141
+ }
142
+ return { command: null, source: "missing", path: toolchainPath };
143
+ }
144
+
145
+ function runCommand(command, args, options = {}) {
146
+ const result = spawnSync(command, args, {
147
+ cwd: options.cwd ?? process.cwd(),
148
+ stdio: options.stdio ?? "inherit",
149
+ env: { ...process.env, ...(options.env ?? {}) },
150
+ });
151
+ if (result.error) {
152
+ throw result.error;
153
+ }
154
+ return result.status ?? 1;
155
+ }
156
+
157
+ function printUsage(body) {
158
+ process.stdout.write(`${body.trim()}\n`);
159
+ return 0;
160
+ }
161
+
162
+ function globalUsage() {
163
+ return `
164
+ Usage: sw <namespace|platform-command> [options]
165
+
166
+ Namespaces:
167
+ worker Delegate Worker project lifecycle to official create-cloudflare / wrangler
168
+ toolchain Install or inspect the local official toolchain used by sw
169
+ platform Talk to sansi Worker Platform Server
170
+
171
+ Platform command compatibility aliases:
172
+ login auth logout deploy workers deployments tail d1 r2 bindings version
173
+ `;
174
+ }
175
+
176
+ function workerUsage() {
177
+ return `
178
+ Usage: sw worker <command> [options]
179
+
180
+ Commands:
181
+ init
182
+ dev
183
+ deploy [cloudflare]
184
+ d1
185
+ r2
186
+ versions
187
+ setup
188
+ tail
189
+
190
+ Shared flags:
191
+ --cwd <project-dir>
192
+ -- <extra flags passed through to the official tool>
193
+ `;
194
+ }
195
+
196
+ function toolchainUsage() {
197
+ return `
198
+ Usage: sw toolchain <command> [options]
199
+
200
+ Commands:
201
+ install
202
+ doctor
203
+ `;
204
+ }
205
+
206
+ function printJSON(value) {
207
+ process.stdout.write(`${JSON.stringify(value)}\n`);
208
+ return 0;
209
+ }
210
+
211
+ function printToolchainStatus(status, json = false) {
212
+ if (json) {
213
+ return printJSON({ ok: true, ...status });
214
+ }
215
+ process.stdout.write(
216
+ [
217
+ `toolchainRoot: ${status.toolchainRoot}`,
218
+ `wrangler: ${status.wrangler.source}${status.wrangler.path ? ` (${status.wrangler.path})` : ""}`,
219
+ `create-cloudflare: ${status.createCloudflare.source}${status.createCloudflare.path ? ` (${status.createCloudflare.path})` : ""}`,
220
+ `node: ${status.node.version}`,
221
+ ].join("\n") + "\n",
222
+ );
223
+ return 0;
224
+ }
225
+
226
+ function toolchainStatus(cwd, rootDir) {
227
+ return {
228
+ toolchainRoot: rootDir,
229
+ node: {
230
+ version: process.version,
231
+ executable: process.execPath,
232
+ },
233
+ wrangler: resolveToolCommand("wrangler", cwd, rootDir),
234
+ createCloudflare: resolveToolCommand("create-cloudflare", cwd, rootDir),
235
+ };
236
+ }
237
+
238
+ function installedToolchainStatus(rootDir) {
239
+ return {
240
+ toolchainRoot: rootDir,
241
+ node: {
242
+ version: process.version,
243
+ executable: process.execPath,
244
+ },
245
+ wrangler: resolveInstalledToolchainCommand("wrangler", rootDir),
246
+ createCloudflare: resolveInstalledToolchainCommand("create-cloudflare", rootDir),
247
+ };
248
+ }
249
+
250
+ function runToolchainInstall(args) {
251
+ const options = parseToolchainFlags(args);
252
+ const rootDir = toolchainRootDir();
253
+ fs.mkdirSync(rootDir, { recursive: true });
254
+ const status = runCommand("npm", [
255
+ "install",
256
+ "--no-save",
257
+ "--prefix",
258
+ rootDir,
259
+ `wrangler@${options.wranglerVersion}`,
260
+ `create-cloudflare@${options.createCloudflareVersion}`,
261
+ ]);
262
+ if (status !== 0) {
263
+ return status;
264
+ }
265
+ const missing = missingToolchainExecutables(rootDir);
266
+ if (missing.length > 0) {
267
+ process.stderr.write(
268
+ `toolchain install did not produce required executables: ${missing.join(", ")}\n`,
269
+ );
270
+ return 1;
271
+ }
272
+ const unusable = unusableToolchainExecutables(rootDir);
273
+ if (unusable.length > 0) {
274
+ process.stderr.write(
275
+ `toolchain install verification failed: ${unusable.join(", ")}\n`,
276
+ );
277
+ return 1;
278
+ }
279
+ return printToolchainStatus(installedToolchainStatus(rootDir), options.json);
280
+ }
281
+
282
+ function runToolchainDoctor(args) {
283
+ const options = parseToolchainFlags(args);
284
+ return printToolchainStatus(toolchainStatus(options.cwd, toolchainRootDir()), options.json);
285
+ }
286
+
287
+ function runToolchainCommand(args) {
288
+ if (args.length === 0 || args[0] === "help" || args[0] === "--help" || args[0] === "-h") {
289
+ return printUsage(toolchainUsage());
290
+ }
291
+ const [subcommand, ...rest] = args;
292
+ switch (subcommand) {
293
+ case "install":
294
+ return runToolchainInstall(rest);
295
+ case "doctor":
296
+ return runToolchainDoctor(rest);
297
+ default:
298
+ process.stderr.write(`unknown sw toolchain command: ${subcommand}\n`);
299
+ return 1;
300
+ }
301
+ }
302
+
303
+ function runWorkerCommand(args) {
304
+ if (args.length === 0 || args[0] === "help" || args[0] === "--help" || args[0] === "-h") {
305
+ return printUsage(workerUsage());
306
+ }
307
+ const { cwd, forwardedArgs } = parseSharedFlags(args);
308
+ if (forwardedArgs.length === 0) {
309
+ return printUsage(workerUsage());
310
+ }
311
+ if (forwardedArgs[0] === "package" && forwardedArgs[1] === "platform") {
312
+ process.stderr.write("sw worker package platform is not implemented yet\n");
313
+ return 1;
314
+ }
315
+
316
+ let commandName = "wrangler";
317
+ let childArgs = [...forwardedArgs];
318
+ if (forwardedArgs[0] === "init") {
319
+ commandName = "create-cloudflare";
320
+ childArgs = forwardedArgs.slice(1);
321
+ } else if (forwardedArgs[0] === "deploy" && forwardedArgs[1] === "cloudflare") {
322
+ childArgs = [forwardedArgs[0], ...forwardedArgs.slice(2)];
323
+ }
324
+
325
+ const resolved = resolveToolCommand(commandName, cwd, toolchainRootDir());
326
+ if (!resolved.command) {
327
+ process.stderr.write(
328
+ `missing ${commandName} toolchain; run "sw toolchain install" or install it in ${cwd}\n`,
329
+ );
330
+ return 1;
331
+ }
332
+ return runCommand(resolved.command, childArgs, { cwd });
333
+ }
334
+
335
+ function runPlatformCommand(args) {
336
+ const binaryPath = installedBinaryPath();
337
+ return runCommand(binaryPath, args);
338
+ }
339
+
340
+ export function classifyCommand(argv) {
341
+ if (argv.length === 0) {
342
+ return { kind: "help", forwardedArgs: [], compatAlias: false };
343
+ }
344
+ if (argv[0] === "platform") {
345
+ return {
346
+ kind: "platform",
347
+ forwardedArgs: argv.slice(1),
348
+ compatAlias: false,
349
+ };
350
+ }
351
+ if (argv[0] === "worker") {
352
+ return {
353
+ kind: "worker",
354
+ forwardedArgs: argv.slice(1),
355
+ compatAlias: false,
356
+ };
357
+ }
358
+ if (argv[0] === "toolchain") {
359
+ return {
360
+ kind: "toolchain",
361
+ forwardedArgs: argv.slice(1),
362
+ compatAlias: false,
363
+ };
364
+ }
365
+ if (PLATFORM_ALIASES.has(argv[0])) {
366
+ return {
367
+ kind: "platform",
368
+ forwardedArgs: argv,
369
+ compatAlias: true,
370
+ };
371
+ }
372
+ return {
373
+ kind: "unknown",
374
+ forwardedArgs: argv,
375
+ compatAlias: false,
376
+ };
377
+ }
378
+
379
+ export function toolchainRootDir(env = process.env) {
380
+ const home = env.HOME?.trim() || os.homedir();
381
+ const configRoot = env.XDG_CONFIG_HOME?.trim() || path.join(home, ".config");
382
+ return path.join(configRoot, "sansi-worker", "sw", "toolchain");
383
+ }
384
+
385
+ export function runCli(argv = process.argv.slice(2)) {
386
+ if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h" || argv[0] === "help") {
387
+ return printUsage(globalUsage());
388
+ }
389
+
390
+ const classification = classifyCommand(argv);
391
+ switch (classification.kind) {
392
+ case "platform":
393
+ return runPlatformCommand(classification.forwardedArgs.length > 0 ? classification.forwardedArgs : ["--help"]);
394
+ case "worker":
395
+ return runWorkerCommand(classification.forwardedArgs);
396
+ case "toolchain":
397
+ return runToolchainCommand(classification.forwardedArgs);
398
+ default:
399
+ process.stderr.write(`unknown sw command: ${argv[0]}\n`);
400
+ process.stderr.write("run `sw --help` to see the unified command model\n");
401
+ return 1;
402
+ }
403
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sh-renjian/sw",
3
- "version": "0.1.0-rc.2",
3
+ "version": "0.1.0-rc.3",
4
4
  "description": "sansi Worker Platform CLI entry package",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -25,10 +25,10 @@
25
25
  "sh-renjian-sw": "bin/sw.js"
26
26
  },
27
27
  "optionalDependencies": {
28
- "@sh-renjian/sw-darwin-arm64": "0.1.0-rc.2",
29
- "@sh-renjian/sw-darwin-x64": "0.1.0-rc.2",
30
- "@sh-renjian/sw-linux-arm64": "0.1.0-rc.2",
31
- "@sh-renjian/sw-linux-x64": "0.1.0-rc.2",
32
- "@sh-renjian/sw-win32-x64": "0.1.0-rc.2"
28
+ "@sh-renjian/sw-darwin-arm64": "0.1.0-rc.3",
29
+ "@sh-renjian/sw-darwin-x64": "0.1.0-rc.3",
30
+ "@sh-renjian/sw-linux-arm64": "0.1.0-rc.3",
31
+ "@sh-renjian/sw-linux-x64": "0.1.0-rc.3",
32
+ "@sh-renjian/sw-win32-x64": "0.1.0-rc.3"
33
33
  }
34
34
  }