@pmate/cli 0.29.10 → 0.30.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 +18 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +85 -18
- package/package.json +10 -17
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A TypeScript-powered command-line tool scaffold for future pMate automation.
|
|
4
4
|
|
|
5
|
+
## Deploy from an isolated checkout
|
|
6
|
+
|
|
7
|
+
Local-build services can be released without modifying the conventional `~/dev/pmate` checkout. Use `pmate service deploy pmate:<service> --env prod --cwd /absolute/path/to/repo` for Vite, static or FC services. The registered service directory is resolved relative to that repository root; `--cwd` does not change the service's registered repository or environment.
|
|
8
|
+
|
|
9
|
+
Remote Node/Python/Rust deployments still use the registered repository and reject `--cwd` instead of silently ignoring it. Merge and push the intended release to `main` before deployment. The normal service deploy-history and log flow is unchanged.
|
|
10
|
+
|
|
5
11
|
## Development
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -228,3 +234,15 @@ Notes:
|
|
|
228
234
|
- `pmate vercel` is a thin wrapper around the installed `vercel` CLI.
|
|
229
235
|
- v1 only supports `vercel.scope` in PMate config.
|
|
230
236
|
- `pmate vercel deploys` requires `--project <name>`.
|
|
237
|
+
# App-scoped access logs
|
|
238
|
+
|
|
239
|
+
Developers access logs use the same authenticated API as `dev.pmate.chat`, so CLI users cannot bypass App-Service associations or `pmate:dev` authorization by reading the Indexer directly.
|
|
240
|
+
|
|
241
|
+
```sh
|
|
242
|
+
pmate service log pmate:developers-api --app pmate:developers --page 0 --json
|
|
243
|
+
pmate service log pmate:developers-api --app pmate:developers --search http.access --since-ms 1790000000000 --json
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Pages are zero-based. `--search` searches the last 24 hours; the ordinary list follows the Indexer's retention window (currently seven days). `--since-ms` is an epoch-millisecond lower bound. This mode does not accept `--env`, `--follow`, `--history`, or `--base-url`: environment filtering and follow are not implemented by the shared API and are never silently ignored. Without `--app`, the existing operational service-log command is unchanged.
|
|
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.
|
package/dist/index.d.ts
CHANGED
|
@@ -942,6 +942,7 @@ type ServiceCommandArgs = {
|
|
|
942
942
|
namespace: string;
|
|
943
943
|
name: string;
|
|
944
944
|
env?: string;
|
|
945
|
+
cwd?: string;
|
|
945
946
|
baseUrl?: string;
|
|
946
947
|
json: boolean;
|
|
947
948
|
} | {
|
|
@@ -956,6 +957,8 @@ type ServiceCommandArgs = {
|
|
|
956
957
|
} | {
|
|
957
958
|
type: Command.Service;
|
|
958
959
|
action: "log";
|
|
960
|
+
app?: string;
|
|
961
|
+
sinceMs?: number;
|
|
959
962
|
namespace: string;
|
|
960
963
|
name: string;
|
|
961
964
|
baseUrl?: string;
|
package/dist/index.js
CHANGED
|
@@ -90,8 +90,8 @@ var init_namespacedRef = __esm({
|
|
|
90
90
|
function buildSessionStorePath() {
|
|
91
91
|
return import_node_path4.default.join(import_node_os3.default.homedir(), ".pmate", "session.json");
|
|
92
92
|
}
|
|
93
|
-
function assertValidAppRef(
|
|
94
|
-
return parseNamespacedRef(
|
|
93
|
+
function assertValidAppRef(appRef2) {
|
|
94
|
+
return parseNamespacedRef(appRef2, { entityLabel: "app ref" }).fullId;
|
|
95
95
|
}
|
|
96
96
|
function saveLoginSession(session, requestedApp = DEFAULT_APP_ID) {
|
|
97
97
|
const store = loadExistingSessionStoreOrEmpty();
|
|
@@ -222,9 +222,9 @@ function isSessionStore(value) {
|
|
|
222
222
|
if (!(normalizedActiveApp in candidate.sessions)) {
|
|
223
223
|
return false;
|
|
224
224
|
}
|
|
225
|
-
for (const [
|
|
225
|
+
for (const [appRef2, session] of Object.entries(candidate.sessions)) {
|
|
226
226
|
try {
|
|
227
|
-
if (assertValidAppRef(
|
|
227
|
+
if (assertValidAppRef(appRef2) !== appRef2) {
|
|
228
228
|
return false;
|
|
229
229
|
}
|
|
230
230
|
} catch {
|
|
@@ -17751,11 +17751,11 @@ function isSupportedImageDataUrl(value) {
|
|
|
17751
17751
|
const mimeType = match[1]?.toLowerCase();
|
|
17752
17752
|
return mimeType === "image/png" || mimeType === "image/webp" || mimeType === "image/svg+xml" || mimeType === "image/x-icon";
|
|
17753
17753
|
}
|
|
17754
|
-
async function withDefaultSvgIconVariant(icon, icons, cwd,
|
|
17754
|
+
async function withDefaultSvgIconVariant(icon, icons, cwd, appRef2) {
|
|
17755
17755
|
const variant = await renderAppIconVariant({
|
|
17756
17756
|
icon,
|
|
17757
17757
|
name: DEFAULT_APP_ICON_VARIANT_NAME,
|
|
17758
|
-
appRef,
|
|
17758
|
+
appRef: appRef2,
|
|
17759
17759
|
width: 512,
|
|
17760
17760
|
height: 512,
|
|
17761
17761
|
format: "webp",
|
|
@@ -17796,7 +17796,7 @@ function buildAppIconVariantObjectKey(input) {
|
|
|
17796
17796
|
`${resolveAppIconAssetStem(input.icon, input.appRef)}-${input.name}.${extensionForIconFormat(input.format)}`
|
|
17797
17797
|
);
|
|
17798
17798
|
}
|
|
17799
|
-
function resolveAppIconAssetStem(icon,
|
|
17799
|
+
function resolveAppIconAssetStem(icon, appRef2) {
|
|
17800
17800
|
if (icon) {
|
|
17801
17801
|
try {
|
|
17802
17802
|
const url = new URL(icon);
|
|
@@ -17809,8 +17809,8 @@ function resolveAppIconAssetStem(icon, appRef) {
|
|
|
17809
17809
|
} catch {
|
|
17810
17810
|
}
|
|
17811
17811
|
}
|
|
17812
|
-
if (
|
|
17813
|
-
const { objectId } = parseNamespacedRef(
|
|
17812
|
+
if (appRef2) {
|
|
17813
|
+
const { objectId } = parseNamespacedRef(appRef2, {
|
|
17814
17814
|
entityLabel: "app ref"
|
|
17815
17815
|
});
|
|
17816
17816
|
const normalized = sanitizeAppIconAssetStem(objectId);
|
|
@@ -18052,9 +18052,9 @@ var AppCommandHandler = class {
|
|
|
18052
18052
|
throw new Error(`Invalid JSON in ${absolutePath}: ${message}`);
|
|
18053
18053
|
}
|
|
18054
18054
|
}
|
|
18055
|
-
async preparePayload(filePath,
|
|
18055
|
+
async preparePayload(filePath, appRef2) {
|
|
18056
18056
|
const absolutePath = this.resolveFilePath(filePath);
|
|
18057
|
-
return prepareAppPayload(this.readJsonFile(filePath), import_node_path23.default.dirname(absolutePath), { appRef });
|
|
18057
|
+
return prepareAppPayload(this.readJsonFile(filePath), import_node_path23.default.dirname(absolutePath), { appRef: appRef2 });
|
|
18058
18058
|
}
|
|
18059
18059
|
resolveFilePath(filePath) {
|
|
18060
18060
|
return import_node_path23.default.resolve(process.cwd(), filePath);
|
|
@@ -18866,6 +18866,9 @@ var ServiceRuntimeRunner = class {
|
|
|
18866
18866
|
this.envProvider = envProvider;
|
|
18867
18867
|
}
|
|
18868
18868
|
async deploy(service, options = {}) {
|
|
18869
|
+
if (options.cwd && !["vite", "static", "fc"].includes(this.getType(service))) {
|
|
18870
|
+
throw new Error("--cwd is only supported for local-build vite, static and fc services; remote deployments use the registered repository");
|
|
18871
|
+
}
|
|
18869
18872
|
switch (this.getType(service)) {
|
|
18870
18873
|
case "node-api":
|
|
18871
18874
|
await this.deployNode(service, options);
|
|
@@ -19165,8 +19168,8 @@ var ServiceRuntimeRunner = class {
|
|
|
19165
19168
|
async deployFc(service, options = {}) {
|
|
19166
19169
|
const fc = this.getFc(service);
|
|
19167
19170
|
this.validateFcConfig(service, fc);
|
|
19168
|
-
const repoRoot = this.resolveRepoRoot(service);
|
|
19169
|
-
const appDir = this.resolveLocalDir(service);
|
|
19171
|
+
const repoRoot = this.resolveRepoRoot(service, options.cwd);
|
|
19172
|
+
const appDir = this.resolveLocalDir(service, options.cwd);
|
|
19170
19173
|
const packageJsonPath = import_node_path25.default.join(appDir, "package.json");
|
|
19171
19174
|
if (!(0, import_node_fs20.existsSync)(packageJsonPath)) {
|
|
19172
19175
|
throw new Error(`No package.json found at ${packageJsonPath}`);
|
|
@@ -20315,6 +20318,51 @@ function parseServiceRef(serviceId) {
|
|
|
20315
20318
|
|
|
20316
20319
|
// src/commands/ServiceCommandHandler.ts
|
|
20317
20320
|
init_namespacedRef();
|
|
20321
|
+
|
|
20322
|
+
// src/services/DeveloperLogsClient.ts
|
|
20323
|
+
init_sessionStore();
|
|
20324
|
+
init_namespacedRef();
|
|
20325
|
+
var appRef = "pmate:developers";
|
|
20326
|
+
async function queryDeveloperLogs(input, fetcher = fetch) {
|
|
20327
|
+
parseNamespacedRef(input.app, { entityLabel: "app ref" });
|
|
20328
|
+
parseNamespacedRef(input.service, { entityLabel: "service ref" });
|
|
20329
|
+
if (!Number.isSafeInteger(input.page) || input.page < 0) throw new Error("Invalid log page");
|
|
20330
|
+
if (input.sinceMs !== void 0 && (!Number.isSafeInteger(input.sinceMs) || input.sinceMs < 0)) throw new Error("Invalid log time");
|
|
20331
|
+
if (input.keyword && input.keyword.length > 256) throw new Error("Log search must be at most 256 characters");
|
|
20332
|
+
const session = readRequiredSession({ preferredApp: appRef, fallbackToActiveApp: true });
|
|
20333
|
+
if (Date.parse(session.expiresAt) <= Date.now() || !Number.isFinite(Date.parse(session.expiresAt)))
|
|
20334
|
+
throw new Error("Session expired. Run pmate login --app pmate:developers");
|
|
20335
|
+
async function request2(url, init) {
|
|
20336
|
+
let response;
|
|
20337
|
+
try {
|
|
20338
|
+
response = await fetcher(url, { ...init, redirect: "error", signal: AbortSignal.timeout(3e4) });
|
|
20339
|
+
} catch {
|
|
20340
|
+
throw new Error("Developers log request failed; no anonymous fallback was attempted");
|
|
20341
|
+
}
|
|
20342
|
+
if (!response.ok) throw new Error(`Developers log request failed (HTTP ${response.status}); check login, pmate:dev role and App-Service access`);
|
|
20343
|
+
let body;
|
|
20344
|
+
try {
|
|
20345
|
+
body = await response.json();
|
|
20346
|
+
} catch {
|
|
20347
|
+
throw new Error("Invalid Developers API response");
|
|
20348
|
+
}
|
|
20349
|
+
if (!body?.success) throw new Error("Developers API rejected the request");
|
|
20350
|
+
return body.data;
|
|
20351
|
+
}
|
|
20352
|
+
const exchanged = await request2("https://auth-api-v2.pmate.chat/session/exchange", {
|
|
20353
|
+
method: "POST",
|
|
20354
|
+
headers: { Authorization: `Bearer ${session.token}`, "Content-Type": "application/json" },
|
|
20355
|
+
body: JSON.stringify({ app: appRef })
|
|
20356
|
+
});
|
|
20357
|
+
if (exchanged?.session?.identity?.app !== appRef || exchanged.session.identity.accountId !== session.accountId || typeof exchanged.token !== "string" || !exchanged.token)
|
|
20358
|
+
throw new Error("Developers session identity mismatch");
|
|
20359
|
+
const params = new URLSearchParams({ app: input.app, service: input.service, page: String(input.page) });
|
|
20360
|
+
if (input.keyword) params.set("keyword", input.keyword);
|
|
20361
|
+
if (input.sinceMs !== void 0) params.set("sinceMs", String(input.sinceMs));
|
|
20362
|
+
return request2(`https://dev-api.pmate.chat/api/logs?${params}`, { headers: { Authorization: `Bearer ${exchanged.token}` } });
|
|
20363
|
+
}
|
|
20364
|
+
|
|
20365
|
+
// src/commands/ServiceCommandHandler.ts
|
|
20318
20366
|
var ServiceCommandHandler = class {
|
|
20319
20367
|
async handle(args) {
|
|
20320
20368
|
const baseUrl = this.resolveBaseUrl(args.baseUrl);
|
|
@@ -20480,7 +20528,7 @@ var ServiceCommandHandler = class {
|
|
|
20480
20528
|
const record = await registry.getService(args.namespace, args.name);
|
|
20481
20529
|
const service = await this.materializeServiceEnv(registry, record, args.env);
|
|
20482
20530
|
try {
|
|
20483
|
-
await runtime.deploy(service, { envName: args.env });
|
|
20531
|
+
await runtime.deploy(service, { envName: args.env, ...args.cwd ? { cwd: args.cwd } : {} });
|
|
20484
20532
|
await this.appendDeployHistory(registry, record.id, args.env, "success");
|
|
20485
20533
|
} catch (error) {
|
|
20486
20534
|
await this.appendDeployHistory(
|
|
@@ -20511,6 +20559,11 @@ var ServiceCommandHandler = class {
|
|
|
20511
20559
|
return;
|
|
20512
20560
|
}
|
|
20513
20561
|
case "log": {
|
|
20562
|
+
if (args.app) {
|
|
20563
|
+
const result = await queryDeveloperLogs({ app: args.app, service: `${args.namespace}:${args.name}`, page: args.page, keyword: args.search, sinceMs: args.sinceMs });
|
|
20564
|
+
console.log(JSON.stringify(result, null, args.json ? void 0 : 2));
|
|
20565
|
+
return;
|
|
20566
|
+
}
|
|
20514
20567
|
const record = await registry.getService(args.namespace, args.name);
|
|
20515
20568
|
const service = args.env ? await this.materializeServiceEnv(registry, record, args.env) : record;
|
|
20516
20569
|
await runtime.log(service, {
|
|
@@ -23767,6 +23820,8 @@ function parseServiceArgs(tokens) {
|
|
|
23767
23820
|
}
|
|
23768
23821
|
let cwd;
|
|
23769
23822
|
let page = 0;
|
|
23823
|
+
let app;
|
|
23824
|
+
let sinceMs;
|
|
23770
23825
|
let history = false;
|
|
23771
23826
|
let search;
|
|
23772
23827
|
let follow = false;
|
|
@@ -23774,11 +23829,20 @@ function parseServiceArgs(tokens) {
|
|
|
23774
23829
|
while (rest.length > 0) {
|
|
23775
23830
|
const token = rest.shift();
|
|
23776
23831
|
switch (token) {
|
|
23832
|
+
case "--app":
|
|
23833
|
+
if (action !== "log") throw new Error(`Unknown service ${action} option: ${token}`);
|
|
23834
|
+
app = parseNamespacedRef(requireValue4(rest, token), { entityLabel: "app ref" }).fullId;
|
|
23835
|
+
break;
|
|
23836
|
+
case "--since-ms":
|
|
23837
|
+
if (action !== "log") throw new Error(`Unknown service ${action} option: ${token}`);
|
|
23838
|
+
sinceMs = Number(requireValue4(rest, token));
|
|
23839
|
+
if (!Number.isSafeInteger(sinceMs) || sinceMs < 0) throw new Error("Invalid --since-ms");
|
|
23840
|
+
break;
|
|
23777
23841
|
case "--base-url":
|
|
23778
23842
|
baseUrl = requireValue4(rest, token);
|
|
23779
23843
|
break;
|
|
23780
23844
|
case "--cwd":
|
|
23781
|
-
if (action !== "init") {
|
|
23845
|
+
if (action !== "init" && action !== "deploy") {
|
|
23782
23846
|
throw new Error(`Unknown service ${action} option: ${token}`);
|
|
23783
23847
|
}
|
|
23784
23848
|
cwd = requireValue4(rest, token);
|
|
@@ -23790,7 +23854,8 @@ function parseServiceArgs(tokens) {
|
|
|
23790
23854
|
env = requireValue4(rest, token);
|
|
23791
23855
|
break;
|
|
23792
23856
|
case "--page":
|
|
23793
|
-
page = Number
|
|
23857
|
+
page = Number(requireValue4(rest, token));
|
|
23858
|
+
if (!Number.isSafeInteger(page) || page < 0) throw new Error("Invalid --page");
|
|
23794
23859
|
break;
|
|
23795
23860
|
case "--history":
|
|
23796
23861
|
history = true;
|
|
@@ -23813,7 +23878,9 @@ function parseServiceArgs(tokens) {
|
|
|
23813
23878
|
const namespace = parsedRef.namespace;
|
|
23814
23879
|
const name = parsedRef.objectId;
|
|
23815
23880
|
if (action === "log") {
|
|
23816
|
-
|
|
23881
|
+
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");
|
|
23882
|
+
if (sinceMs !== void 0 && !app) throw new Error("--since-ms requires --app");
|
|
23883
|
+
return { type: "service" /* Service */, action, namespace, name, baseUrl, json, page, history, search, follow, env, ...app ? { app } : {}, ...sinceMs !== void 0 ? { sinceMs } : {} };
|
|
23817
23884
|
}
|
|
23818
23885
|
if (action === "deploy-logs" || action === "deploy-history") {
|
|
23819
23886
|
return { type: "service" /* Service */, action, namespace, name, env, page, baseUrl, json };
|
|
@@ -23822,7 +23889,7 @@ function parseServiceArgs(tokens) {
|
|
|
23822
23889
|
if (action === "deploy" && !env) {
|
|
23823
23890
|
throw new Error("Missing required option --env for service deploy");
|
|
23824
23891
|
}
|
|
23825
|
-
return { type: "service" /* Service */, action, namespace, name, env, baseUrl, json };
|
|
23892
|
+
return { type: "service" /* Service */, action, namespace, name, env, ...cwd ? { cwd } : {}, baseUrl, json };
|
|
23826
23893
|
}
|
|
23827
23894
|
if (action === "init") {
|
|
23828
23895
|
if (!env) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pmate/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "CLI utilities for the pMate project",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -11,16 +11,6 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist"
|
|
13
13
|
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "npm run clean && tsup --config tsup.config.ts && node scripts/copy-templates.js",
|
|
16
|
-
"clean": "rimraf dist",
|
|
17
|
-
"dev": "ts-node src/index.ts",
|
|
18
|
-
"release": "pnpm publish",
|
|
19
|
-
"start": "node dist/index.js",
|
|
20
|
-
"test": "vitest run src/tests",
|
|
21
|
-
"typecheck": "tsc --noEmit",
|
|
22
|
-
"prepare": "npm run build"
|
|
23
|
-
},
|
|
24
14
|
"keywords": [
|
|
25
15
|
"cli",
|
|
26
16
|
"typescript",
|
|
@@ -67,10 +57,13 @@
|
|
|
67
57
|
"typescript": "^5.9.3",
|
|
68
58
|
"vitest": "^4.0.17"
|
|
69
59
|
},
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "npm run clean && tsup --config tsup.config.ts && node scripts/copy-templates.js",
|
|
62
|
+
"clean": "rimraf dist",
|
|
63
|
+
"dev": "ts-node src/index.ts",
|
|
64
|
+
"release": "pnpm publish",
|
|
65
|
+
"start": "node dist/index.js",
|
|
66
|
+
"test": "vitest run src/tests",
|
|
67
|
+
"typecheck": "tsc --noEmit"
|
|
75
68
|
}
|
|
76
|
-
}
|
|
69
|
+
}
|