supacloud 0.5.4 → 0.6.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 +14 -10
- package/dist/index.js +91 -28
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -25,19 +25,22 @@ npx --package supacloud supacloudctl admin status
|
|
|
25
25
|
it gives you the full toolset. You can still install either CLI on its own
|
|
26
26
|
(`@supacloud/cli` exposes `supacloud-cli`, `@supacloud/admin` exposes `supacloud-admin`).
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## 更新检查
|
|
29
29
|
|
|
30
|
-
`supacloudctl cli ...` and `supacloudctl admin ...`
|
|
31
|
-
|
|
32
|
-
version bundled with the installed `supacloud` package. It never downloads or executes a newer
|
|
33
|
-
operator CLI implicitly.
|
|
30
|
+
Normal `supacloudctl cli ...` and `supacloudctl admin ...` dispatch is local-only and does not
|
|
31
|
+
contact npm. Run an explicit check when needed:
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
```bash
|
|
34
|
+
supacloudctl check-update
|
|
35
|
+
supacloudctl check-update cli
|
|
36
|
+
supacloudctl check-update admin
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The dispatcher never downloads or executes a newer operator CLI implicitly. To opt into the old
|
|
40
|
+
notify-before-dispatch behavior, set:
|
|
36
41
|
|
|
37
42
|
```bash
|
|
38
|
-
|
|
39
|
-
# or
|
|
40
|
-
SUPACLOUD_AUTO_UPDATE=0 supacloudctl admin status
|
|
43
|
+
SUPACLOUD_AUTO_UPDATE=1 supacloudctl cli status
|
|
41
44
|
```
|
|
42
45
|
|
|
43
46
|
Use `SUPACLOUD_NPM_REGISTRY` or `npm_config_registry` to point the check at a registry mirror.
|
|
@@ -67,10 +70,11 @@ supacloudctl admin --help
|
|
|
67
70
|
|
|
68
71
|
- `SUPABASE_URL` / `SUPACLOUD_API_URL`
|
|
69
72
|
- `SUPABASE_SERVICE_ROLE_KEY` / `SUPACLOUD_API_TOKEN`
|
|
73
|
+
- `SUPACLOUD_PROJECT_REF` when it cannot be inferred from a managed `<ref>.api.*` hostname
|
|
70
74
|
|
|
71
75
|
`supacloudctl admin` expects platform context:
|
|
72
76
|
|
|
73
|
-
- `SUPACLOUD_HOST` + `SUPACLOUD_SSH_KEY` / `SUPACLOUD_SSH_PASS`
|
|
77
|
+
- `SUPACLOUD_HOST` + `SUPACLOUD_SSH_KEY` / `SUPACLOUD_SSH_PASS` + `SUPACLOUD_SSH_HOST_FINGERPRINT`
|
|
74
78
|
- `SUPACLOUD_API_URL` + `SUPACLOUD_API_TOKEN`
|
|
75
79
|
|
|
76
80
|
> Note: `/usr/local/bin/supacloud` is the compiled management-api server binary.
|
package/dist/index.js
CHANGED
|
@@ -75,6 +75,11 @@ function isAutoUpdateDisabled(env = process.env) {
|
|
|
75
75
|
const autoUpdate = env.SUPACLOUD_AUTO_UPDATE;
|
|
76
76
|
return Boolean(autoUpdate && ["0", "false", "no", "off"].includes(autoUpdate.toLowerCase()));
|
|
77
77
|
}
|
|
78
|
+
function isAutoUpdateEnabled(env = process.env) {
|
|
79
|
+
if (isAutoUpdateDisabled(env))
|
|
80
|
+
return false;
|
|
81
|
+
return Boolean(env.SUPACLOUD_AUTO_UPDATE);
|
|
82
|
+
}
|
|
78
83
|
function getNpmRegistry(env = process.env) {
|
|
79
84
|
const registry = env.SUPACLOUD_NPM_REGISTRY || env.npm_config_registry || env.NPM_CONFIG_REGISTRY || DEFAULT_NPM_REGISTRY;
|
|
80
85
|
return registry.endsWith("/") ? registry : `${registry}/`;
|
|
@@ -139,21 +144,23 @@ function isNewerVersion(latestVersion, currentVersion) {
|
|
|
139
144
|
return true;
|
|
140
145
|
return compareSemver(latestVersion, currentVersion) > 0;
|
|
141
146
|
}
|
|
147
|
+
async function updateNoticeForLaunch(target, installed, env, fetchLatest) {
|
|
148
|
+
if (!isAutoUpdateEnabled(env))
|
|
149
|
+
return;
|
|
150
|
+
const latestVersion = await fetchLatest(target.pkg, env);
|
|
151
|
+
if (!latestVersion || !isNewerVersion(latestVersion, installed.version))
|
|
152
|
+
return;
|
|
153
|
+
return installed.version ? `${target.pkg} ${latestVersion} 可用;当前固定使用已安装版本 ${installed.version}。请显式运行包管理器更新。` : `${target.pkg} ${latestVersion} 可用;已安装版本无法识别,请通过包管理器检查锁定版本。`;
|
|
154
|
+
}
|
|
142
155
|
async function createLaunchPlan(target, forwardedArgs, options = {}) {
|
|
143
156
|
const env = options.env ?? process.env;
|
|
144
157
|
const resolveInstalled = options.resolveInstalled ?? resolveInstalledPackage;
|
|
145
158
|
const fetchLatest = options.fetchLatest ?? fetchLatestVersion;
|
|
146
159
|
const installed = resolveInstalled(target.pkg);
|
|
147
|
-
let updateNotice;
|
|
148
|
-
if (!isAutoUpdateDisabled(env)) {
|
|
149
|
-
const latestVersion = await fetchLatest(target.pkg, env);
|
|
150
|
-
if (latestVersion && isNewerVersion(latestVersion, installed?.version ?? null)) {
|
|
151
|
-
updateNotice = installed?.version ? `${target.pkg} ${latestVersion} 可用;当前固定使用已安装版本 ${installed.version}。请显式运行包管理器更新。` : `${target.pkg} ${latestVersion} 可用;请先通过包管理器显式安装。`;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
160
|
if (!installed) {
|
|
155
161
|
throw new Error(`${target.pkg} 未安装。请先通过包管理器显式安装固定版本。`);
|
|
156
162
|
}
|
|
163
|
+
const updateNotice = await updateNoticeForLaunch(target, installed, env, fetchLatest);
|
|
157
164
|
return {
|
|
158
165
|
mode: "local",
|
|
159
166
|
command: options.nodePath ?? process.execPath,
|
|
@@ -162,8 +169,42 @@ async function createLaunchPlan(target, forwardedArgs, options = {}) {
|
|
|
162
169
|
...updateNotice ? { updateNotice } : {}
|
|
163
170
|
};
|
|
164
171
|
}
|
|
172
|
+
async function checkUpdate(target, options = {}) {
|
|
173
|
+
const env = options.env ?? process.env;
|
|
174
|
+
const resolveInstalled = options.resolveInstalled ?? resolveInstalledPackage;
|
|
175
|
+
const fetchLatest = options.fetchLatest ?? fetchLatestVersion;
|
|
176
|
+
const installed = resolveInstalled(target.pkg);
|
|
177
|
+
if (!installed)
|
|
178
|
+
return missingPackageCheck(target.pkg);
|
|
179
|
+
const latestVersion = await fetchLatest(target.pkg, env);
|
|
180
|
+
if (!latestVersion)
|
|
181
|
+
return unavailableRegistryCheck(target.pkg, installed.version);
|
|
182
|
+
return completedUpdateCheck(target.pkg, installed.version, latestVersion);
|
|
183
|
+
}
|
|
184
|
+
function missingPackageCheck(packageName) {
|
|
185
|
+
return { packageName, currentVersion: null, latestVersion: null, status: "not_installed" };
|
|
186
|
+
}
|
|
187
|
+
function unavailableRegistryCheck(packageName, currentVersion) {
|
|
188
|
+
return { packageName, currentVersion, latestVersion: null, status: "registry_unavailable" };
|
|
189
|
+
}
|
|
190
|
+
function completedUpdateCheck(packageName, currentVersion, latestVersion) {
|
|
191
|
+
const status = isNewerVersion(latestVersion, currentVersion) ? "update_available" : "up_to_date";
|
|
192
|
+
return { packageName, currentVersion, latestVersion, status };
|
|
193
|
+
}
|
|
194
|
+
function formatUpdateCheck(check) {
|
|
195
|
+
switch (check.status) {
|
|
196
|
+
case "update_available":
|
|
197
|
+
return `${check.packageName}: ${check.latestVersion} 可用(当前 ${check.currentVersion ?? "未知"})`;
|
|
198
|
+
case "up_to_date":
|
|
199
|
+
return `${check.packageName}: 已是最新版本 ${check.currentVersion ?? check.latestVersion ?? "未知"}`;
|
|
200
|
+
case "not_installed":
|
|
201
|
+
return `${check.packageName}: 未安装`;
|
|
202
|
+
case "registry_unavailable":
|
|
203
|
+
return `${check.packageName}: 无法从 npm registry 获取版本信息`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
165
206
|
function buildHelp() {
|
|
166
|
-
const
|
|
207
|
+
const subcommandHelp = Object.entries(SUBCOMMANDS).map(([name, sub]) => ` ${name.padEnd(6)} ${sub.desc}`).join(`
|
|
167
208
|
`);
|
|
168
209
|
return `
|
|
169
210
|
╔═══════════════════════════════════════════════════════════╗
|
|
@@ -174,11 +215,12 @@ function buildHelp() {
|
|
|
174
215
|
用法
|
|
175
216
|
|
|
176
217
|
${COMMAND} <子命令> [args...]
|
|
218
|
+
${COMMAND} check-update [cli|admin]
|
|
177
219
|
${COMMAND} --help
|
|
178
220
|
|
|
179
221
|
子命令
|
|
180
222
|
|
|
181
|
-
${
|
|
223
|
+
${subcommandHelp}
|
|
182
224
|
|
|
183
225
|
示例
|
|
184
226
|
|
|
@@ -188,37 +230,56 @@ ${subs}
|
|
|
188
230
|
${COMMAND} admin status
|
|
189
231
|
${COMMAND} admin project list
|
|
190
232
|
${COMMAND} admin ssh ping
|
|
233
|
+
${COMMAND} check-update
|
|
234
|
+
${COMMAND} check-update cli
|
|
191
235
|
|
|
192
|
-
|
|
193
|
-
|
|
236
|
+
普通分发默认不访问 npm,始终运行已安装的固定版本。
|
|
237
|
+
使用 check-update 显式查询更新;SUPACLOUD_AUTO_UPDATE=1 可选择恢复分发前提示。
|
|
194
238
|
`;
|
|
195
239
|
}
|
|
240
|
+
async function runUpdateCheckCommand(args) {
|
|
241
|
+
const targetName = args[1];
|
|
242
|
+
if (args.length > 2 || targetName && !SUBCOMMANDS[targetName]) {
|
|
243
|
+
console.error(`❌ 未知更新检查目标: ${targetName ?? ""}`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
const targets = targetName ? [SUBCOMMANDS[targetName]] : Object.values(SUBCOMMANDS);
|
|
247
|
+
const checks = await Promise.all(targets.map((target) => checkUpdate(target)));
|
|
248
|
+
for (const check of checks)
|
|
249
|
+
console.log(formatUpdateCheck(check));
|
|
250
|
+
const failed = checks.some((check) => ["registry_unavailable", "not_installed"].includes(check.status));
|
|
251
|
+
process.exit(failed ? 1 : 0);
|
|
252
|
+
}
|
|
253
|
+
async function dispatchSubcommand(target, forwardedArgs) {
|
|
254
|
+
const launchPlan = await createLaunchPlan(target, forwardedArgs);
|
|
255
|
+
if (launchPlan.updateNotice)
|
|
256
|
+
console.error(`ℹ️ ${launchPlan.updateNotice}`);
|
|
257
|
+
const childProcess = spawn(launchPlan.command, launchPlan.args, {
|
|
258
|
+
stdio: "inherit",
|
|
259
|
+
shell: launchPlan.shell
|
|
260
|
+
});
|
|
261
|
+
childProcess.on("close", (code) => process.exit(code ?? 1));
|
|
262
|
+
childProcess.on("error", (error) => {
|
|
263
|
+
console.error(`❌ 启动 ${target.pkg} 失败: ${error.message}`);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
196
267
|
async function run(args) {
|
|
197
|
-
const
|
|
198
|
-
if (!
|
|
268
|
+
const subcommandName = args[0];
|
|
269
|
+
if (!subcommandName || subcommandName === "--help" || subcommandName === "-h" || subcommandName === "help") {
|
|
199
270
|
console.error(buildHelp());
|
|
200
271
|
process.exit(0);
|
|
201
272
|
}
|
|
202
|
-
|
|
273
|
+
if (subcommandName === "check-update")
|
|
274
|
+
await runUpdateCheckCommand(args);
|
|
275
|
+
const target = SUBCOMMANDS[subcommandName];
|
|
203
276
|
if (!target) {
|
|
204
|
-
console.error(`❌ 未知子命令: ${
|
|
277
|
+
console.error(`❌ 未知子命令: ${subcommandName}
|
|
205
278
|
`);
|
|
206
279
|
console.error(buildHelp());
|
|
207
280
|
process.exit(1);
|
|
208
281
|
}
|
|
209
|
-
|
|
210
|
-
if (plan.updateNotice) {
|
|
211
|
-
console.error(`ℹ️ ${plan.updateNotice}`);
|
|
212
|
-
}
|
|
213
|
-
const child = spawn(plan.command, plan.args, {
|
|
214
|
-
stdio: "inherit",
|
|
215
|
-
shell: plan.shell
|
|
216
|
-
});
|
|
217
|
-
child.on("close", (code) => process.exit(code ?? 1));
|
|
218
|
-
child.on("error", (err) => {
|
|
219
|
-
console.error(`❌ 启动 ${target.pkg} 失败: ${err.message}`);
|
|
220
|
-
process.exit(1);
|
|
221
|
-
});
|
|
282
|
+
await dispatchSubcommand(target, args.slice(1));
|
|
222
283
|
}
|
|
223
284
|
function isMainModule(moduleUrl, argvEntry = process.argv[1]) {
|
|
224
285
|
if (!argvEntry)
|
|
@@ -241,11 +302,13 @@ export {
|
|
|
241
302
|
resolveInstalledPackage,
|
|
242
303
|
isNewerVersion,
|
|
243
304
|
isMainModule,
|
|
305
|
+
isAutoUpdateEnabled,
|
|
244
306
|
isAutoUpdateDisabled,
|
|
245
307
|
getNpmRegistry,
|
|
246
308
|
fetchLatestVersion,
|
|
247
309
|
createLaunchPlan,
|
|
248
310
|
compareSemver,
|
|
311
|
+
checkUpdate,
|
|
249
312
|
buildLatestMetadataUrl,
|
|
250
313
|
buildHelp,
|
|
251
314
|
SUBCOMMANDS
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supacloud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Unified entrypoint: `supacloudctl cli` and `supacloudctl admin` in one command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"directory": "packages/supacloud"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@supacloud/cli": "^0.
|
|
35
|
-
"@supacloud/admin": "^0.
|
|
34
|
+
"@supacloud/cli": "^0.11.0",
|
|
35
|
+
"@supacloud/admin": "^0.5.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/bun": "^1.3.14",
|