@pmate/cli 0.24.3 → 0.24.5

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.
Files changed (2) hide show
  1. package/dist/index.js +125 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12234,6 +12234,7 @@ var PrCommandHandler = class {
12234
12234
  return Boolean(context.status.trim()) || Boolean(context.diff.trim()) || Boolean(context.diffCached.trim());
12235
12235
  }
12236
12236
  async handleNoLocalChanges(context, existingPrUrl) {
12237
+ await this.pushCurrentBranchIfAheadOfRemote(context.currentBranch);
12237
12238
  if (existingPrUrl) {
12238
12239
  await this.pushBranchIfNeeded(context.currentBranch, context.baseBranch);
12239
12240
  ui.info(`PR already exists: ${existingPrUrl}`);
@@ -12436,6 +12437,51 @@ ${lines}`;
12436
12437
  ui.info(`Pushing branch ${branchName}`);
12437
12438
  await this.run("git", ["push", "-u", "origin", branchName]);
12438
12439
  }
12440
+ async pushCurrentBranchIfAheadOfRemote(branchName) {
12441
+ if (!branchName || branchName === "HEAD") {
12442
+ throw new Error("Cannot inspect remote sync from detached HEAD.");
12443
+ }
12444
+ const remoteRef = await this.getRemoteTrackingRef(branchName);
12445
+ if (!remoteRef) {
12446
+ ui.info(`No remote branch found for ${branchName}; pushing branch`);
12447
+ await this.pushCurrentBranch();
12448
+ return;
12449
+ }
12450
+ const counts = (await this.runCapture("git", [
12451
+ "rev-list",
12452
+ "--left-right",
12453
+ "--count",
12454
+ `${remoteRef}...HEAD`
12455
+ ])).trim().split(/\s+/).map((item) => Number(item));
12456
+ const behind = counts[0] ?? 0;
12457
+ const ahead = counts[1] ?? 0;
12458
+ if (ahead <= 0) {
12459
+ return;
12460
+ }
12461
+ if (behind > 0) {
12462
+ ui.warn(
12463
+ `Current branch is ${ahead} commit(s) ahead and ${behind} commit(s) behind ${remoteRef}; pushing may require reconciliation.`
12464
+ );
12465
+ }
12466
+ await this.pushCurrentBranch();
12467
+ }
12468
+ async getRemoteTrackingRef(branchName) {
12469
+ const upstream = (await this.runCapture(
12470
+ "git",
12471
+ ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
12472
+ true
12473
+ )).trim();
12474
+ if (upstream) {
12475
+ return upstream;
12476
+ }
12477
+ const originBranch = `origin/${branchName}`;
12478
+ const hasOriginBranch = await this.run(
12479
+ "git",
12480
+ ["show-ref", "--verify", "--quiet", `refs/remotes/${originBranch}`],
12481
+ true
12482
+ );
12483
+ return hasOriginBranch === 0 ? originBranch : "";
12484
+ }
12439
12485
  async pushCurrentBranch() {
12440
12486
  const branchName = (await this.runCapture("git", ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
12441
12487
  if (!branchName || branchName === "HEAD") {
@@ -18081,6 +18127,12 @@ var ServiceRegistryNotFoundError = class extends Error {
18081
18127
  this.name = "ServiceRegistryNotFoundError";
18082
18128
  }
18083
18129
  };
18130
+ var ServiceEnvNotFoundError = class extends Error {
18131
+ constructor(appId) {
18132
+ super(`Service env not found: ${appId}`);
18133
+ this.name = "ServiceEnvNotFoundError";
18134
+ }
18135
+ };
18084
18136
  var ServiceRegistryClient = class {
18085
18137
  constructor(baseUrl) {
18086
18138
  this.baseUrl = baseUrl;
@@ -18120,7 +18172,7 @@ var ServiceRegistryClient = class {
18120
18172
  payload
18121
18173
  );
18122
18174
  }
18123
- async listEnvItems(appId, options = {}) {
18175
+ async listEnvItems(serviceId, options = {}) {
18124
18176
  const params = new URLSearchParams();
18125
18177
  if (options.scope) {
18126
18178
  params.set("scope", options.scope);
@@ -18130,14 +18182,14 @@ var ServiceRegistryClient = class {
18130
18182
  }
18131
18183
  const payload = await this.request(
18132
18184
  "GET",
18133
- `/apps/by-id/${encodeURIComponent(appId)}/env${params.size > 0 ? `?${params.toString()}` : ""}`
18185
+ `/services/by-id/${encodeURIComponent(serviceId)}/env${params.size > 0 ? `?${params.toString()}` : ""}`
18134
18186
  );
18135
18187
  return payload.items;
18136
18188
  }
18137
- async resolveEnv(appId, options) {
18189
+ async resolveEnv(serviceId, options) {
18138
18190
  return this.requestWithBody(
18139
18191
  "POST",
18140
- `/apps/by-id/${encodeURIComponent(appId)}/env/resolve`,
18192
+ `/services/by-id/${encodeURIComponent(serviceId)}/env/resolve`,
18141
18193
  {
18142
18194
  stage: options.stage,
18143
18195
  ...options.branch ? { branch: options.branch } : {},
@@ -18145,6 +18197,35 @@ var ServiceRegistryClient = class {
18145
18197
  }
18146
18198
  );
18147
18199
  }
18200
+ async pullEnv(serviceId, options) {
18201
+ const pathname = `/services/by-id/${encodeURIComponent(serviceId)}/env/pull`;
18202
+ const response = await fetch(`${this.resolveBaseUrl()}${pathname}`, {
18203
+ method: "POST",
18204
+ headers: {
18205
+ Authorization: `Bearer ${this.readToken()}`,
18206
+ Accept: "application/json",
18207
+ "Content-Type": "application/json"
18208
+ },
18209
+ body: JSON.stringify({
18210
+ stage: options.stage,
18211
+ ...options.branch ? { branch: options.branch } : {},
18212
+ format: options.format
18213
+ })
18214
+ });
18215
+ let payload = null;
18216
+ try {
18217
+ payload = await response.json();
18218
+ } catch {
18219
+ payload = null;
18220
+ }
18221
+ if (response.status === 404) {
18222
+ throw new ServiceEnvNotFoundError(serviceId);
18223
+ }
18224
+ if (!response.ok || !payload?.success || payload.data === void 0) {
18225
+ throw new Error(payload?.message || `Env pull failed with status ${response.status}`);
18226
+ }
18227
+ return payload.data;
18228
+ }
18148
18229
  resolveBaseUrl() {
18149
18230
  return (this.baseUrl || process.env.PMATE_APPS_API_BASE_URL || "https://apps-api.pmate.chat").replace(/\/+$/, "");
18150
18231
  }
@@ -18226,6 +18307,9 @@ function normalizeRepository(repository) {
18226
18307
 
18227
18308
  // src/services/ServiceRuntimeRunner.ts
18228
18309
  var ServiceRuntimeRunner = class {
18310
+ constructor(envProvider) {
18311
+ this.envProvider = envProvider;
18312
+ }
18229
18313
  async deploy(service, options = {}) {
18230
18314
  switch (this.getType(service)) {
18231
18315
  case "vite":
@@ -18612,6 +18696,7 @@ var ServiceRuntimeRunner = class {
18612
18696
  `pkg="${appDir}/package.json"`,
18613
18697
  `if [ -f "$pkg" ] && node -e 'const fs=require("node:fs"); const p=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); process.exit(p?.scripts?.build ? 0 : 1);' "$pkg"; then (cd "${appDir}" && pnpm run build); fi`
18614
18698
  ].join("; ");
18699
+ const envSyncCommand = await this.buildNodeEnvSyncCommand(service.id, appDir);
18615
18700
  const command = [
18616
18701
  `cd "$HOME"`,
18617
18702
  `[ -d "${repoDir}/.git" ] || git clone "${gitUrl}" "${repoDir}"`,
@@ -18621,6 +18706,7 @@ var ServiceRuntimeRunner = class {
18621
18706
  `if [ -n "$(git status --porcelain)" ]; then git reset --hard HEAD; git clean -fd; fi`,
18622
18707
  `git pull`,
18623
18708
  `pnpm install`,
18709
+ ...envSyncCommand ? [envSyncCommand] : [],
18624
18710
  buildIfNeeded,
18625
18711
  `if [ -f "${appDir}/ecosystem.config.cjs" ]; then eco="${appDir}/ecosystem.config.cjs"; elif [ -f "${appDir}/ecosystem.config.js" ]; then eco="${appDir}/ecosystem.config.js"; else echo "Missing pm2 config: ${appDir}/ecosystem.config.cjs|.js" 1>&2; exit 2; fi`,
18626
18712
  pm2Start,
@@ -18628,6 +18714,33 @@ var ServiceRuntimeRunner = class {
18628
18714
  ].join(" && ");
18629
18715
  await this.runRemoteCommand(server, command);
18630
18716
  }
18717
+ async buildNodeEnvSyncCommand(serviceId, appDir) {
18718
+ if (!this.envProvider) {
18719
+ return void 0;
18720
+ }
18721
+ let pulled;
18722
+ try {
18723
+ pulled = await this.envProvider(serviceId, { stage: "prod", format: "dotenv" });
18724
+ } catch (error) {
18725
+ if (error instanceof ServiceEnvNotFoundError) {
18726
+ ui.warn(`No env found for ${serviceId}; skipping .env.local sync.`);
18727
+ return void 0;
18728
+ }
18729
+ throw error;
18730
+ }
18731
+ const content = pulled?.content;
18732
+ if (!content) {
18733
+ ui.warn(`No env found for ${serviceId}; skipping .env.local sync.`);
18734
+ return void 0;
18735
+ }
18736
+ const encodedContent = Buffer.from(content, "utf8").toString("base64");
18737
+ return [
18738
+ `env_local="${appDir}/.env.local"`,
18739
+ `mkdir -p "$(dirname "$env_local")"`,
18740
+ `printf '%s' ${shellQuote2(encodedContent)} | base64 --decode > "$env_local"`,
18741
+ `echo "[pmate] Synced env to $env_local"`
18742
+ ].join(" && ");
18743
+ }
18631
18744
  async deployScriptManaged(service) {
18632
18745
  const network = this.getNetwork(service);
18633
18746
  const runner = this.getRunner(service);
@@ -19473,6 +19586,9 @@ var ServiceRuntimeRunner = class {
19473
19586
  return new Promise((resolve) => setTimeout(resolve, ms));
19474
19587
  }
19475
19588
  };
19589
+ function shellQuote2(value) {
19590
+ return `'${value.replace(/'/g, `'\\''`)}'`;
19591
+ }
19476
19592
 
