@pmate/cli 0.24.29 → 0.24.31

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 CHANGED
@@ -936,6 +936,7 @@ type ServiceCommandArgs = {
936
936
  history: boolean;
937
937
  search?: string;
938
938
  follow: boolean;
939
+ env?: string;
939
940
  } | {
940
941
  type: Command.Service;
941
942
  action: "deploy-logs" | "deploy-history";
package/dist/index.js CHANGED
@@ -18201,6 +18201,13 @@ var ServiceRegistryClient = class {
18201
18201
  );
18202
18202
  return payload.items;
18203
18203
  }
18204
+ async listServiceEnvs(serviceId) {
18205
+ const payload = await this.request(
18206
+ "GET",
18207
+ `/services/by-id/${encodeURIComponent(serviceId)}/envs`
18208
+ );
18209
+ return payload.items;
18210
+ }
18204
18211
  async resolveEnv(serviceId, options) {
18205
18212
  return this.requestWithBody(
18206
18213
  "POST",
@@ -18355,7 +18362,7 @@ var ServiceRuntimeRunner = class {
18355
18362
  await this.deployNode(service, options);
18356
18363
  return;
18357
18364
  case "node-job":
18358
- await this.deployScriptManaged(service);
18365
+ await this.deployScriptManaged(service, options);
18359
18366
  return;
18360
18367
  case "vite":
18361
18368
  await this.deployVite(service, options);
@@ -18368,7 +18375,7 @@ var ServiceRuntimeRunner = class {
18368
18375
  return;
18369
18376
  case "rust":
18370
18377
  case "python":
18371
- await this.deployScriptManaged(service);
18378
+ await this.deployScriptManaged(service, options);
18372
18379
  return;
18373
18380
  case "fc":
18374
18381
  await this.deployFc(service, options);
@@ -18382,12 +18389,12 @@ var ServiceRuntimeRunner = class {
18382
18389
  throw new Error(`Unsupported service type for deploy: ${this.getType(service)}`);
18383
18390
  }
18384
18391
  }
18385
- async stop(service) {
18392
+ async stop(service, options = {}) {
18386
18393
  switch (this.getType(service)) {
18387
18394
  case "node-job":
18388
18395
  case "rust":
18389
18396
  case "python":
18390
- await this.stopScriptManaged(service);
18397
+ await this.stopScriptManaged(service, options);
18391
18398
  return;
18392
18399
  case "node":
18393
18400
  throw new Error("service stop is only supported for script-managed python/rust services for now");
@@ -18813,7 +18820,7 @@ var ServiceRuntimeRunner = class {
18813
18820
  `echo "[pmate] Synced env to $env_local"`
18814
18821
  ].join(" && ");
18815
18822
  }
18816
- async deployScriptManaged(service) {
18823
+ async deployScriptManaged(service, options = {}) {
18817
18824
  const network = this.getNetwork(service);
18818
18825
  const runner = this.getRunner(service);
18819
18826
  const server = network.server?.trim();
@@ -18832,6 +18839,8 @@ var ServiceRuntimeRunner = class {
18832
18839
  const repoUrl = this.getRepoUrl(repository);
18833
18840
  const gitUrl = this.withToken(repoUrl);
18834
18841
  const scriptPath = script.startsWith("/") || script.startsWith("./") ? script : `./${script}`;
18842
+ const envName = options.envName ?? "prod";
18843
+ const envSyncCommand = await this.buildScriptEnvSyncCommand(service.id, envName);
18835
18844
  const command = [
18836
18845
  `cd "$HOME"`,
18837
18846
  `[ -d "${repoDir}/.git" ] || git clone "${gitUrl}" "${repoDir}"`,
@@ -18840,11 +18849,12 @@ var ServiceRuntimeRunner = class {
18840
18849
  `if [ -f ~/.bashrc ]; then . ~/.bashrc; fi`,
18841
18850
  `if [ -n "$(git status --porcelain)" ]; then git reset --hard HEAD; git clean -fd; fi`,
18842
18851
  `git pull`,
18843
- `"${scriptPath}" restart`
18852
+ ...envSyncCommand ? [envSyncCommand] : [],
18853
+ `"${scriptPath}" restart --env ${shellQuote2(envName)}`
18844
18854
  ].join(" && ");
18845
18855
  await this.runRemoteCommand(server, command);
18846
18856
  }
18847
- async stopScriptManaged(service) {
18857
+ async stopScriptManaged(service, options = {}) {
18848
18858
  const network = this.getNetwork(service);
18849
18859
  const runner = this.getRunner(service);
18850
18860
  const server = network.server?.trim();
@@ -18861,15 +18871,51 @@ var ServiceRuntimeRunner = class {
18861
18871
  }
18862
18872
  const repoDir = this.getRepoDirName(repository);
18863
18873
  const scriptPath = script.startsWith("/") || script.startsWith("./") ? script : `./${script}`;
18874
+ const envName = options.envName ?? "prod";
18875
+ const envSyncCommand = await this.buildScriptEnvSyncCommand(service.id, envName);
18864
18876
  const command = [
18865
18877
  `cd "$HOME"`,
18866
18878
  `cd "${repoDir}"`,
18867
18879
  `if [ -f ~/.profile ]; then . ~/.profile; fi`,
18868
18880
  `if [ -f ~/.bashrc ]; then . ~/.bashrc; fi`,
18869
- `"${scriptPath}" stop`
18881
+ ...envSyncCommand ? [envSyncCommand] : [],
18882
+ `"${scriptPath}" stop --env ${shellQuote2(envName)}`
18870
18883
  ].join(" && ");
18871
18884
  await this.runRemoteCommand(server, command);
18872
18885
  }
18886
+ async buildScriptEnvSyncCommand(serviceId, envName) {
18887
+ if (!this.envProvider) {
18888
+ return void 0;
18889
+ }
18890
+ let pulled;
18891
+ try {
18892
+ pulled = await this.envProvider(serviceId, { stage: this.envNameToStage(envName), format: "dotenv" });
18893
+ } catch (error) {
18894
+ if (error instanceof ServiceEnvNotFoundError) {
18895
+ ui.warn(`No env found for ${serviceId}; skipping PMate env file sync.`);
18896
+ return void 0;
18897
+ }
18898
+ throw error;
18899
+ }
18900
+ const serviceRef = parseServiceRef(serviceId);
18901
+ const envFile = `.pmate/services/${serviceRef.namespace}/${serviceRef.name}/${envName}.env`;
18902
+ const content = [
18903
+ `PMATE_SERVICE_ID=${serviceId}`,
18904
+ `PMATE_SERVICE_NAMESPACE=${serviceRef.namespace}`,
18905
+ `PMATE_SERVICE_NAME=${serviceRef.name}`,
18906
+ `PMATE_SERVICE_ENV=${envName}`,
18907
+ pulled?.content?.trimEnd() ?? "",
18908
+ ""
18909
+ ].join("\n");
18910
+ const encodedContent = Buffer.from(content, "utf8").toString("base64");
18911
+ return [
18912
+ `env_file="$HOME/${envFile}"`,
18913
+ `mkdir -p "$(dirname "$env_file")"`,
18914
+ `printf '%s' ${shellQuote2(encodedContent)} | base64 --decode > "$env_file"`,
18915
+ `chmod 600 "$env_file"`,
18916
+ `echo "[pmate] Synced env to $env_file"`
18917
+ ].join(" && ");
18918
+ }
18873
18919
  async initStaticLike(service) {
18874
18920
  const site = this.getSite(service);
18875
18921
  const bucketName = site.bucket?.trim();
@@ -19698,6 +19744,13 @@ var ServiceRuntimeRunner = class {
19698
19744
  function shellQuote2(value) {
19699
19745
  return `'${value.replace(/'/g, `'\\''`)}'`;
19700
19746
  }
19747
+ function parseServiceRef(serviceId) {
19748
+ const [namespace, name, extra] = serviceId.split(":");
19749
+ if (!namespace || !name || extra !== void 0) {
19750
+ throw new Error(`Invalid service id: ${serviceId}`);
19751
+ }
19752
+ return { namespace, name };
19753
+ }
19701
19754
 
19702
19755
  // src/commands/ServiceCommandHandler.ts
19703
19756
  init_namespacedRef();
@@ -19718,12 +19771,36 @@ var ServiceCommandHandler = class {
19718
19771
  this.renderList(items, args.json);
19719
19772
  return;
19720
19773
  }
19721
- case "get":
19722
- case "view": {
19774
+ case "get": {
19723
19775
  const record = await registry.getService(args.namespace, args.name);
19724
19776
  this.renderJson(record, true);
19725
19777
  return;
19726
19778
  }
19779
+ case "view": {
19780
+ const record = await registry.getService(args.namespace, args.name);
19781
+ const [envsResult, envVarsResult] = await Promise.allSettled([
19782
+ registry.listServiceEnvs(record.id),
19783
+ registry.listEnvItems(record.id, { includeSecretMeta: true })
19784
+ ]);
19785
+ const envs = envsResult.status === "fulfilled" ? envsResult.value : [];
19786
+ const envVars = envVarsResult.status === "fulfilled" ? envVarsResult.value : [];
19787
+ const errors = [
19788
+ envsResult.status === "rejected" ? { section: "envs", message: errorMessage(envsResult.reason) } : void 0,
19789
+ envVarsResult.status === "rejected" ? { section: "envVars", message: errorMessage(envVarsResult.reason) } : void 0
19790
+ ].filter((item) => item !== void 0);
19791
+ const payload = {
19792
+ service: record,
19793
+ envs,
19794
+ envVars,
19795
+ ...errors.length > 0 ? { errors } : {}
19796
+ };
19797
+ if (args.json) {
19798
+ this.renderJson(payload, true);
19799
+ } else {
19800
+ this.renderServiceView(payload);
19801
+ }
19802
+ return;
19803
+ }
19727
19804
  case "create": {
19728
19805
  const payload = this.readJsonFile(args.filePath);
19729
19806
  await this.requireNamespace(payload, baseUrl);
@@ -19820,7 +19897,7 @@ var ServiceCommandHandler = class {
19820
19897
  case "stop": {
19821
19898
  const record = await registry.getService(args.namespace, args.name);
19822
19899
  const service = args.env ? await this.materializeServiceEnv(registry, record, args.env) : record;
19823
- await runtime.stop(service);
19900
+ await runtime.stop(service, { envName: args.env });
19824
19901
  return;
19825
19902
  }
19826
19903
  case "init": {
@@ -19831,11 +19908,13 @@ var ServiceCommandHandler = class {
19831
19908
  }
19832
19909
  case "log": {
19833
19910
  const record = await registry.getService(args.namespace, args.name);
19834
- await runtime.log(record, {
19911
+ const service = args.env ? await this.materializeServiceEnv(registry, record, args.env) : record;
19912
+ await runtime.log(service, {
19835
19913
  page: args.page,
19836
19914
  history: args.history,
19837
19915
  search: args.search,
19838
- follow: args.follow
19916
+ follow: args.follow,
19917
+ envName: args.env
19839
19918
  });
19840
19919
  return;
19841
19920
  }
@@ -20370,6 +20449,85 @@ var ServiceCommandHandler = class {
20370
20449
  ui.info(`${time} ${item.environment} ${status}${message}`, item);
20371
20450
  }
20372
20451
  }
20452
+ renderServiceView(payload) {
20453
+ const service = payload.service;
20454
+ ui.title(`Service ${service.id}`);
20455
+ ui.plain("");
20456
+ this.renderField("Type", this.getServiceType(service));
20457
+ this.renderField("Repository", "repository" in service ? service.repository : void 0);
20458
+ this.renderField("Directory", "dir" in service ? service.dir : void 0);
20459
+ this.renderField("Status", "status" in service ? service.status : void 0);
20460
+ this.renderField("Updated", formatTimestamp("updatedAt" in service ? service.updatedAt : void 0));
20461
+ ui.plain("");
20462
+ ui.title("Environments");
20463
+ if (payload.envs.length === 0) {
20464
+ ui.plain(" none");
20465
+ } else {
20466
+ for (const env of payload.envs) {
20467
+ ui.plain(` ${env.name}`);
20468
+ const runtime = env.runtime;
20469
+ if (runtime) {
20470
+ this.renderField("Runtime", runtime.type, 4);
20471
+ this.renderRuntimeFields(runtime);
20472
+ } else {
20473
+ this.renderField("Runtime", "none", 4);
20474
+ }
20475
+ const network = env.network;
20476
+ if (network) {
20477
+ this.renderField("Network", network.type, 4);
20478
+ this.renderField("Domain", network.domain ? `https://${network.domain}` : void 0, 4);
20479
+ } else {
20480
+ this.renderField("Network", "none", 4);
20481
+ }
20482
+ }
20483
+ }
20484
+ ui.plain("");
20485
+ ui.title("Env Vars");
20486
+ if (payload.envVars.length === 0) {
20487
+ ui.plain(" none");
20488
+ } else {
20489
+ for (const item of payload.envVars) {
20490
+ const key = item.key ?? item.name ?? item.id ?? "unknown";
20491
+ const scope = item.scope ? ` [${item.scope}]` : "";
20492
+ const kind = item.kind ? ` ${item.kind}` : "";
20493
+ const value = item.value !== void 0 ? ` = ${item.value}` : "";
20494
+ ui.plain(` ${key}${scope}${kind}${value}`);
20495
+ }
20496
+ }
20497
+ for (const error of payload.errors ?? []) {
20498
+ ui.warn(`Could not load ${error.section}`, { message: error.message });
20499
+ }
20500
+ }
20501
+ renderRuntimeFields(runtime) {
20502
+ if ("bucket" in runtime) {
20503
+ this.renderField("Bucket", runtime.bucket, 4);
20504
+ }
20505
+ if ("server" in runtime) {
20506
+ this.renderField("Server", runtime.server, 4);
20507
+ }
20508
+ if ("port" in runtime) {
20509
+ this.renderField("Port", runtime.port, 4);
20510
+ }
20511
+ if ("region" in runtime) {
20512
+ this.renderField("Region", runtime.region, 4);
20513
+ }
20514
+ if ("functionName" in runtime) {
20515
+ this.renderField("Function", runtime.functionName, 4);
20516
+ }
20517
+ if ("command" in runtime) {
20518
+ this.renderField("Command", runtime.command, 4);
20519
+ }
20520
+ if ("cwd" in runtime) {
20521
+ this.renderField("Cwd", runtime.cwd, 4);
20522
+ }
20523
+ }
20524
+ renderField(label, value, indent = 0) {
20525
+ if (value === void 0 || value === null || value === "") {
20526
+ return;
20527
+ }
20528
+ const padding = " ".repeat(indent);
20529
+ ui.plain(`${padding}${label.padEnd(11)} ${String(value)}`);
20530
+ }
20373
20531
  async deleteServiceResources(service) {
20374
20532
  switch (this.getServiceType(service)) {
20375
20533
  case "vite":
@@ -20583,6 +20741,15 @@ function requireString(value, field) {
20583
20741
  }
20584
20742
  return value.trim();
20585
20743
  }
20744
+ function errorMessage(error) {
20745
+ return error instanceof Error ? error.message : String(error);
20746
+ }
20747
+ function formatTimestamp(value) {
20748
+ if (typeof value !== "number" || !Number.isFinite(value)) {
20749
+ return void 0;
20750
+ }
20751
+ return new Date(value).toISOString();
20752
+ }
20586
20753
  function splitPrimaryDomain(host) {
20587
20754
  const normalized = host?.trim();
20588
20755
  if (!normalized) {
@@ -22905,6 +23072,9 @@ function parseServiceArgs(tokens) {
22905
23072
  case "--base-url":
22906
23073
  baseUrl = requireValue6(rest, token);
22907
23074
  break;
23075
+ case "--json":
23076
+ json = true;
23077
+ break;
22908
23078
  default:
22909
23079
  throw new Error(`Unknown service view option: ${token}`);
22910
23080
  }
@@ -22943,7 +23113,7 @@ function parseServiceArgs(tokens) {
22943
23113
  cwd = requireValue6(rest, token);
22944
23114
  break;
22945
23115
  case "--env":
22946
- if (action !== "deploy" && action !== "init" && action !== "stop" && action !== "deploy-logs" && action !== "deploy-history") {
23116
+ if (action !== "deploy" && action !== "init" && action !== "stop" && action !== "log" && action !== "deploy-logs" && action !== "deploy-history") {
22947
23117
  throw new Error(`Unknown service ${action} option: ${token}`);
22948
23118
  }
22949
23119
  env = requireValue6(rest, token);
@@ -22972,7 +23142,7 @@ function parseServiceArgs(tokens) {
22972
23142
  const namespace = parsedRef.namespace;
22973
23143
  const name = parsedRef.objectId;
22974
23144
  if (action === "log") {
22975
- return { type: "service" /* Service */, action, namespace, name, baseUrl, json, page, history, search, follow };
23145
+ return { type: "service" /* Service */, action, namespace, name, baseUrl, json, page, history, search, follow, env };
22976
23146
  }
22977
23147
  if (action === "deploy-logs" || action === "deploy-history") {
22978
23148
  return { type: "service" /* Service */, action, namespace, name, env, page, baseUrl, json };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/cli",
3
- "version": "0.24.29",
3
+ "version": "0.24.31",
4
4
  "description": "CLI utilities for the pMate project",
5
5
  "license": "MIT",
6
6
  "bin": {