@pmate/cli 0.24.25 → 0.24.26
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/dist/index.d.ts +10 -0
- package/dist/index.js +304 -8
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1000,6 +1000,16 @@ type ServiceCommandArgs = {
|
|
|
1000
1000
|
onlyKeys?: string[];
|
|
1001
1001
|
baseUrl?: string;
|
|
1002
1002
|
json: boolean;
|
|
1003
|
+
} | {
|
|
1004
|
+
type: Command.Service;
|
|
1005
|
+
action: "env-update-scope";
|
|
1006
|
+
appId: string;
|
|
1007
|
+
fromScope: string;
|
|
1008
|
+
toScope: string;
|
|
1009
|
+
overwrite: boolean;
|
|
1010
|
+
onlyKeys?: string[];
|
|
1011
|
+
baseUrl?: string;
|
|
1012
|
+
json: boolean;
|
|
1003
1013
|
};
|
|
1004
1014
|
type VercelProjectsCommandArgs = {
|
|
1005
1015
|
type: Command.Vercel;
|
package/dist/index.js
CHANGED
|
@@ -10050,6 +10050,19 @@ var AliyunService = class {
|
|
|
10050
10050
|
);
|
|
10051
10051
|
ui.success(`Created FC custom domain: ${input.domainName}`);
|
|
10052
10052
|
}
|
|
10053
|
+
static async deleteFcCustomDomain(domainName, region) {
|
|
10054
|
+
const client = await this.createFcClient(region);
|
|
10055
|
+
try {
|
|
10056
|
+
await client.deleteCustomDomain(domainName);
|
|
10057
|
+
ui.success(`Deleted FC custom domain: ${domainName}`);
|
|
10058
|
+
} catch (error) {
|
|
10059
|
+
if (this.isNotFoundError(error)) {
|
|
10060
|
+
ui.warn(`FC custom domain not found: ${domainName}`);
|
|
10061
|
+
return;
|
|
10062
|
+
}
|
|
10063
|
+
throw error;
|
|
10064
|
+
}
|
|
10065
|
+
}
|
|
10053
10066
|
/** Add a DNS record via Aliyun DNS. */
|
|
10054
10067
|
static async addRecord(input) {
|
|
10055
10068
|
const userConfig = loadPmateConfig();
|
|
@@ -18110,6 +18123,7 @@ function readNamespaceFromPayload(payload) {
|
|
|
18110
18123
|
var import_node_fs20 = require("fs");
|
|
18111
18124
|
var import_promises6 = require("fs/promises");
|
|
18112
18125
|
var import_node_path24 = __toESM(require("path"));
|
|
18126
|
+
var import_node_child_process11 = require("child_process");
|
|
18113
18127
|
var import_promises7 = require("readline/promises");
|
|
18114
18128
|
|
|
18115
18129
|
// src/services/ServiceRuntimeRunner.ts
|
|
@@ -19443,10 +19457,15 @@ var ServiceRuntimeRunner = class {
|
|
|
19443
19457
|
return [];
|
|
19444
19458
|
}
|
|
19445
19459
|
const appId = `${service.namespace}:${service.name}`;
|
|
19446
|
-
const
|
|
19460
|
+
const scopedItems = await registry.listEnvItems(appId, {
|
|
19447
19461
|
scope: stage,
|
|
19448
19462
|
includeSecretMeta: true
|
|
19449
19463
|
});
|
|
19464
|
+
const allItems = await registry.listEnvItems(appId, {
|
|
19465
|
+
scope: "all",
|
|
19466
|
+
includeSecretMeta: true
|
|
19467
|
+
});
|
|
19468
|
+
const items = [...allItems, ...scopedItems];
|
|
19450
19469
|
const requiredEnv = Array.from(
|
|
19451
19470
|
new Set(
|
|
19452
19471
|
items.map((item) => item.key ?? item.name).filter((key) => typeof key === "string" && key.trim().length > 0).map((key) => key.trim())
|
|
@@ -19693,6 +19712,8 @@ var ServiceCommandHandler = class {
|
|
|
19693
19712
|
return;
|
|
19694
19713
|
}
|
|
19695
19714
|
case "delete": {
|
|
19715
|
+
const service = await registry.getService(args.namespace, args.name);
|
|
19716
|
+
await this.deleteServiceResources(service);
|
|
19696
19717
|
const record = await this.request(
|
|
19697
19718
|
baseUrl,
|
|
19698
19719
|
"DELETE",
|
|
@@ -19863,6 +19884,23 @@ var ServiceCommandHandler = class {
|
|
|
19863
19884
|
this.renderJson(payload, args.json);
|
|
19864
19885
|
return;
|
|
19865
19886
|
}
|
|
19887
|
+
case "env-update-scope": {
|
|
19888
|
+
const payload = await this.request(
|
|
19889
|
+
baseUrl,
|
|
19890
|
+
"POST",
|
|
19891
|
+
`${this.buildEnvBasePath(args.appId)}/update-scope`,
|
|
19892
|
+
{
|
|
19893
|
+
fromScope: args.fromScope,
|
|
19894
|
+
toScope: args.toScope,
|
|
19895
|
+
...args.overwrite ? { overwrite: true } : {},
|
|
19896
|
+
...args.onlyKeys?.length ? { onlyKeys: args.onlyKeys } : {}
|
|
19897
|
+
}
|
|
19898
|
+
);
|
|
19899
|
+
ui.success(`Updated env scope for ${args.appId}: ${args.fromScope} -> ${args.toScope}`);
|
|
19900
|
+
await this.syncFcRequiredEnvFromServiceEnv(registry, args.appId, args.toScope);
|
|
19901
|
+
this.renderJson(payload, args.json);
|
|
19902
|
+
return;
|
|
19903
|
+
}
|
|
19866
19904
|
}
|
|
19867
19905
|
}
|
|
19868
19906
|
resolveBaseUrl(baseUrl) {
|
|
@@ -20014,10 +20052,7 @@ var ServiceCommandHandler = class {
|
|
|
20014
20052
|
if (!("type" in service) || service.type !== "fc" || !service.fc) {
|
|
20015
20053
|
return;
|
|
20016
20054
|
}
|
|
20017
|
-
const items = await
|
|
20018
|
-
scope,
|
|
20019
|
-
includeSecretMeta: true
|
|
20020
|
-
});
|
|
20055
|
+
const items = await this.listEffectiveEnvItems(registry, appId, scope);
|
|
20021
20056
|
const requiredEnv = Array.from(
|
|
20022
20057
|
new Set(
|
|
20023
20058
|
items.map((item) => item.key ?? item.name).filter((key) => typeof key === "string" && key.trim().length > 0).map((key) => key.trim())
|
|
@@ -20039,6 +20074,20 @@ var ServiceCommandHandler = class {
|
|
|
20039
20074
|
const normalizedRight = [...right].sort();
|
|
20040
20075
|
return normalizedLeft.every((value, index) => value === normalizedRight[index]);
|
|
20041
20076
|
}
|
|
20077
|
+
async listEffectiveEnvItems(registry, appId, scope) {
|
|
20078
|
+
const scopedItems = await registry.listEnvItems(appId, {
|
|
20079
|
+
scope,
|
|
20080
|
+
includeSecretMeta: true
|
|
20081
|
+
});
|
|
20082
|
+
if (scope === "all") {
|
|
20083
|
+
return scopedItems;
|
|
20084
|
+
}
|
|
20085
|
+
const allItems = await registry.listEnvItems(appId, {
|
|
20086
|
+
scope: "all",
|
|
20087
|
+
includeSecretMeta: true
|
|
20088
|
+
});
|
|
20089
|
+
return [...allItems, ...scopedItems];
|
|
20090
|
+
}
|
|
20042
20091
|
renderList(items, asJson) {
|
|
20043
20092
|
if (asJson) {
|
|
20044
20093
|
ui.plain(JSON.stringify(items, null, 2));
|
|
@@ -20052,6 +20101,206 @@ var ServiceCommandHandler = class {
|
|
|
20052
20101
|
ui.info(formatNamespacedDisplayId(item.namespace, item.name), item);
|
|
20053
20102
|
}
|
|
20054
20103
|
}
|
|
20104
|
+
async deleteServiceResources(service) {
|
|
20105
|
+
switch (this.getServiceType(service)) {
|
|
20106
|
+
case "vite":
|
|
20107
|
+
case "static":
|
|
20108
|
+
await this.deleteStaticResources(service);
|
|
20109
|
+
return;
|
|
20110
|
+
case "node":
|
|
20111
|
+
case "rust":
|
|
20112
|
+
case "python":
|
|
20113
|
+
await this.deleteServerResources(service);
|
|
20114
|
+
return;
|
|
20115
|
+
case "fc":
|
|
20116
|
+
await this.deleteFcResources(service);
|
|
20117
|
+
return;
|
|
20118
|
+
case "ios":
|
|
20119
|
+
case "android":
|
|
20120
|
+
ui.warn(`Service type "${this.getServiceType(service)}" has no runtime resources to delete`);
|
|
20121
|
+
return;
|
|
20122
|
+
default:
|
|
20123
|
+
throw new Error(`Unsupported service type for delete: ${this.getServiceType(service)}`);
|
|
20124
|
+
}
|
|
20125
|
+
}
|
|
20126
|
+
async deleteStaticResources(service) {
|
|
20127
|
+
const site = this.getStaticSiteConfig(service);
|
|
20128
|
+
const bucketName = site.bucket?.trim();
|
|
20129
|
+
const domainName = site.domain?.trim();
|
|
20130
|
+
if (!bucketName) {
|
|
20131
|
+
throw new Error(`Missing site.bucket for ${service.id}`);
|
|
20132
|
+
}
|
|
20133
|
+
if (!domainName) {
|
|
20134
|
+
throw new Error(`Missing site.domain for ${service.id}`);
|
|
20135
|
+
}
|
|
20136
|
+
const subdomain = site.subdomain?.trim();
|
|
20137
|
+
const rr = subdomain || "@";
|
|
20138
|
+
const cdnDomain = subdomain ? `${subdomain}.${domainName}` : domainName;
|
|
20139
|
+
const cname = await AliyunService.getCdnCname(cdnDomain);
|
|
20140
|
+
if (cname) {
|
|
20141
|
+
this.logDeletedRecords(
|
|
20142
|
+
`Removed CNAME record for ${cdnDomain}`,
|
|
20143
|
+
await AliyunService.deleteDomainRecords({
|
|
20144
|
+
domainName,
|
|
20145
|
+
rr,
|
|
20146
|
+
type: "CNAME",
|
|
20147
|
+
value: cname
|
|
20148
|
+
})
|
|
20149
|
+
);
|
|
20150
|
+
} else {
|
|
20151
|
+
ui.warn(`No CDN CNAME found for ${cdnDomain}; deleting matching CNAME records by host.`);
|
|
20152
|
+
this.logDeletedRecords(
|
|
20153
|
+
`Removed CNAME record for ${cdnDomain}`,
|
|
20154
|
+
await AliyunService.deleteDomainRecords({
|
|
20155
|
+
domainName,
|
|
20156
|
+
rr,
|
|
20157
|
+
type: "CNAME"
|
|
20158
|
+
})
|
|
20159
|
+
);
|
|
20160
|
+
}
|
|
20161
|
+
if (!subdomain) {
|
|
20162
|
+
this.logDeletedRecords(
|
|
20163
|
+
`Removed CNAME record for www.${domainName}`,
|
|
20164
|
+
await AliyunService.deleteDomainRecords({
|
|
20165
|
+
domainName,
|
|
20166
|
+
rr: "www",
|
|
20167
|
+
type: "CNAME",
|
|
20168
|
+
value: domainName
|
|
20169
|
+
})
|
|
20170
|
+
);
|
|
20171
|
+
}
|
|
20172
|
+
await AliyunService.deleteCdnDomain(cdnDomain);
|
|
20173
|
+
await AliyunService.deleteBucketAndContents(bucketName);
|
|
20174
|
+
}
|
|
20175
|
+
async deleteServerResources(service) {
|
|
20176
|
+
const network = this.getNetworkConfig(service);
|
|
20177
|
+
const subdomain = network.subdomain?.trim();
|
|
20178
|
+
if (!subdomain) {
|
|
20179
|
+
ui.warn(`No subdomain configured for ${service.id}; nothing to tear down on DNS/nginx.`);
|
|
20180
|
+
return;
|
|
20181
|
+
}
|
|
20182
|
+
const server = network.server?.trim();
|
|
20183
|
+
if (!server) {
|
|
20184
|
+
throw new Error(`Missing network.server for ${service.id}`);
|
|
20185
|
+
}
|
|
20186
|
+
await this.removeRemoteConfig(server, `/etc/nginx/sites-enabled/${subdomain}.conf`);
|
|
20187
|
+
ui.success(`Removed nginx config for ${subdomain} from ${server}`);
|
|
20188
|
+
await this.reloadNginx(server);
|
|
20189
|
+
ui.success(`Reloaded nginx on ${server}`);
|
|
20190
|
+
const domainName = network.domain?.trim() || "pmate.chat";
|
|
20191
|
+
const serverAddress = await resolveServerAddress(server);
|
|
20192
|
+
this.logDeletedRecords(
|
|
20193
|
+
`Removed A record for ${subdomain}.${domainName}`,
|
|
20194
|
+
await AliyunService.deleteDomainRecords({
|
|
20195
|
+
domainName,
|
|
20196
|
+
rr: subdomain,
|
|
20197
|
+
type: "A",
|
|
20198
|
+
value: serverAddress
|
|
20199
|
+
})
|
|
20200
|
+
);
|
|
20201
|
+
}
|
|
20202
|
+
async deleteFcResources(service) {
|
|
20203
|
+
const fc = this.getFcConfig(service);
|
|
20204
|
+
const domainName = fc.domain?.trim();
|
|
20205
|
+
if (!domainName) {
|
|
20206
|
+
ui.warn(`No fc.domain configured for ${service.id}; nothing to tear down on DNS/FC custom domain.`);
|
|
20207
|
+
return;
|
|
20208
|
+
}
|
|
20209
|
+
const region = fc.region?.trim();
|
|
20210
|
+
if (!region) {
|
|
20211
|
+
throw new Error(`Missing fc.region for ${service.id}`);
|
|
20212
|
+
}
|
|
20213
|
+
const subdomain = fc.subdomain?.trim();
|
|
20214
|
+
const rr = subdomain || "@";
|
|
20215
|
+
const host = subdomain ? `${subdomain}.${domainName}` : domainName;
|
|
20216
|
+
await AliyunService.deleteFcCustomDomain(host, region);
|
|
20217
|
+
const accountId = await AliyunService.getAccountId();
|
|
20218
|
+
this.logDeletedRecords(
|
|
20219
|
+
`Removed CNAME record for ${host}`,
|
|
20220
|
+
await AliyunService.deleteDomainRecords({
|
|
20221
|
+
domainName,
|
|
20222
|
+
rr,
|
|
20223
|
+
type: "CNAME",
|
|
20224
|
+
value: `${accountId}.${region}.fc.aliyuncs.com`
|
|
20225
|
+
})
|
|
20226
|
+
);
|
|
20227
|
+
}
|
|
20228
|
+
getServiceType(service) {
|
|
20229
|
+
return "runtimeType" in service ? service.runtimeType : service.type;
|
|
20230
|
+
}
|
|
20231
|
+
getStaticSiteConfig(service) {
|
|
20232
|
+
if ("runtimeType" in service) {
|
|
20233
|
+
return {
|
|
20234
|
+
bucket: service.deploy?.bucket,
|
|
20235
|
+
domain: service.deploy?.domain,
|
|
20236
|
+
subdomain: service.deploy?.subdomain
|
|
20237
|
+
};
|
|
20238
|
+
}
|
|
20239
|
+
return service.site ?? {};
|
|
20240
|
+
}
|
|
20241
|
+
getNetworkConfig(service) {
|
|
20242
|
+
if ("runtimeType" in service) {
|
|
20243
|
+
return {
|
|
20244
|
+
server: service.deploy?.server,
|
|
20245
|
+
domain: service.deploy?.domain,
|
|
20246
|
+
subdomain: service.deploy?.subdomain
|
|
20247
|
+
};
|
|
20248
|
+
}
|
|
20249
|
+
return service.network ?? {};
|
|
20250
|
+
}
|
|
20251
|
+
getFcConfig(service) {
|
|
20252
|
+
if ("runtimeType" in service) {
|
|
20253
|
+
return {
|
|
20254
|
+
region: void 0,
|
|
20255
|
+
domain: service.deploy?.domain,
|
|
20256
|
+
subdomain: service.deploy?.subdomain
|
|
20257
|
+
};
|
|
20258
|
+
}
|
|
20259
|
+
return service.fc ?? {};
|
|
20260
|
+
}
|
|
20261
|
+
logDeletedRecords(message, deletedCount) {
|
|
20262
|
+
if (deletedCount > 0) {
|
|
20263
|
+
ui.success(message, { deletedCount });
|
|
20264
|
+
return;
|
|
20265
|
+
}
|
|
20266
|
+
ui.warn(`${message} skipped`, { deletedCount });
|
|
20267
|
+
}
|
|
20268
|
+
removeRemoteConfig(server, remotePath) {
|
|
20269
|
+
return new Promise((resolve, reject) => {
|
|
20270
|
+
const target = `pmate@${server}`;
|
|
20271
|
+
const child = (0, import_node_child_process11.spawn)("ssh", [target, "sudo", "rm", "-f", remotePath], {
|
|
20272
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
20273
|
+
});
|
|
20274
|
+
child.on("error", (error) => {
|
|
20275
|
+
reject(error);
|
|
20276
|
+
});
|
|
20277
|
+
child.on("close", (code) => {
|
|
20278
|
+
if (code === 0) {
|
|
20279
|
+
resolve();
|
|
20280
|
+
return;
|
|
20281
|
+
}
|
|
20282
|
+
reject(new Error(`ssh to ${server} exited with code ${code}`));
|
|
20283
|
+
});
|
|
20284
|
+
});
|
|
20285
|
+
}
|
|
20286
|
+
reloadNginx(server) {
|
|
20287
|
+
return new Promise((resolve, reject) => {
|
|
20288
|
+
const target = `pmate@${server}`;
|
|
20289
|
+
const child = (0, import_node_child_process11.spawn)("ssh", [target, "sudo", "nginx", "-s", "reload"], {
|
|
20290
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
20291
|
+
});
|
|
20292
|
+
child.on("error", (error) => {
|
|
20293
|
+
reject(error);
|
|
20294
|
+
});
|
|
20295
|
+
child.on("close", (code) => {
|
|
20296
|
+
if (code === 0) {
|
|
20297
|
+
resolve();
|
|
20298
|
+
return;
|
|
20299
|
+
}
|
|
20300
|
+
reject(new Error(`nginx reload on ${server} exited with code ${code}`));
|
|
20301
|
+
});
|
|
20302
|
+
});
|
|
20303
|
+
}
|
|
20055
20304
|
renderJson(value, asJson) {
|
|
20056
20305
|
if (!asJson) {
|
|
20057
20306
|
return;
|
|
@@ -20061,9 +20310,9 @@ var ServiceCommandHandler = class {
|
|
|
20061
20310
|
};
|
|
20062
20311
|
|
|
20063
20312
|
// src/commands/VercelCommandHandler.ts
|
|
20064
|
-
var
|
|
20313
|
+
var import_node_child_process12 = require("child_process");
|
|
20065
20314
|
var import_node_util2 = require("util");
|
|
20066
|
-
var execFileAsync2 = (0, import_node_util2.promisify)(
|
|
20315
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process12.execFile);
|
|
20067
20316
|
function parseDeploymentTimestamp(deployment) {
|
|
20068
20317
|
const candidates = [deployment.createdAt, deployment.readyAt, deployment.ready];
|
|
20069
20318
|
for (const value of candidates) {
|
|
@@ -22594,7 +22843,7 @@ function parseServiceEnvArgs(appId, tokens) {
|
|
|
22594
22843
|
const rest = [...tokens];
|
|
22595
22844
|
const action = rest.shift();
|
|
22596
22845
|
if (!action) {
|
|
22597
|
-
throw new Error("Usage: service <ns:app> env list|get|set|unset|resolve|pull|copy-from");
|
|
22846
|
+
throw new Error("Usage: service <ns:app> env list|get|set|unset|resolve|pull|copy-from|update-scope");
|
|
22598
22847
|
}
|
|
22599
22848
|
let baseUrl;
|
|
22600
22849
|
let json = false;
|
|
@@ -22824,6 +23073,53 @@ function parseServiceEnvArgs(appId, tokens) {
|
|
|
22824
23073
|
}
|
|
22825
23074
|
return { type: "service" /* Service */, action: "env-copy-from", targetAppId: appId, sourceAppId, scope, includeSecret, overwrite, onlyKeys, baseUrl, json };
|
|
22826
23075
|
}
|
|
23076
|
+
if (action === "update-scope") {
|
|
23077
|
+
let fromScope;
|
|
23078
|
+
let toScope;
|
|
23079
|
+
let overwrite = false;
|
|
23080
|
+
let onlyKeys;
|
|
23081
|
+
while (rest.length > 0) {
|
|
23082
|
+
const token = rest.shift();
|
|
23083
|
+
switch (token) {
|
|
23084
|
+
case "--from":
|
|
23085
|
+
case "--from-scope":
|
|
23086
|
+
fromScope = requireValue6(rest, token);
|
|
23087
|
+
break;
|
|
23088
|
+
case "--to":
|
|
23089
|
+
case "--to-scope":
|
|
23090
|
+
toScope = requireValue6(rest, token);
|
|
23091
|
+
break;
|
|
23092
|
+
case "--overwrite":
|
|
23093
|
+
overwrite = true;
|
|
23094
|
+
break;
|
|
23095
|
+
case "--only":
|
|
23096
|
+
onlyKeys = requireValue6(rest, token).split(",").map((item) => item.trim()).filter(Boolean);
|
|
23097
|
+
break;
|
|
23098
|
+
case "--base-url":
|
|
23099
|
+
baseUrl = requireValue6(rest, token);
|
|
23100
|
+
break;
|
|
23101
|
+
case "--json":
|
|
23102
|
+
json = true;
|
|
23103
|
+
break;
|
|
23104
|
+
default:
|
|
23105
|
+
throw new Error(`Unknown service env update-scope option: ${token}`);
|
|
23106
|
+
}
|
|
23107
|
+
}
|
|
23108
|
+
if (!fromScope || !toScope) {
|
|
23109
|
+
throw new Error("Usage: service <ns:app> env update-scope --from <scope> --to <scope> [--overwrite] [--only <key,key>] [--base-url <url>] [--json]");
|
|
23110
|
+
}
|
|
23111
|
+
return {
|
|
23112
|
+
type: "service" /* Service */,
|
|
23113
|
+
action: "env-update-scope",
|
|
23114
|
+
appId,
|
|
23115
|
+
fromScope,
|
|
23116
|
+
toScope,
|
|
23117
|
+
overwrite,
|
|
23118
|
+
onlyKeys,
|
|
23119
|
+
baseUrl,
|
|
23120
|
+
json
|
|
23121
|
+
};
|
|
23122
|
+
}
|
|
22827
23123
|
throw new Error(`Unknown service env action: ${action}`);
|
|
22828
23124
|
}
|
|
22829
23125
|
|