19477
19593
  // src/commands/ServiceCommandHandler.ts
19478
19594
  init_namespacedRef();
@@ -19480,7 +19596,9 @@ var ServiceCommandHandler = class {
19480
19596
  async handle(args) {
19481
19597
  const baseUrl = this.resolveBaseUrl(args.baseUrl);
19482
19598
  const registry = new ServiceRegistryClient(baseUrl);
19483
- const runtime = new ServiceRuntimeRunner();
19599
+ const runtime = new ServiceRuntimeRunner(
19600
+ (appId, options) => registry.pullEnv(appId, options)
19601
+ );
19484
19602
  switch (args.action) {
19485
19603
  case "list": {
19486
19604
  const items = await registry.listServices({
@@ -19682,7 +19800,7 @@ var ServiceCommandHandler = class {
19682
19800
  "POST",
19683
19801
  `${this.buildEnvBasePath(args.targetAppId)}/copy-from`,
19684
19802
  {
19685
- sourceAppId: args.sourceAppId,
19803
+ sourceServiceId: args.sourceAppId,
19686
19804
  ...args.scope ? { scope: args.scope } : {},
19687
19805
  ...args.includeSecret ? { includeSecret: true } : {},
19688
19806
  ...args.overwrite ? { overwrite: true } : {},
@@ -19733,7 +19851,7 @@ var ServiceCommandHandler = class {
19733
19851
  return payload.data;
19734
19852
  }
19735
19853
  buildEnvBasePath(appId) {
19736
- return `/apps/by-id/${encodeURIComponent(appId)}/env`;
19854
+ return `/services/by-id/${encodeURIComponent(appId)}/env`;
19737
19855
  }
19738
19856
  buildEnvPath(appId, query) {
19739
19857
  const params = new URLSearchParams();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/cli",
3
- "version": "0.24.3",
3
+ "version": "0.24.5",
4
4
  "description": "CLI utilities for the pMate project",
5
5
  "license": "MIT",
6
6
  "bin": {