@pmate/cli 0.31.0 → 0.33.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 +7 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.js +100 -85
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -234,19 +234,19 @@ Notes:
|
|
|
234
234
|
- `pmate vercel` is a thin wrapper around the installed `vercel` CLI.
|
|
235
235
|
- v1 only supports `vercel.scope` in PMate config.
|
|
236
236
|
- `pmate vercel deploys` requires `--project <name>`.
|
|
237
|
-
#
|
|
237
|
+
# Service logs
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
Service logs use the same authenticated API as `dev.pmate.chat`. Each registered Service has `app` (application/deployment) and `runtime` (PM2/Nginx) Table topics on the dedicated `log` chain.
|
|
240
240
|
|
|
241
241
|
```sh
|
|
242
|
-
pmate service log pmate:
|
|
243
|
-
pmate service log pmate:
|
|
242
|
+
pmate service log pmate:issues-api --topic application --page 0 --json
|
|
243
|
+
pmate service log pmate:issues-api --topic nginx --field status=500 --env prod
|
|
244
|
+
pmate service log pmate:issues-api --field requestId=<id> --follow
|
|
244
245
|
```
|
|
245
246
|
|
|
246
|
-
Pages are zero-based. `--search`
|
|
247
|
-
|
|
248
|
-
A current CLI session is exchanged in memory for `pmate:developers`; the active App and session file are not changed. If login is required, run `pmate login --app pmate:developers`. Access denial never falls back to anonymous log reads. Neither source nor exchanged tokens are printed.
|
|
247
|
+
Pages are zero-based. Lists and `--search` cover the chain's configured retention window (30 days in production). `--since-ms` adds an epoch-millisecond lower bound. `--field name=value` (repeatable, scalar JSON values) and `--env` filter on the server. Topics are independent sources such as `application`, `nginx`, `pm2`, `deployment`, `pv`, and `vitals`; custom fields live in `data`. Old `--type`, `--level`, `--source`, and `--request-id` flags are removed. The legacy `--app` argument is optional and does not select a different source.
|
|
249
248
|
|
|
249
|
+
A current CLI session is exchanged in memory for `pmate:developers`; the active App and session file are unchanged. If needed, run `pmate login --app pmate:developers`. Permission denial never falls back to anonymous Indexer reads. Credentials are never printed.
|
|
250
250
|
|
|
251
251
|
### Package a desktop app
|
|
252
252
|
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -18740,6 +18740,51 @@ function capitalize(value) {
|
|
|
18740
18740
|
return value.slice(0, 1).toUpperCase() + value.slice(1);
|
|
18741
18741
|
}
|
|
18742
18742
|
|
|
18743
|
+
// src/services/DeveloperLogsClient.ts
|
|
18744
|
+
init_sessionStore();
|
|
18745
|
+
init_namespacedRef();
|
|
18746
|
+
var appRef = "pmate:developers";
|
|
18747
|
+
async function queryDeveloperLogs(input, fetcher = fetch) {
|
|
18748
|
+
if (input.app) parseNamespacedRef(input.app, { entityLabel: "app ref" });
|
|
18749
|
+
parseNamespacedRef(input.service, { entityLabel: "service ref" });
|
|
18750
|
+
if (!Number.isSafeInteger(input.page) || input.page < 0) throw new Error("Invalid log page");
|
|
18751
|
+
if (input.sinceMs !== void 0 && (!Number.isSafeInteger(input.sinceMs) || input.sinceMs < 0)) throw new Error("Invalid log time");
|
|
18752
|
+
if (input.keyword && input.keyword.length > 256) throw new Error("Log search must be at most 256 characters");
|
|
18753
|
+
const session = readRequiredSession({ preferredApp: appRef, fallbackToActiveApp: true });
|
|
18754
|
+
if (Date.parse(session.expiresAt) <= Date.now() || !Number.isFinite(Date.parse(session.expiresAt)))
|
|
18755
|
+
throw new Error("Session expired. Run pmate login --app pmate:developers");
|
|
18756
|
+
async function request2(url, init) {
|
|
18757
|
+
let response;
|
|
18758
|
+
try {
|
|
18759
|
+
response = await fetcher(url, { ...init, redirect: "error", signal: AbortSignal.timeout(3e4) });
|
|
18760
|
+
} catch {
|
|
18761
|
+
throw new Error("Developers log request failed; no anonymous fallback was attempted");
|
|
18762
|
+
}
|
|
18763
|
+
if (!response.ok) throw new Error(`Developers log request failed (HTTP ${response.status}); check login, pmate:dev role`);
|
|
18764
|
+
let body;
|
|
18765
|
+
try {
|
|
18766
|
+
body = await response.json();
|
|
18767
|
+
} catch {
|
|
18768
|
+
throw new Error("Invalid Developers API response");
|
|
18769
|
+
}
|
|
18770
|
+
if (!body?.success) throw new Error("Developers API rejected the request");
|
|
18771
|
+
return body.data;
|
|
18772
|
+
}
|
|
18773
|
+
const exchanged = await request2("https://auth-api-v2.pmate.chat/session/exchange", {
|
|
18774
|
+
method: "POST",
|
|
18775
|
+
headers: { Authorization: `Bearer ${session.token}`, "Content-Type": "application/json" },
|
|
18776
|
+
body: JSON.stringify({ app: appRef })
|
|
18777
|
+
});
|
|
18778
|
+
if (exchanged?.session?.identity?.app !== appRef || exchanged.session.identity.accountId !== session.accountId || typeof exchanged.token !== "string" || !exchanged.token)
|
|
18779
|
+
throw new Error("Developers session identity mismatch");
|
|
18780
|
+
const params = new URLSearchParams({ service: input.service, page: String(input.page), topic: input.topic ?? "application" });
|
|
18781
|
+
if (input.environment) params.set("environment", input.environment);
|
|
18782
|
+
if (input.fields) params.set("fields", JSON.stringify(input.fields));
|
|
18783
|
+
if (input.keyword) params.set("keyword", input.keyword);
|
|
18784
|
+
if (input.sinceMs !== void 0) params.set("sinceMs", String(input.sinceMs));
|
|
18785
|
+
return request2(`https://dev-api.pmate.chat/api/logs?${params}`, { headers: { Authorization: `Bearer ${exchanged.token}` } });
|
|
18786
|
+
}
|
|
18787
|
+
|
|
18743
18788
|
// src/services/ServiceRuntimeRunner.ts
|
|
18744
18789
|
var import_node_fs22 = require("fs");
|
|
18745
18790
|
var import_node_os12 = __toESM(require("os"));
|
|
@@ -19111,15 +19156,21 @@ var ServiceRuntimeRunner = class {
|
|
|
19111
19156
|
const namespace = service.namespace;
|
|
19112
19157
|
const appName = service.name;
|
|
19113
19158
|
const page = await this.fetchLogPage(namespace, appName, {
|
|
19159
|
+
...options,
|
|
19114
19160
|
page: options.page ?? 0,
|
|
19115
19161
|
history: options.history ?? false,
|
|
19116
19162
|
search: options.search
|
|
19117
19163
|
});
|
|
19118
|
-
|
|
19164
|
+
if (options.json) {
|
|
19165
|
+
console.log(JSON.stringify(page));
|
|
19166
|
+
return;
|
|
19167
|
+
}
|
|
19168
|
+
ui.title(`Logs for ${namespace}/${appName} \xB7 ${options.topic ?? "app"}`);
|
|
19119
19169
|
ui.info("Source", { mode: options.history ? "history" : "online" });
|
|
19120
19170
|
this.renderPage(page);
|
|
19121
19171
|
if (options.follow) {
|
|
19122
19172
|
await this.followLogs(namespace, appName, {
|
|
19173
|
+
...options,
|
|
19123
19174
|
page: options.page ?? 0,
|
|
19124
19175
|
history: options.history ?? false,
|
|
19125
19176
|
search: options.search,
|
|
@@ -19136,7 +19187,7 @@ var ServiceRuntimeRunner = class {
|
|
|
19136
19187
|
interactive?.rl.on("close", () => {
|
|
19137
19188
|
running = false;
|
|
19138
19189
|
});
|
|
19139
|
-
const seen = new Set(options.seedItems.map((item) => item.hash));
|
|
19190
|
+
const seen = new Set(options.seedItems.map((item) => item.id ?? item.hash));
|
|
19140
19191
|
ui.info(
|
|
19141
19192
|
"Follow",
|
|
19142
19193
|
interactive ? "enabled (poll every 2s, Ctrl+C to stop, /help for filters)" : "enabled (poll every 2s, Ctrl+C to stop)"
|
|
@@ -19147,6 +19198,7 @@ var ServiceRuntimeRunner = class {
|
|
|
19147
19198
|
let page;
|
|
19148
19199
|
try {
|
|
19149
19200
|
page = await this.fetchLogPage(namespace, appName, {
|
|
19201
|
+
...options,
|
|
19150
19202
|
page: options.page,
|
|
19151
19203
|
history: options.history,
|
|
19152
19204
|
search: options.search
|
|
@@ -19156,12 +19208,12 @@ var ServiceRuntimeRunner = class {
|
|
|
19156
19208
|
ui.warn("Follow fetch failed", { message });
|
|
19157
19209
|
continue;
|
|
19158
19210
|
}
|
|
19159
|
-
const newItems = page.data.filter((item) => !seen.has(item.hash));
|
|
19211
|
+
const newItems = page.data.filter((item) => !seen.has(item.id ?? item.hash));
|
|
19160
19212
|
if (newItems.length === 0) {
|
|
19161
19213
|
continue;
|
|
19162
19214
|
}
|
|
19163
19215
|
for (const item of newItems) {
|
|
19164
|
-
seen.add(item.hash);
|
|
19216
|
+
seen.add(item.id ?? item.hash);
|
|
19165
19217
|
recent.unshift(item);
|
|
19166
19218
|
if (recent.length > 200) {
|
|
19167
19219
|
recent.pop();
|
|
@@ -19747,35 +19799,16 @@ var ServiceRuntimeRunner = class {
|
|
|
19747
19799
|
await this.runRemoteCommand(server, command);
|
|
19748
19800
|
}
|
|
19749
19801
|
async fetchLogPage(namespace, appName, options) {
|
|
19750
|
-
|
|
19751
|
-
|
|
19752
|
-
|
|
19753
|
-
|
|
19754
|
-
|
|
19755
|
-
|
|
19756
|
-
|
|
19757
|
-
|
|
19758
|
-
|
|
19759
|
-
if (hasSearch && !options.history) {
|
|
19760
|
-
url.searchParams.set("keyword", options.search.trim());
|
|
19761
|
-
}
|
|
19762
|
-
const response = await fetch(url.toString(), {
|
|
19763
|
-
method: "GET",
|
|
19764
|
-
headers: { Accept: "application/json" }
|
|
19802
|
+
resolveLogChainId();
|
|
19803
|
+
return await queryDeveloperLogs({
|
|
19804
|
+
service: `${namespace}:${appName}`,
|
|
19805
|
+
page: options.page,
|
|
19806
|
+
keyword: options.search,
|
|
19807
|
+
topic: options.topic,
|
|
19808
|
+
fields: options.fields,
|
|
19809
|
+
environment: options.envName,
|
|
19810
|
+
sinceMs: options.sinceMs
|
|
19765
19811
|
});
|
|
19766
|
-
const payload = await response.json();
|
|
19767
|
-
if (response.status === 404 && /resource not found/i.test(payload.message || "")) {
|
|
19768
|
-
return {
|
|
19769
|
-
page: options.page,
|
|
19770
|
-
totalPage: 0,
|
|
19771
|
-
pageSize: 100,
|
|
19772
|
-
data: []
|
|
19773
|
-
};
|
|
19774
|
-
}
|
|
19775
|
-
if (!response.ok || !payload.success || !payload.data) {
|
|
19776
|
-
throw new Error(payload.message || `Log request failed: ${response.status}`);
|
|
19777
|
-
}
|
|
19778
|
-
return payload.data;
|
|
19779
19812
|
}
|
|
19780
19813
|
envNameToStage(envName) {
|
|
19781
19814
|
if (envName === "dev" || envName === "preview" || envName === "prod") {
|
|
@@ -19801,8 +19834,8 @@ var ServiceRuntimeRunner = class {
|
|
|
19801
19834
|
const type = isErrorLike ? import_chalk3.default.bold.red(`[${item.type}]`) : import_chalk3.default.bold.blue(`[${item.type}]`);
|
|
19802
19835
|
const level = resolvedLevel ? `${isErrorLike ? import_chalk3.default.bold.red(resolvedLevel.toUpperCase()) : import_chalk3.default.bold.yellow(resolvedLevel.toUpperCase())} ` : "";
|
|
19803
19836
|
const data = this.formatLogData(item.data);
|
|
19804
|
-
const hash = import_chalk3.default.gray(`#${item.hash}`);
|
|
19805
|
-
return `${ts} ${level}${type} ${data} ${hash}`;
|
|
19837
|
+
const hash = import_chalk3.default.gray(`#${item.id ?? item.hash}`);
|
|
19838
|
+
return `${ts} ${level}${type} ${item.source ?? ""} ${item.message ?? ""} ${data} ${hash}`;
|
|
19806
19839
|
}
|
|
19807
19840
|
formatLogData(value) {
|
|
19808
19841
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -20459,7 +20492,8 @@ var ServiceRuntimeRunner = class {
|
|
|
20459
20492
|
}
|
|
20460
20493
|
};
|
|
20461
20494
|
function resolveLogChainId(env = process.env) {
|
|
20462
|
-
|
|
20495
|
+
if (env.APP_LOG_CHAIN_ID && env.APP_LOG_CHAIN_ID !== "log") throw new Error("Logs require log chain");
|
|
20496
|
+
return "log";
|
|
20463
20497
|
}
|
|
20464
20498
|
function shellQuote2(value) {
|
|
20465
20499
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -20474,51 +20508,6 @@ function parseServiceRef(serviceId) {
|
|
|
20474
20508
|
|
|
20475
20509
|
// src/commands/ServiceCommandHandler.ts
|
|
20476
20510
|
init_namespacedRef();
|
|
20477
|
-
|
|
20478
|
-
// src/services/DeveloperLogsClient.ts
|
|
20479
|
-
init_sessionStore();
|
|
20480
|
-
init_namespacedRef();
|
|
20481
|
-
var appRef = "pmate:developers";
|
|
20482
|
-
async function queryDeveloperLogs(input, fetcher = fetch) {
|
|
20483
|
-
parseNamespacedRef(input.app, { entityLabel: "app ref" });
|
|
20484
|
-
parseNamespacedRef(input.service, { entityLabel: "service ref" });
|
|
20485
|
-
if (!Number.isSafeInteger(input.page) || input.page < 0) throw new Error("Invalid log page");
|
|
20486
|
-
if (input.sinceMs !== void 0 && (!Number.isSafeInteger(input.sinceMs) || input.sinceMs < 0)) throw new Error("Invalid log time");
|
|
20487
|
-
if (input.keyword && input.keyword.length > 256) throw new Error("Log search must be at most 256 characters");
|
|
20488
|
-
const session = readRequiredSession({ preferredApp: appRef, fallbackToActiveApp: true });
|
|
20489
|
-
if (Date.parse(session.expiresAt) <= Date.now() || !Number.isFinite(Date.parse(session.expiresAt)))
|
|
20490
|
-
throw new Error("Session expired. Run pmate login --app pmate:developers");
|
|
20491
|
-
async function request2(url, init) {
|
|
20492
|
-
let response;
|
|
20493
|
-
try {
|
|
20494
|
-
response = await fetcher(url, { ...init, redirect: "error", signal: AbortSignal.timeout(3e4) });
|
|
20495
|
-
} catch {
|
|
20496
|
-
throw new Error("Developers log request failed; no anonymous fallback was attempted");
|
|
20497
|
-
}
|
|
20498
|
-
if (!response.ok) throw new Error(`Developers log request failed (HTTP ${response.status}); check login, pmate:dev role and App-Service access`);
|
|
20499
|
-
let body;
|
|
20500
|
-
try {
|
|
20501
|
-
body = await response.json();
|
|
20502
|
-
} catch {
|
|
20503
|
-
throw new Error("Invalid Developers API response");
|
|
20504
|
-
}
|
|
20505
|
-
if (!body?.success) throw new Error("Developers API rejected the request");
|
|
20506
|
-
return body.data;
|
|
20507
|
-
}
|
|
20508
|
-
const exchanged = await request2("https://auth-api-v2.pmate.chat/session/exchange", {
|
|
20509
|
-
method: "POST",
|
|
20510
|
-
headers: { Authorization: `Bearer ${session.token}`, "Content-Type": "application/json" },
|
|
20511
|
-
body: JSON.stringify({ app: appRef })
|
|
20512
|
-
});
|
|
20513
|
-
if (exchanged?.session?.identity?.app !== appRef || exchanged.session.identity.accountId !== session.accountId || typeof exchanged.token !== "string" || !exchanged.token)
|
|
20514
|
-
throw new Error("Developers session identity mismatch");
|
|
20515
|
-
const params = new URLSearchParams({ app: input.app, service: input.service, page: String(input.page) });
|
|
20516
|
-
if (input.keyword) params.set("keyword", input.keyword);
|
|
20517
|
-
if (input.sinceMs !== void 0) params.set("sinceMs", String(input.sinceMs));
|
|
20518
|
-
return request2(`https://dev-api.pmate.chat/api/logs?${params}`, { headers: { Authorization: `Bearer ${exchanged.token}` } });
|
|
20519
|
-
}
|
|
20520
|
-
|
|
20521
|
-
// src/commands/ServiceCommandHandler.ts
|
|
20522
20511
|
var ServiceCommandHandler = class {
|
|
20523
20512
|
async handle(args) {
|
|
20524
20513
|
const baseUrl = this.resolveBaseUrl(args.baseUrl);
|
|
@@ -20716,7 +20705,7 @@ var ServiceCommandHandler = class {
|
|
|
20716
20705
|
}
|
|
20717
20706
|
case "log": {
|
|
20718
20707
|
if (args.app) {
|
|
20719
|
-
const result = await queryDeveloperLogs({ app: args.app, service: `${args.namespace}:${args.name}`, page: args.page, keyword: args.search, sinceMs: args.sinceMs });
|
|
20708
|
+
const result = await queryDeveloperLogs({ app: args.app, service: `${args.namespace}:${args.name}`, page: args.page, keyword: args.search, sinceMs: args.sinceMs, topic: args.topic, fields: args.fields, environment: args.env });
|
|
20720
20709
|
console.log(JSON.stringify(result, null, args.json ? void 0 : 2));
|
|
20721
20710
|
return;
|
|
20722
20711
|
}
|
|
@@ -20727,7 +20716,11 @@ var ServiceCommandHandler = class {
|
|
|
20727
20716
|
history: args.history,
|
|
20728
20717
|
search: args.search,
|
|
20729
20718
|
follow: args.follow,
|
|
20730
|
-
envName: args.env
|
|
20719
|
+
envName: args.env,
|
|
20720
|
+
topic: args.topic,
|
|
20721
|
+
fields: args.fields,
|
|
20722
|
+
sinceMs: args.sinceMs,
|
|
20723
|
+
json: args.json
|
|
20731
20724
|
});
|
|
20732
20725
|
return;
|
|
20733
20726
|
}
|
|
@@ -23998,6 +23991,7 @@ function parseServiceArgs(tokens) {
|
|
|
23998
23991
|
let page = 0;
|
|
23999
23992
|
let app;
|
|
24000
23993
|
let sinceMs;
|
|
23994
|
+
const logFilters = {};
|
|
24001
23995
|
let history = false;
|
|
24002
23996
|
let search;
|
|
24003
23997
|
let follow = false;
|
|
@@ -24005,6 +23999,28 @@ function parseServiceArgs(tokens) {
|
|
|
24005
23999
|
while (rest.length > 0) {
|
|
24006
24000
|
const token = rest.shift();
|
|
24007
24001
|
switch (token) {
|
|
24002
|
+
case "--topic": {
|
|
24003
|
+
if (action !== "log") throw new Error(`Unknown service ${action} option: ${token}`);
|
|
24004
|
+
const value = requireValue4(rest, token);
|
|
24005
|
+
if (!/^[a-z][a-z0-9-]{0,31}$/.test(value)) throw new Error("Invalid --topic");
|
|
24006
|
+
logFilters.topic = value;
|
|
24007
|
+
break;
|
|
24008
|
+
}
|
|
24009
|
+
case "--field": {
|
|
24010
|
+
if (action !== "log") throw new Error(`Unknown service ${action} option: ${token}`);
|
|
24011
|
+
const value = requireValue4(rest, token);
|
|
24012
|
+
const match = /^([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*)=(.*)$/.exec(value);
|
|
24013
|
+
if (!match || match[1].length > 128 || match[2].length > 256) throw new Error("Use --field name=value");
|
|
24014
|
+
let parsed = match[2];
|
|
24015
|
+
try {
|
|
24016
|
+
parsed = JSON.parse(match[2]);
|
|
24017
|
+
} catch {
|
|
24018
|
+
}
|
|
24019
|
+
if (!["string", "number", "boolean"].includes(typeof parsed)) throw new Error("Field value must be text, number or boolean");
|
|
24020
|
+
logFilters.fields = { ...logFilters.fields, [match[1]]: parsed };
|
|
24021
|
+
if (Object.keys(logFilters.fields).length > 20) throw new Error("At most 20 field filters");
|
|
24022
|
+
break;
|
|
24023
|
+
}
|
|
24008
24024
|
case "--app":
|
|
24009
24025
|
if (action !== "log") throw new Error(`Unknown service ${action} option: ${token}`);
|
|
24010
24026
|
app = parseNamespacedRef(requireValue4(rest, token), { entityLabel: "app ref" }).fullId;
|
|
@@ -24055,8 +24071,7 @@ function parseServiceArgs(tokens) {
|
|
|
24055
24071
|
const name = parsedRef.objectId;
|
|
24056
24072
|
if (action === "log") {
|
|
24057
24073
|
if (app && (history || follow || env || baseUrl)) throw new Error("App logs do not support --history, --follow, --env or --base-url; use --page, --search and --since-ms");
|
|
24058
|
-
|
|
24059
|
-
return { type: "service" /* Service */, action, namespace, name, baseUrl, json, page, history, search, follow, env, ...app ? { app } : {}, ...sinceMs !== void 0 ? { sinceMs } : {} };
|
|
24074
|
+
return { type: "service" /* Service */, action, namespace, name, baseUrl, json, page, history, search, follow, env, ...logFilters, ...app ? { app } : {}, ...sinceMs !== void 0 ? { sinceMs } : {} };
|
|
24060
24075
|
}
|
|
24061
24076
|
if (action === "deploy-logs" || action === "deploy-history") {
|
|
24062
24077
|
return { type: "service" /* Service */, action, namespace, name, env, page, baseUrl, json };
|
|
@@ -26303,7 +26318,7 @@ var HELP_TEXT = [
|
|
|
26303
26318
|
` ${import_chalk4.default.gray(" init <ns:objectId> [--cwd <dir>] [--base-url <url>]")}`,
|
|
26304
26319
|
` ${import_chalk4.default.gray(" ssl set <ns:objectId> --cert <key> [--base-url <url>] [--json]")}`,
|
|
26305
26320
|
` ${import_chalk4.default.gray(" ssl refresh <ns:objectId> [--cert <key>] [--dry-run] [--base-url <url>] [--json]")}`,
|
|
26306
|
-
` ${import_chalk4.default.gray(" log <ns:objectId> [--
|
|
26321
|
+
` ${import_chalk4.default.gray(" log <ns:objectId> [--topic <topic>] [--env <env>] [--field <name=value>] [--search <keyword>] [--since-ms <ms>] [--page <n>] [--follow|-f] [--json]")}`,
|
|
26307
26322
|
` ${import_chalk4.default.gray(" default base URL: https://apps-api.pmate.chat")}`,
|
|
26308
26323
|
` ${import_chalk4.default.cyan("host")} Manage host-level environment variables`,
|
|
26309
26324
|
` ${import_chalk4.default.gray(" env list [--host <id>] [--json]")}`,
|