@pmate/cli 0.36.1 → 0.37.1

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 CHANGED
@@ -313,3 +313,23 @@ A registration failure fails the release command, while preserving already uploa
313
313
  ### Android release signing
314
314
 
315
315
  `pmate service apk release <service> --env prod --variant release` resolves signing credentials from that service's server-side environment (host variables and local signing environment variables are not used). Configure `ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, and `ANDROID_KEY_PASSWORD` as Service Secrets, plus `ANDROID_SIGNING_CERT_SHA256` as the expected certificate fingerprint. Android SDK `apksigner` must be installed. The CLI signs the Gradle release APK and verifies its certificate before uploading; missing credentials, invalid signatures, or fingerprint mismatches stop publication. Keystore files use restricted temporary storage and are removed after signing. Existing apps must retain their compatible signing key. Debug builds keep their existing Gradle signing behavior.
316
+
317
+
318
+ ### iOS TestFlight builds
319
+
320
+ On macOS with Xcode, a signed-in Apple developer account, valid signing profiles and an existing App Store Connect app:
321
+
322
+ ```sh
323
+ # First build on this machine: import the highest Build already used in Apple.
324
+ pmate service ios build td:time-duck-ios --scheme TimeDuck --team D9D59BE76S --last-build 2026101501
325
+ # Subsequent builds on this machine:
326
+ pmate service ios build td:time-duck-ios --scheme TimeDuck --team D9D59BE76S
327
+ ```
328
+
329
+ `service ios build` archives Release and uploads directly to App Store Connect for TestFlight. It preserves the marketing version (for example `0.1.0`) and increments only the integer Build by one. It does not publish an App Store release. `uploaded-processing` means Apple accepted the upload; processing and TestFlight download availability require separate verification.
330
+
331
+ Register an `ios` service with `repository` and `dir`. `--cwd` overrides the repository root; `--project` / `--workspace` paths are relative to the app directory. Exactly one discovered project/workspace is required unless explicitly selected. Always provide `--scheme`; `--team` overrides the project's signing team. `--json` returns version, Build, archive and log paths after upload.
332
+
333
+ The first invocation requires `--last-build <highest-used-build>` (0 only for a new app). The next Build is one greater than the maximum of the project value, imported value and locally reserved sequence. A previous date-based Build therefore continues as that integer plus one. Import the current Apple maximum again after uploads outside this CLI or on another machine; this command does not query Apple build history. Do not lower or delete the sequence to reuse a number.
334
+
335
+ Sequences, retained archives, derived data and logs live in `~/.pmate/ios-builds/`. Project/source version files are not changed. A bundle-level exclusive lock prevents simultaneous builds on the same machine; it does not coordinate different machines. Build/upload failures keep the reserved number and logs; retry consumes the next number. After an interrupted process, remove a leftover `.lock` file only after confirming no build is still running. Builds require automatic signing; resolve Apple account/certificate/profile errors in Xcode. Extension or App targets with fixed plist versions must produce matching archive metadata or the upload is stopped.
package/dist/index.d.ts CHANGED
@@ -1003,6 +1003,19 @@ type ServiceCommandArgs = {
1003
1003
  dryRun: boolean;
1004
1004
  baseUrl?: string;
1005
1005
  json: boolean;
1006
+ } | {
1007
+ type: Command.Service;
1008
+ action: "ios-build";
1009
+ namespace: string;
1010
+ name: string;
1011
+ scheme: string;
1012
+ project?: string;
1013
+ workspace?: string;
1014
+ team?: string;
1015
+ cwd?: string;
1016
+ lastBuild?: number;
1017
+ baseUrl?: string;
1018
+ json: boolean;
1006
1019
  } | {
1007
1020
  type: Command.Service;
1008
1021
  action: "apk-release";
package/dist/index.js CHANGED
@@ -8818,8 +8818,8 @@ __export(index_exports, {
8818
8818
  parseCommandArgs: () => parseCommandArgs
8819
8819
  });
8820
8820
  module.exports = __toCommonJS(index_exports);
8821
- var import_node_fs28 = require("fs");
8822
- var import_node_path33 = __toESM(require("path"));
8821
+ var import_node_fs29 = require("fs");
8822
+ var import_node_path34 = __toESM(require("path"));
8823
8823
 
8824
8824
  // src/managedSession.ts
8825
8825
  var import_node_fs = require("fs");
@@ -14267,9 +14267,9 @@ ${indent}\`\`\``;
14267
14267
  static richTextToPlain(richText) {
14268
14268
  return richText.map((item) => item.plain_text ?? item.text?.content ?? "").join("").trim();
14269
14269
  }
14270
- static async requestJson(path33, init) {
14270
+ static async requestJson(path34, init) {
14271
14271
  const token = this.getToken();
14272
- const response = await fetch(`${this.baseUrl}${path33}`, {
14272
+ const response = await fetch(`${this.baseUrl}${path34}`, {
14273
14273
  method: init.method,
14274
14274
  headers: {
14275
14275
  Authorization: `Bearer ${token}`,
@@ -16974,8 +16974,8 @@ function descriptorPathSegments(descriptor) {
16974
16974
  return ["chains", normalized.chain, "namespaces", normalized.ns, normalized.kind, normalized.name];
16975
16975
  }
16976
16976
  function buildPathUrl(baseUrl, pathSegments, query) {
16977
- const path33 = `/${pathSegments.map((segment) => encodeURIComponent(segment)).join("/")}`;
16978
- const url = new URL(path33, baseUrl);
16977
+ const path34 = `/${pathSegments.map((segment) => encodeURIComponent(segment)).join("/")}`;
16978
+ const url = new URL(path34, baseUrl);
16979
16979
  if (query) {
16980
16980
  for (const [key, value] of Object.entries(query)) {
16981
16981
  url.searchParams.set(key, String(value));
@@ -18404,12 +18404,19 @@ function readNamespaceFromPayload(payload) {
18404
18404
  }
18405
18405
 
18406
18406
  // src/commands/ServiceCommandHandler.ts
18407
- var import_node_fs24 = require("fs");
18407
+ var import_node_fs25 = require("fs");
18408
18408
  var import_promises5 = require("fs/promises");
18409
- var import_node_path29 = __toESM(require("path"));
18410
- var import_node_child_process12 = require("child_process");
18409
+ var import_node_path30 = __toESM(require("path"));
18410
+ var import_node_child_process13 = require("child_process");
18411
18411
  var import_promises6 = require("readline/promises");
18412
18412
 
18413
+ // src/services/IosBuildService.ts
18414
+ var import_node_child_process11 = require("child_process");
18415
+ var import_node_crypto7 = require("crypto");
18416
+ var import_node_fs23 = require("fs");
18417
+ var import_node_os12 = __toESM(require("os"));
18418
+ var import_node_path28 = __toESM(require("path"));
18419
+
18413
18420
  // src/services/AndroidApkReleaseService.ts
18414
18421
  var import_node_child_process10 = require("child_process");
18415
18422
  var import_node_crypto6 = require("crypto");
@@ -18900,6 +18907,153 @@ function capitalize(value) {
18900
18907
  return value.slice(0, 1).toUpperCase() + value.slice(1);
18901
18908
  }
18902
18909
 
18910
+ // src/services/IosBuildService.ts
18911
+ function iosBuildInteger(value) {
18912
+ if (!/^(0|[1-9]\d*)$/.test(String(value)) || !Number.isSafeInteger(Number(value))) {
18913
+ throw new Error(`iOS Build must be a non-negative safe integer: ${String(value)}`);
18914
+ }
18915
+ return Number(value);
18916
+ }
18917
+ async function runXcode(args, cwd, logPath) {
18918
+ return new Promise((resolve, reject) => {
18919
+ const child = (0, import_node_child_process11.spawn)("xcodebuild", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
18920
+ let output = "";
18921
+ child.stdout.on("data", (chunk) => {
18922
+ if (args.includes("-json") || args.includes("-version")) output += chunk.toString();
18923
+ (0, import_node_fs23.appendFileSync)(logPath, chunk);
18924
+ });
18925
+ child.stderr.on("data", (chunk) => (0, import_node_fs23.appendFileSync)(logPath, chunk));
18926
+ child.on("error", reject);
18927
+ child.on("close", (code) => code === 0 ? resolve(output) : reject(new Error(`xcodebuild failed (${code}); see ${logPath}`)));
18928
+ });
18929
+ }
18930
+ var IosBuildService = class {
18931
+ constructor(deps = {}) {
18932
+ this.deps = deps;
18933
+ }
18934
+ async build(service, options) {
18935
+ if ((this.deps.platform ?? process.platform) !== "darwin") throw new Error("iOS TestFlight builds require macOS and Xcode");
18936
+ if (!("type" in service) || service.type !== "ios") throw new Error("iOS build requires an ios service");
18937
+ if (!options.scheme?.trim() || options.scheme.startsWith("-")) throw new Error("An explicit --scheme is required");
18938
+ if (options.project && options.workspace) throw new Error("Use only one of --project or --workspace");
18939
+ if (options.team && !/^[A-Z0-9]{10}$/.test(options.team)) throw new Error("Invalid Apple team ID");
18940
+ if (options.lastBuild !== void 0) iosBuildInteger(options.lastBuild);
18941
+ const repository = service.repository ?? service.mobile?.repository;
18942
+ const dir = service.dir ?? service.mobile?.dir;
18943
+ if (!dir || !repository && !options.cwd) throw new Error("iOS service requires repository and dir, or --cwd and dir");
18944
+ const repoRoot = options.cwd ? import_node_path28.default.resolve(options.cwd) : resolveRepositoryLocalRoot(repository);
18945
+ const appDir = import_node_path28.default.resolve(repoRoot, dir);
18946
+ if (!(0, import_node_fs23.existsSync)(appDir)) throw new Error(`iOS app dir not found: ${appDir}`);
18947
+ const explicit = options.project ?? options.workspace;
18948
+ const candidates = explicit ? [import_node_path28.default.resolve(appDir, explicit)] : (0, import_node_fs23.readdirSync)(appDir).filter((name) => /\.(xcodeproj|xcworkspace)$/.test(name)).map((name) => import_node_path28.default.join(appDir, name));
18949
+ if (candidates.length !== 1 || !(0, import_node_fs23.existsSync)(candidates[0])) throw new Error("Specify exactly one existing --project or --workspace; project discovery is ambiguous or empty");
18950
+ const project = candidates[0];
18951
+ const expectedExtension = options.workspace ? ".xcworkspace" : options.project ? ".xcodeproj" : import_node_path28.default.extname(project);
18952
+ if (![".xcodeproj", ".xcworkspace"].includes(expectedExtension) || import_node_path28.default.extname(project) !== expectedExtension) throw new Error("Invalid Xcode project/workspace extension");
18953
+ const root = this.deps.stateRoot ?? import_node_path28.default.join(import_node_os12.default.homedir(), ".pmate", "ios-builds");
18954
+ (0, import_node_fs23.mkdirSync)(root, { recursive: true, mode: 448 });
18955
+ const preflightLog = import_node_path28.default.join(root, `preflight-${process.pid}-${Date.now()}.log`);
18956
+ const run = this.deps.run ?? runXcode;
18957
+ await run(["-version"], appDir, preflightLog);
18958
+ const common2 = [expectedExtension === ".xcworkspace" ? "-workspace" : "-project", project, "-scheme", options.scheme, "-configuration", "Release", "-destination", "generic/platform=iOS"];
18959
+ if (options.team) common2.push(`DEVELOPMENT_TEAM=${options.team}`);
18960
+ const settings = JSON.parse(await run([...common2, "-showBuildSettings", "-json"], appDir, preflightLog));
18961
+ const apps = settings.filter((item) => item.buildSettings.PRODUCT_TYPE === "com.apple.product-type.application");
18962
+ if (apps.length !== 1) throw new Error("Scheme must contain exactly one application target");
18963
+ const cfg = apps[0].buildSettings;
18964
+ const bundleId = cfg.PRODUCT_BUNDLE_IDENTIFIER;
18965
+ const version = cfg.MARKETING_VERSION;
18966
+ const team = cfg.DEVELOPMENT_TEAM;
18967
+ if (!bundleId || !/^\d+\.\d+(?:\.\d+)?$/.test(version ?? "") || !/^[A-Z0-9]{10}$/.test(team ?? "")) throw new Error("Missing or invalid bundle ID, MARKETING_VERSION or signing team; use --team if needed");
18968
+ if (service.mobile && "bundleId" in service.mobile && service.mobile.bundleId !== bundleId) throw new Error("Service bundle ID does not match application target");
18969
+ const currentBuild = iosBuildInteger(cfg.CURRENT_PROJECT_VERSION);
18970
+ const key = (0, import_node_crypto7.createHash)("sha256").update(bundleId).digest("hex");
18971
+ const statePath = import_node_path28.default.join(root, `${key}.json`);
18972
+ const lockPath = import_node_path28.default.join(root, `${key}.lock`);
18973
+ let lock;
18974
+ try {
18975
+ lock = (0, import_node_fs23.openSync)(lockPath, "wx", 384);
18976
+ } catch (error) {
18977
+ if (error.code === "EEXIST") throw new Error(`iOS build already locked: ${lockPath}. Remove only after confirming no build is running.`);
18978
+ throw error;
18979
+ }
18980
+ try {
18981
+ (0, import_node_fs23.writeFileSync)(lock, JSON.stringify({ pid: process.pid, bundleId }));
18982
+ const previous = (0, import_node_fs23.existsSync)(statePath) ? JSON.parse((0, import_node_fs23.readFileSync)(statePath, "utf8")) : void 0;
18983
+ if (previous && previous.bundleId !== bundleId) throw new Error("iOS build state bundle ID mismatch");
18984
+ if (!previous && options.lastBuild === void 0) throw new Error("First build on this machine requires --last-build <highest Apple build>; use 0 only for a new app. Import again after external uploads or changing machines.");
18985
+ const build = Math.max(currentBuild, previous ? iosBuildInteger(previous.build) : 0, options.lastBuild ?? 0) + 1;
18986
+ iosBuildInteger(build);
18987
+ const buildDir = import_node_path28.default.join(root, key, `${version}-${build}`);
18988
+ (0, import_node_fs23.mkdirSync)(buildDir, { recursive: true, mode: 448 });
18989
+ const archivePath = import_node_path28.default.join(buildDir, "App.xcarchive");
18990
+ const logPath = import_node_path28.default.join(buildDir, "build.log");
18991
+ const state = { bundleId, version, build, status: "reserved", archivePath, logPath };
18992
+ const persist = () => {
18993
+ const temp = `${statePath}.${process.pid}.tmp`;
18994
+ (0, import_node_fs23.writeFileSync)(temp, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
18995
+ (0, import_node_fs23.renameSync)(temp, statePath);
18996
+ };
18997
+ persist();
18998
+ (0, import_node_fs23.writeFileSync)(logPath, `Service ${service.id}: ${bundleId} ${version} (${build})
18999
+ Preflight log: ${preflightLog}
19000
+ `, { mode: 384 });
19001
+ try {
19002
+ await run([...common2, `CURRENT_PROJECT_VERSION=${build}`, `MARKETING_VERSION=${version}`, "-archivePath", archivePath, "-derivedDataPath", import_node_path28.default.join(buildDir, "DerivedData"), "-allowProvisioningUpdates", "archive"], appDir, logPath);
19003
+ const applications = import_node_path28.default.join(archivePath, "Products", "Applications");
19004
+ const builtApps = (0, import_node_fs23.readdirSync)(applications).filter((name) => name.endsWith(".app"));
19005
+ if (builtApps.length !== 1) throw new Error("Archive must contain exactly one application");
19006
+ const plist = (0, import_node_fs23.readFileSync)(import_node_path28.default.join(applications, builtApps[0], "Info.plist"));
19007
+ const info = await (this.deps.readPlist ?? readPlist)(plist);
19008
+ if (info.CFBundleIdentifier !== bundleId || info.CFBundleShortVersionString !== version || String(info.CFBundleVersion) !== String(build)) throw new Error("Archive metadata does not match requested bundle, version and Build; upload stopped");
19009
+ const exportPlist = import_node_path28.default.join(buildDir, "ExportOptions.plist");
19010
+ (0, import_node_fs23.writeFileSync)(exportPlist, `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>method</key><string>app-store-connect</string><key>destination</key><string>upload</string><key>teamID</key><string>${team}</string><key>signingStyle</key><string>automatic</string><key>manageAppVersionAndBuildNumber</key><false/><key>uploadSymbols</key><true/></dict></plist>`);
19011
+ await run(["-exportArchive", "-archivePath", archivePath, "-exportOptionsPlist", exportPlist, "-exportPath", import_node_path28.default.join(buildDir, "export"), "-allowProvisioningUpdates"], appDir, logPath);
19012
+ state.status = "uploaded-processing";
19013
+ persist();
19014
+ return { serviceId: service.id, bundleId, version, build, status: "uploaded-processing", archivePath, logPath };
19015
+ } catch (error) {
19016
+ state.status = "failed";
19017
+ persist();
19018
+ (0, import_node_fs23.appendFileSync)(logPath, `
19019
+ ${String(error)}
19020
+ `);
19021
+ throw new Error(`iOS Build ${build} failed; sequence reserved. See ${logPath}. ${String(error)}`);
19022
+ }
19023
+ } finally {
19024
+ (0, import_node_fs23.closeSync)(lock);
19025
+ (0, import_node_fs23.unlinkSync)(lockPath);
19026
+ }
19027
+ }
19028
+ };
19029
+ async function readPlist(input) {
19030
+ return new Promise((resolve, reject) => {
19031
+ const child = (0, import_node_child_process11.spawn)("plutil", ["-convert", "json", "-o", "-", "--", "-"], { stdio: ["pipe", "pipe", "pipe"] });
19032
+ let output = "";
19033
+ let error = "";
19034
+ child.stdout.on("data", (chunk) => {
19035
+ output += chunk.toString();
19036
+ });
19037
+ child.stderr.on("data", (chunk) => {
19038
+ error += chunk.toString();
19039
+ });
19040
+ child.on("error", reject);
19041
+ child.on("close", (code) => {
19042
+ if (code !== 0) {
19043
+ reject(new Error(`Cannot read archive plist: ${error}`));
19044
+ return;
19045
+ }
19046
+ try {
19047
+ resolve(JSON.parse(output));
19048
+ } catch (err2) {
19049
+ reject(err2);
19050
+ }
19051
+ });
19052
+ child.stdin.on("error", reject);
19053
+ child.stdin.end(input);
19054
+ });
19055
+ }
19056
+
18903
19057
  // src/services/DeveloperLogsClient.ts
18904
19058
  init_sessionStore();
18905
19059
  init_namespacedRef();
@@ -18987,10 +19141,10 @@ async function requestDeveloperLogTool(tool, params, fetcher = fetch, delegate)
18987
19141
  }
18988
19142
 
18989
19143
  // src/services/ServiceRuntimeRunner.ts
18990
- var import_node_fs23 = require("fs");
18991
- var import_node_os12 = __toESM(require("os"));
18992
- var import_node_path28 = __toESM(require("path"));
18993
- var import_node_child_process11 = require("child_process");
19144
+ var import_node_fs24 = require("fs");
19145
+ var import_node_os13 = __toESM(require("os"));
19146
+ var import_node_path29 = __toESM(require("path"));
19147
+ var import_node_child_process12 = require("child_process");
18994
19148
  var import_node_readline = require("readline");
18995
19149
  var import_chalk3 = __toESM(require("chalk"));
18996
19150
  var import_dotenv = require("dotenv");
@@ -19489,14 +19643,14 @@ var ServiceRuntimeRunner = class {
19489
19643
  }
19490
19644
  resolveRepoRoot(service, cwd) {
19491
19645
  if (cwd) {
19492
- return import_node_path28.default.resolve(process.cwd(), cwd);
19646
+ return import_node_path29.default.resolve(process.cwd(), cwd);
19493
19647
  }
19494
19648
  const repository = this.getType(service) === "vite" || this.getType(service) === "static" ? this.getRepoStaticSite(service).repository.trim() : this.getType(service) === "fc" ? this.getFc(service).repository?.trim() : this.getType(service) === "ios" || this.getType(service) === "android" ? void 0 : this.getNetwork(service).repository?.trim();
19495
19649
  if (repository) {
19496
19650
  const repoName = repository.replace(/\/+$/, "").split("/").pop()?.replace(/\.git$/i, "");
19497
19651
  if (repoName) {
19498
- const candidate = import_node_path28.default.join(import_node_os12.default.homedir(), "dev", "pmate", repoName);
19499
- if ((0, import_node_fs23.existsSync)(candidate)) {
19652
+ const candidate = import_node_path29.default.join(import_node_os13.default.homedir(), "dev", "pmate", repoName);
19653
+ if ((0, import_node_fs24.existsSync)(candidate)) {
19500
19654
  return candidate;
19501
19655
  }
19502
19656
  }
@@ -19509,13 +19663,13 @@ var ServiceRuntimeRunner = class {
19509
19663
  if (!dir) {
19510
19664
  throw new Error(`Missing service dir for ${service.id}`);
19511
19665
  }
19512
- return import_node_path28.default.resolve(repoRoot, dir);
19666
+ return import_node_path29.default.resolve(repoRoot, dir);
19513
19667
  }
19514
19668
  async deployVite(service, options) {
19515
19669
  this.assertDeployableStaticSite(service);
19516
19670
  const appDir = this.resolveLocalDir(service, options.cwd);
19517
- const packageJsonPath = import_node_path28.default.join(appDir, "package.json");
19518
- if (!(0, import_node_fs23.existsSync)(packageJsonPath)) {
19671
+ const packageJsonPath = import_node_path29.default.join(appDir, "package.json");
19672
+ if (!(0, import_node_fs24.existsSync)(packageJsonPath)) {
19519
19673
  throw new Error(`No package.json found at ${packageJsonPath}`);
19520
19674
  }
19521
19675
  const pkg2 = this.loadPackageJson(packageJsonPath);
@@ -19530,8 +19684,8 @@ var ServiceRuntimeRunner = class {
19530
19684
  this.resolveRepoRoot(service, options.cwd),
19531
19685
  buildEnv
19532
19686
  );
19533
- const distDir = import_node_path28.default.join(appDir, "dist");
19534
- if (!(0, import_node_fs23.existsSync)(distDir)) {
19687
+ const distDir = import_node_path29.default.join(appDir, "dist");
19688
+ if (!(0, import_node_fs24.existsSync)(distDir)) {
19535
19689
  throw new Error(`No dist directory found at ${distDir}`);
19536
19690
  }
19537
19691
  const site = this.getSite(service);
@@ -19544,8 +19698,8 @@ var ServiceRuntimeRunner = class {
19544
19698
  async deployStatic(service, options) {
19545
19699
  this.assertDeployableStaticSite(service);
19546
19700
  const appDir = this.resolveLocalDir(service, options.cwd);
19547
- const srcDir = import_node_path28.default.join(appDir, "src");
19548
- if (!(0, import_node_fs23.existsSync)(srcDir)) {
19701
+ const srcDir = import_node_path29.default.join(appDir, "src");
19702
+ if (!(0, import_node_fs24.existsSync)(srcDir)) {
19549
19703
  throw new Error(`No src directory found at ${srcDir}`);
19550
19704
  }
19551
19705
  const site = this.getSite(service);
@@ -19579,8 +19733,8 @@ var ServiceRuntimeRunner = class {
19579
19733
  this.validateFcConfig(service, fc);
19580
19734
  const repoRoot = this.resolveRepoRoot(service, options.cwd);
19581
19735
  const appDir = this.resolveLocalDir(service, options.cwd);
19582
- const packageJsonPath = import_node_path28.default.join(appDir, "package.json");
19583
- if (!(0, import_node_fs23.existsSync)(packageJsonPath)) {
19736
+ const packageJsonPath = import_node_path29.default.join(appDir, "package.json");
19737
+ if (!(0, import_node_fs24.existsSync)(packageJsonPath)) {
19584
19738
  throw new Error(`No package.json found at ${packageJsonPath}`);
19585
19739
  }
19586
19740
  const pkg2 = this.loadPackageJson(packageJsonPath);
@@ -19633,15 +19787,15 @@ var ServiceRuntimeRunner = class {
19633
19787
  const repoRoot = this.resolveRepoRoot(service, options.cwd);
19634
19788
  const appDir = this.resolveLocalDir(service, options.cwd);
19635
19789
  await this.ensurePnpmWorkspaceRoot(repoRoot);
19636
- const configPath = import_node_path28.default.join(appDir, fc.configFile ?? "s.yaml");
19637
- if (!(0, import_node_fs23.existsSync)(configPath)) {
19790
+ const configPath = import_node_path29.default.join(appDir, fc.configFile ?? "s.yaml");
19791
+ if (!(0, import_node_fs24.existsSync)(configPath)) {
19638
19792
  throw new Error(`Missing FC config file at ${configPath}`);
19639
19793
  }
19640
- const packageJsonPath = import_node_path28.default.join(appDir, "package.json");
19641
- if (!(0, import_node_fs23.existsSync)(packageJsonPath)) {
19794
+ const packageJsonPath = import_node_path29.default.join(appDir, "package.json");
19795
+ if (!(0, import_node_fs24.existsSync)(packageJsonPath)) {
19642
19796
  throw new Error(`No package.json found at ${packageJsonPath}`);
19643
19797
  }
19644
- const rawConfig = (0, import_node_fs23.readFileSync)(configPath, "utf8");
19798
+ const rawConfig = (0, import_node_fs24.readFileSync)(configPath, "utf8");
19645
19799
  if (!rawConfig.includes(fc.functionName)) {
19646
19800
  throw new Error(`FC config ${configPath} does not mention functionName ${fc.functionName}`);
19647
19801
  }
@@ -19931,7 +20085,7 @@ var ServiceRuntimeRunner = class {
19931
20085
  const serverAddress = await resolveServerAddress(server);
19932
20086
  const templateName = exposeType === "websocket" ? "nginx.ws.template" : exposeType === "mixed" ? "nginx.mixed.template" : "nginx.rest.template";
19933
20087
  const templatePath = this.resolveTemplatePath(templateName);
19934
- const template = (0, import_node_fs23.readFileSync)(templatePath, "utf8");
20088
+ const template = (0, import_node_fs24.readFileSync)(templatePath, "utf8");
19935
20089
  const host = `${subdomain}.${("domain" in network ? network.domain?.trim() : void 0) || "pmate.chat"}`;
19936
20090
  const rendered = template.replace(/\$\{\{PORT\}\}/g, String(port)).replace(/\$\{\{HOSTS\}\}/g, host).replace(/\$\{\{HOST\}\}/g, host);
19937
20091
  const remotePath = `/etc/nginx/sites-enabled/${subdomain}.conf`;
@@ -19963,11 +20117,11 @@ var ServiceRuntimeRunner = class {
19963
20117
  }
19964
20118
  if (config.ensurePackageJson) {
19965
20119
  const appDir = this.resolveLocalDir(service, options.cwd);
19966
- const packageJsonPath = import_node_path28.default.join(appDir, "package.json");
19967
- if (!(0, import_node_fs23.existsSync)(packageJsonPath)) {
20120
+ const packageJsonPath = import_node_path29.default.join(appDir, "package.json");
20121
+ if (!(0, import_node_fs24.existsSync)(packageJsonPath)) {
19968
20122
  throw new Error(`Missing package.json at ${packageJsonPath}`);
19969
20123
  }
19970
- const pkgStat = (0, import_node_fs23.statSync)(packageJsonPath);
20124
+ const pkgStat = (0, import_node_fs24.statSync)(packageJsonPath);
19971
20125
  if (!pkgStat.isFile()) {
19972
20126
  throw new Error(`package.json is not a file at ${packageJsonPath}`);
19973
20127
  }
@@ -20378,9 +20532,9 @@ var ServiceRuntimeRunner = class {
20378
20532
  return tokens;
20379
20533
  }
20380
20534
  readPathValue(source, pathExpr) {
20381
- const path33 = pathExpr.split(".").map((segment) => segment.trim()).filter(Boolean);
20535
+ const path34 = pathExpr.split(".").map((segment) => segment.trim()).filter(Boolean);
20382
20536
  let cursor = source;
20383
- for (const segment of path33) {
20537
+ for (const segment of path34) {
20384
20538
  if (!cursor || typeof cursor !== "object" || Array.isArray(cursor)) {
20385
20539
  return void 0;
20386
20540
  }
@@ -20423,12 +20577,12 @@ var ServiceRuntimeRunner = class {
20423
20577
  }
20424
20578
  resolveTemplatePath(templateName) {
20425
20579
  const candidates = [
20426
- import_node_path28.default.join(__dirname, "..", "templates", templateName),
20427
- import_node_path28.default.join(__dirname, "..", "src", "templates", templateName),
20428
- import_node_path28.default.join(__dirname, "templates", templateName)
20580
+ import_node_path29.default.join(__dirname, "..", "templates", templateName),
20581
+ import_node_path29.default.join(__dirname, "..", "src", "templates", templateName),
20582
+ import_node_path29.default.join(__dirname, "templates", templateName)
20429
20583
  ];
20430
20584
  for (const candidate of candidates) {
20431
- if ((0, import_node_fs23.existsSync)(candidate)) {
20585
+ if ((0, import_node_fs24.existsSync)(candidate)) {
20432
20586
  return candidate;
20433
20587
  }
20434
20588
  }
@@ -20437,7 +20591,7 @@ var ServiceRuntimeRunner = class {
20437
20591
  writeRemoteConfig(server, content, remotePath) {
20438
20592
  return new Promise((resolve, reject) => {
20439
20593
  const target = `pmate@${server}`;
20440
- const child = (0, import_node_child_process11.spawn)("ssh", [target, "sudo", "tee", remotePath], {
20594
+ const child = (0, import_node_child_process12.spawn)("ssh", [target, "sudo", "tee", remotePath], {
20441
20595
  stdio: ["pipe", "inherit", "inherit"]
20442
20596
  });
20443
20597
  child.on("error", reject);
@@ -20452,7 +20606,7 @@ var ServiceRuntimeRunner = class {
20452
20606
  reloadNginx(server) {
20453
20607
  return new Promise((resolve, reject) => {
20454
20608
  const target = `pmate@${server}`;
20455
- const child = (0, import_node_child_process11.spawn)("ssh", [target, "sudo", "nginx", "-s", "reload"], {
20609
+ const child = (0, import_node_child_process12.spawn)("ssh", [target, "sudo", "nginx", "-s", "reload"], {
20456
20610
  stdio: ["ignore", "inherit", "inherit"]
20457
20611
  });
20458
20612
  child.on("error", reject);
@@ -20460,7 +20614,7 @@ var ServiceRuntimeRunner = class {
20460
20614
  });
20461
20615
  }
20462
20616
  loadPackageJson(packageJsonPath) {
20463
- const raw = (0, import_node_fs23.readFileSync)(packageJsonPath, "utf8");
20617
+ const raw = (0, import_node_fs24.readFileSync)(packageJsonPath, "utf8");
20464
20618
  const parsed = JSON.parse(raw);
20465
20619
  if (!parsed || typeof parsed !== "object") {
20466
20620
  throw new Error(`Invalid package.json at ${packageJsonPath}`);
@@ -20469,7 +20623,7 @@ var ServiceRuntimeRunner = class {
20469
20623
  }
20470
20624
  writePackageJson(packageJsonPath, data) {
20471
20625
  const serialized = JSON.stringify(data, null, 2);
20472
- (0, import_node_fs23.writeFileSync)(packageJsonPath, `${serialized}
20626
+ (0, import_node_fs24.writeFileSync)(packageJsonPath, `${serialized}
20473
20627
  `, "utf8");
20474
20628
  }
20475
20629
  validateFcConfig(service, fc) {
@@ -20496,8 +20650,8 @@ var ServiceRuntimeRunner = class {
20496
20650
  }
20497
20651
  }
20498
20652
  async ensurePnpmWorkspaceRoot(repoRoot) {
20499
- const workspacePath = import_node_path28.default.join(repoRoot, "pnpm-workspace.yaml");
20500
- if (!(0, import_node_fs23.existsSync)(workspacePath)) {
20653
+ const workspacePath = import_node_path29.default.join(repoRoot, "pnpm-workspace.yaml");
20654
+ if (!(0, import_node_fs24.existsSync)(workspacePath)) {
20501
20655
  throw new Error(`Missing pnpm-workspace.yaml at ${workspacePath}`);
20502
20656
  }
20503
20657
  }
@@ -20571,16 +20725,16 @@ var ServiceRuntimeRunner = class {
20571
20725
  if (!privateKeyPath) {
20572
20726
  throw new Error("Missing ssl.prv in ~/.pmate/config.yaml.");
20573
20727
  }
20574
- if (!(0, import_node_fs23.existsSync)(certificatePath)) {
20728
+ if (!(0, import_node_fs24.existsSync)(certificatePath)) {
20575
20729
  throw new Error(`SSL certificate file not found: ${certificatePath}`);
20576
20730
  }
20577
- if (!(0, import_node_fs23.existsSync)(privateKeyPath)) {
20731
+ if (!(0, import_node_fs24.existsSync)(privateKeyPath)) {
20578
20732
  throw new Error(`SSL private key file not found: ${privateKeyPath}`);
20579
20733
  }
20580
20734
  return {
20581
20735
  certName: host,
20582
- certificate: (0, import_node_fs23.readFileSync)(certificatePath, "utf8"),
20583
- privateKey: (0, import_node_fs23.readFileSync)(privateKeyPath, "utf8")
20736
+ certificate: (0, import_node_fs24.readFileSync)(certificatePath, "utf8"),
20737
+ privateKey: (0, import_node_fs24.readFileSync)(privateKeyPath, "utf8")
20584
20738
  };
20585
20739
  }
20586
20740
  resolveFcProtocol(fc, certConfig) {
@@ -20639,7 +20793,7 @@ var ServiceRuntimeRunner = class {
20639
20793
  }
20640
20794
  runCommandWithEnv(command, args, cwd, env) {
20641
20795
  return new Promise((resolve, reject) => {
20642
- const child = (0, import_node_child_process11.spawn)(command, args, {
20796
+ const child = (0, import_node_child_process12.spawn)(command, args, {
20643
20797
  cwd,
20644
20798
  stdio: "inherit",
20645
20799
  env: { ...process.env, ...env ?? {} }
@@ -20650,7 +20804,7 @@ var ServiceRuntimeRunner = class {
20650
20804
  }
20651
20805
  runShellCommand(command, cwd, env) {
20652
20806
  return new Promise((resolve, reject) => {
20653
- const child = (0, import_node_child_process11.spawn)(command, [], {
20807
+ const child = (0, import_node_child_process12.spawn)(command, [], {
20654
20808
  cwd,
20655
20809
  stdio: "inherit",
20656
20810
  shell: true,
@@ -20661,10 +20815,10 @@ var ServiceRuntimeRunner = class {
20661
20815
  });
20662
20816
  }
20663
20817
  runRemoteCommand(server, command) {
20664
- if (server === "local") return this.runCommandWithEnv("bash", ["-c", command], import_node_os12.default.homedir());
20818
+ if (server === "local") return this.runCommandWithEnv("bash", ["-c", command], import_node_os13.default.homedir());
20665
20819
  return new Promise((resolve, reject) => {
20666
20820
  const target = `pmate@${server}`;
20667
- const child = (0, import_node_child_process11.spawn)("ssh", [target, "bash -ls"], { stdio: ["pipe", "inherit", "inherit"] });
20821
+ const child = (0, import_node_child_process12.spawn)("ssh", [target, "bash -ls"], { stdio: ["pipe", "inherit", "inherit"] });
20668
20822
  child.stdin.on("error", reject);
20669
20823
  child.stdin.end(`${command}
20670
20824
  `);
@@ -20955,6 +21109,16 @@ var ServiceCommandHandler = class {
20955
21109
  );
20956
21110
  return;
20957
21111
  }
21112
+ case "ios-build": {
21113
+ const record = await registry.getService(args.namespace, args.name);
21114
+ const result = await new IosBuildService().build(record, args);
21115
+ if (args.json) this.renderJson(result, true);
21116
+ else {
21117
+ ui.success(`Uploaded iOS ${result.version} (${result.build}); Apple processing, TestFlight availability not yet verified`);
21118
+ this.renderJson(result, true);
21119
+ }
21120
+ return;
21121
+ }
20958
21122
  case "apk-release": {
20959
21123
  const record = await registry.getService(args.namespace, args.name);
20960
21124
  const apkRelease = new AndroidApkReleaseService(registry);
@@ -21057,7 +21221,7 @@ var ServiceCommandHandler = class {
21057
21221
  format: "dotenv"
21058
21222
  }
21059
21223
  );
21060
- const outputPath = import_node_path29.default.resolve(process.cwd(), args.output);
21224
+ const outputPath = import_node_path30.default.resolve(process.cwd(), args.output);
21061
21225
  await (0, import_promises5.writeFile)(outputPath, payload.content, "utf8");
21062
21226
  ui.success(`Wrote env file for ${args.appId} to ${outputPath}`);
21063
21227
  this.renderJson({ ...payload, output: outputPath }, args.json);
@@ -21283,11 +21447,11 @@ var ServiceCommandHandler = class {
21283
21447
  return (baseUrl || process.env.PMATE_APPS_API_BASE_URL || "https://apps-api.pmate.chat").replace(/\/+$/, "");
21284
21448
  }
21285
21449
  readJsonFile(filePath) {
21286
- const absolutePath = import_node_path29.default.resolve(process.cwd(), filePath);
21287
- if (!(0, import_node_fs24.existsSync)(absolutePath)) {
21450
+ const absolutePath = import_node_path30.default.resolve(process.cwd(), filePath);
21451
+ if (!(0, import_node_fs25.existsSync)(absolutePath)) {
21288
21452
  throw new Error(`JSON file not found: ${absolutePath}`);
21289
21453
  }
21290
- const raw = (0, import_node_fs24.readFileSync)(absolutePath, "utf8");
21454
+ const raw = (0, import_node_fs25.readFileSync)(absolutePath, "utf8");
21291
21455
  try {
21292
21456
  return JSON.parse(raw);
21293
21457
  } catch (error) {
@@ -21811,7 +21975,7 @@ var ServiceCommandHandler = class {
21811
21975
  removeRemoteConfig(server, remotePath) {
21812
21976
  return new Promise((resolve, reject) => {
21813
21977
  const target = `pmate@${server}`;
21814
- const child = (0, import_node_child_process12.spawn)("ssh", [target, "sudo", "rm", "-f", remotePath], {
21978
+ const child = (0, import_node_child_process13.spawn)("ssh", [target, "sudo", "rm", "-f", remotePath], {
21815
21979
  stdio: ["ignore", "inherit", "inherit"]
21816
21980
  });
21817
21981
  child.on("error", (error) => {
@@ -21829,7 +21993,7 @@ var ServiceCommandHandler = class {
21829
21993
  reloadNginx(server) {
21830
21994
  return new Promise((resolve, reject) => {
21831
21995
  const target = `pmate@${server}`;
21832
- const child = (0, import_node_child_process12.spawn)("ssh", [target, "sudo", "nginx", "-s", "reload"], {
21996
+ const child = (0, import_node_child_process13.spawn)("ssh", [target, "sudo", "nginx", "-s", "reload"], {
21833
21997
  stdio: ["ignore", "inherit", "inherit"]
21834
21998
  });
21835
21999
  child.on("error", (error) => {
@@ -21902,9 +22066,9 @@ function sleep3(ms) {
21902
22066
  }
21903
22067
 
21904
22068
  // src/commands/VercelCommandHandler.ts
21905
- var import_node_child_process13 = require("child_process");
22069
+ var import_node_child_process14 = require("child_process");
21906
22070
  var import_node_util = require("util");
21907
- var execFileAsync = (0, import_node_util.promisify)(import_node_child_process13.execFile);
22071
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process14.execFile);
21908
22072
  function parseDeploymentTimestamp(deployment) {
21909
22073
  const candidates = [deployment.createdAt, deployment.readyAt, deployment.ready];
21910
22074
  for (const value of candidates) {
@@ -22095,9 +22259,9 @@ var RoleCommandHandler = class {
22095
22259
  };
22096
22260
 
22097
22261
  // src/commands/AccountCommandHandler.ts
22098
- var import_node_fs25 = require("fs");
22099
- var import_node_crypto7 = require("crypto");
22100
- var import_node_path30 = __toESM(require("path"));
22262
+ var import_node_fs26 = require("fs");
22263
+ var import_node_crypto8 = require("crypto");
22264
+ var import_node_path31 = __toESM(require("path"));
22101
22265
  init_sessionStore();
22102
22266
  var AccountCommandHandler = class {
22103
22267
  async handle(args) {
@@ -22105,9 +22269,9 @@ var AccountCommandHandler = class {
22105
22269
  case "bot-keygen": {
22106
22270
  const keyFile = this.generateBotKeyFile(args.app, args.name);
22107
22271
  const outPath = args.out ? this.resolveFilePath(args.out) : this.resolveDefaultBotKeyPath(args.app, args.name);
22108
- (0, import_node_fs25.mkdirSync)(import_node_path30.default.dirname(outPath), { recursive: true });
22109
- (0, import_node_fs25.writeFileSync)(outPath, JSON.stringify(keyFile, null, 2), { mode: 384 });
22110
- (0, import_node_fs25.chmodSync)(outPath, 384);
22272
+ (0, import_node_fs26.mkdirSync)(import_node_path31.default.dirname(outPath), { recursive: true });
22273
+ (0, import_node_fs26.writeFileSync)(outPath, JSON.stringify(keyFile, null, 2), { mode: 384 });
22274
+ (0, import_node_fs26.chmodSync)(outPath, 384);
22111
22275
  if (!args.json) {
22112
22276
  ui.success(`Bot key saved to ${outPath}`);
22113
22277
  }
@@ -22667,10 +22831,10 @@ var AccountCommandHandler = class {
22667
22831
  throw new Error("Missing visible apps input");
22668
22832
  }
22669
22833
  const absolutePath = this.resolveFilePath(args.filePath);
22670
- if (!(0, import_node_fs25.existsSync)(absolutePath)) {
22834
+ if (!(0, import_node_fs26.existsSync)(absolutePath)) {
22671
22835
  throw new Error(`JSON file not found: ${absolutePath}`);
22672
22836
  }
22673
- const raw = (0, import_node_fs25.readFileSync)(absolutePath, "utf8");
22837
+ const raw = (0, import_node_fs26.readFileSync)(absolutePath, "utf8");
22674
22838
  let parsed;
22675
22839
  try {
22676
22840
  parsed = JSON.parse(raw);
@@ -22684,19 +22848,19 @@ var AccountCommandHandler = class {
22684
22848
  return parsed;
22685
22849
  }
22686
22850
  resolveFilePath(filePath) {
22687
- return import_node_path30.default.resolve(process.cwd(), filePath);
22851
+ return import_node_path31.default.resolve(process.cwd(), filePath);
22688
22852
  }
22689
22853
  resolveDefaultBotKeyPath(app, name) {
22690
- return import_node_path30.default.join(this.homeDir(), ".pmate", "bots", app, `${name}.bot-key.json`);
22854
+ return import_node_path31.default.join(this.homeDir(), ".pmate", "bots", app, `${name}.bot-key.json`);
22691
22855
  }
22692
22856
  resolveDefaultAccessKeyPath(app, name) {
22693
- return import_node_path30.default.join(this.homeDir(), ".pmate", "access-keys", app, `${name}.access-key.json`);
22857
+ return import_node_path31.default.join(this.homeDir(), ".pmate", "access-keys", app, `${name}.access-key.json`);
22694
22858
  }
22695
22859
  homeDir() {
22696
22860
  return process.env.HOME || process.env.USERPROFILE || process.cwd();
22697
22861
  }
22698
22862
  generateBotKeyFile(app, name) {
22699
- const { publicKey, privateKey } = (0, import_node_crypto7.generateKeyPairSync)("ed25519");
22863
+ const { publicKey, privateKey } = (0, import_node_crypto8.generateKeyPairSync)("ed25519");
22700
22864
  return {
22701
22865
  type: "pmate-bot-key",
22702
22866
  version: 1,
@@ -22710,14 +22874,14 @@ var AccountCommandHandler = class {
22710
22874
  }
22711
22875
  renameDefaultBotKeyFile(app, name, newName) {
22712
22876
  const oldPath = this.resolveDefaultBotKeyPath(app, name);
22713
- if (!(0, import_node_fs25.existsSync)(oldPath)) {
22877
+ if (!(0, import_node_fs26.existsSync)(oldPath)) {
22714
22878
  return void 0;
22715
22879
  }
22716
22880
  const keyFile = this.readBotKeyFile(oldPath, app, name);
22717
22881
  const newPath = this.resolveDefaultBotKeyPath(app, newName);
22718
- (0, import_node_fs25.mkdirSync)(import_node_path30.default.dirname(newPath), { recursive: true, mode: 448 });
22719
- (0, import_node_fs25.writeFileSync)(newPath, JSON.stringify({ ...keyFile, name: newName }, null, 2));
22720
- (0, import_node_fs25.chmodSync)(newPath, 384);
22882
+ (0, import_node_fs26.mkdirSync)(import_node_path31.default.dirname(newPath), { recursive: true, mode: 448 });
22883
+ (0, import_node_fs26.writeFileSync)(newPath, JSON.stringify({ ...keyFile, name: newName }, null, 2));
22884
+ (0, import_node_fs26.chmodSync)(newPath, 384);
22721
22885
  return newPath;
22722
22886
  }
22723
22887
  readBotKeyFile(filePath, expectedApp, expectedName) {
@@ -22725,10 +22889,10 @@ var AccountCommandHandler = class {
22725
22889
  throw new Error("Missing bot key file");
22726
22890
  }
22727
22891
  const absolutePath = this.resolveFilePath(filePath);
22728
- if (!(0, import_node_fs25.existsSync)(absolutePath)) {
22892
+ if (!(0, import_node_fs26.existsSync)(absolutePath)) {
22729
22893
  throw new Error(`Bot key file not found: ${absolutePath}`);
22730
22894
  }
22731
- const parsed = JSON.parse((0, import_node_fs25.readFileSync)(absolutePath, "utf8"));
22895
+ const parsed = JSON.parse((0, import_node_fs26.readFileSync)(absolutePath, "utf8"));
22732
22896
  if (parsed.type !== "pmate-bot-key" || parsed.version !== 1 || parsed.algorithm !== "ed25519" || !parsed.publicKey || !parsed.privateKey) {
22733
22897
  throw new Error(`Invalid PMate bot key file: ${absolutePath}`);
22734
22898
  }
@@ -22745,10 +22909,10 @@ var AccountCommandHandler = class {
22745
22909
  throw new Error("Missing access key file");
22746
22910
  }
22747
22911
  const absolutePath = this.resolveFilePath(filePath);
22748
- if (!(0, import_node_fs25.existsSync)(absolutePath)) {
22912
+ if (!(0, import_node_fs26.existsSync)(absolutePath)) {
22749
22913
  throw new Error(`Access key file not found: ${absolutePath}`);
22750
22914
  }
22751
- const parsed = JSON.parse((0, import_node_fs25.readFileSync)(absolutePath, "utf8"));
22915
+ const parsed = JSON.parse((0, import_node_fs26.readFileSync)(absolutePath, "utf8"));
22752
22916
  if (parsed.type !== "pmate-access-key" || parsed.version !== 1 || !parsed.id || !parsed.app || !parsed.secret || !parsed.expiresAt) {
22753
22917
  throw new Error(`Invalid PMate access key file: ${absolutePath}`);
22754
22918
  }
@@ -22770,9 +22934,9 @@ var AccountCommandHandler = class {
22770
22934
  createdAt: result.key.createdAt
22771
22935
  };
22772
22936
  const outPath = out ? this.resolveFilePath(out) : this.resolveDefaultAccessKeyPath(result.key.app, result.key.name);
22773
- (0, import_node_fs25.mkdirSync)(import_node_path30.default.dirname(outPath), { recursive: true });
22774
- (0, import_node_fs25.writeFileSync)(outPath, JSON.stringify(keyFile, null, 2), { mode: 384 });
22775
- (0, import_node_fs25.chmodSync)(outPath, 384);
22937
+ (0, import_node_fs26.mkdirSync)(import_node_path31.default.dirname(outPath), { recursive: true });
22938
+ (0, import_node_fs26.writeFileSync)(outPath, JSON.stringify(keyFile, null, 2), { mode: 384 });
22939
+ (0, import_node_fs26.chmodSync)(outPath, 384);
22776
22940
  }
22777
22941
  renderAccessKeyCreateResult(result, asJson, out) {
22778
22942
  if (asJson) {
@@ -22818,12 +22982,12 @@ var AccountCommandHandler = class {
22818
22982
  });
22819
22983
  }
22820
22984
  signBotChallenge(challenge, privateKey) {
22821
- const key = (0, import_node_crypto7.createPrivateKey)({
22985
+ const key = (0, import_node_crypto8.createPrivateKey)({
22822
22986
  key: Buffer.from(privateKey, "base64url"),
22823
22987
  format: "der",
22824
22988
  type: "pkcs8"
22825
22989
  });
22826
- return (0, import_node_crypto7.sign)(
22990
+ return (0, import_node_crypto8.sign)(
22827
22991
  null,
22828
22992
  Buffer.from(JSON.stringify({
22829
22993
  id: challenge.id,
@@ -22857,8 +23021,8 @@ function encodePathSegment2(value) {
22857
23021
  }
22858
23022
 
22859
23023
  // src/commands/OrgCommandHandler.ts
22860
- var import_node_fs26 = require("fs");
22861
- var import_node_path31 = __toESM(require("path"));
23024
+ var import_node_fs27 = require("fs");
23025
+ var import_node_path32 = __toESM(require("path"));
22862
23026
  init_sessionStore();
22863
23027
  var OrgCommandHandler = class {
22864
23028
  async handle(args) {
@@ -23028,11 +23192,11 @@ var OrgCommandHandler = class {
23028
23192
  if (!filePath) {
23029
23193
  throw new Error(`Missing ${itemLabel} input`);
23030
23194
  }
23031
- const absolutePath = import_node_path31.default.resolve(process.cwd(), filePath);
23032
- if (!(0, import_node_fs26.existsSync)(absolutePath)) {
23195
+ const absolutePath = import_node_path32.default.resolve(process.cwd(), filePath);
23196
+ if (!(0, import_node_fs27.existsSync)(absolutePath)) {
23033
23197
  throw new Error(`JSON file not found: ${absolutePath}`);
23034
23198
  }
23035
- const raw = (0, import_node_fs26.readFileSync)(absolutePath, "utf8");
23199
+ const raw = (0, import_node_fs27.readFileSync)(absolutePath, "utf8");
23036
23200
  let parsed;
23037
23201
  try {
23038
23202
  parsed = JSON.parse(raw);
@@ -23103,10 +23267,10 @@ function encodePathSegment3(value) {
23103
23267
  }
23104
23268
 
23105
23269
  // src/commands/HostCommandHandler.ts
23106
- var import_node_fs27 = require("fs");
23107
- var import_node_os13 = require("os");
23108
- var import_node_path32 = __toESM(require("path"));
23109
- var import_node_child_process14 = require("child_process");
23270
+ var import_node_fs28 = require("fs");
23271
+ var import_node_os14 = require("os");
23272
+ var import_node_path33 = __toESM(require("path"));
23273
+ var import_node_child_process15 = require("child_process");
23110
23274
  var BLOCK_START = "# >>> pmate host env >>>";
23111
23275
  var BLOCK_END = "# <<< pmate host env <<<";
23112
23276
  var PROFILE_BLOCK = `${BLOCK_START}
@@ -23118,7 +23282,7 @@ fi
23118
23282
  ${BLOCK_END}`;
23119
23283
  var HostCommandHandler = class {
23120
23284
  async handle(args) {
23121
- const hostId = normalizeHostId(args.host ?? process.env.PMATE_HOST_ID ?? (0, import_node_os13.hostname)());
23285
+ const hostId = normalizeHostId(args.host ?? process.env.PMATE_HOST_ID ?? (0, import_node_os14.hostname)());
23122
23286
  const client = new ServiceRegistryClient(args.baseUrl);
23123
23287
  if (args.action === "env-list") {
23124
23288
  const items = await client.listHostEnv(hostId);
@@ -23149,7 +23313,7 @@ async function applyHostEnvRemote(host, values) {
23149
23313
  const content = serializeHostEnv(values);
23150
23314
  const script = buildRemoteApplyScript(Buffer.from(content, "utf8").toString("base64"));
23151
23315
  await new Promise((resolve, reject) => {
23152
- const child = (0, import_node_child_process14.spawn)("ssh", [target, "bash -s"], { stdio: ["pipe", "pipe", "pipe"] });
23316
+ const child = (0, import_node_child_process15.spawn)("ssh", [target, "bash -s"], { stdio: ["pipe", "pipe", "pipe"] });
23153
23317
  let stdout = "";
23154
23318
  let stderr = "";
23155
23319
  child.stdout.on("data", (chunk) => {
@@ -24194,6 +24358,7 @@ function parseServiceArgs(tokens) {
24194
24358
  json
24195
24359
  };
24196
24360
  }
24361
+ if (action === "ios") return parseServiceIosArgs(rest);
24197
24362
  if (action === "apk") {
24198
24363
  return parseServiceApkArgs(rest, { baseUrl, json });
24199
24364
  }
@@ -24965,6 +25130,48 @@ function parseServiceEnvArgs(appId, tokens) {
24965
25130
  }
24966
25131
  throw new Error(`Unknown service env action: ${action}`);
24967
25132
  }
25133
+ function parseServiceIosArgs(tokens) {
25134
+ const rest = [...tokens];
25135
+ if (rest.shift() !== "build") throw new Error("Usage: service ios build <ns:service> --scheme <scheme>");
25136
+ const ref = rest.shift();
25137
+ if (!ref || ref.startsWith("-")) throw new Error("Missing iOS service ref");
25138
+ const parsed = parseNamespacedRef(ref, { entityLabel: "service ref" });
25139
+ const values = {};
25140
+ let json = false;
25141
+ const allowed = ["--scheme", "--project", "--workspace", "--team", "--cwd", "--last-build", "--base-url"];
25142
+ while (rest.length) {
25143
+ const token = rest.shift();
25144
+ if (token === "--json") {
25145
+ json = true;
25146
+ continue;
25147
+ }
25148
+ if (!allowed.includes(token)) throw new Error(`Unknown service ios build option: ${token}`);
25149
+ if (values[token] !== void 0) throw new Error(`Duplicate option: ${token}`);
25150
+ values[token] = requireValue4(rest, token);
25151
+ }
25152
+ if (!values["--scheme"]) throw new Error("An explicit --scheme is required");
25153
+ if (values["--project"] && values["--workspace"]) throw new Error("Use only one of --project or --workspace");
25154
+ let lastBuild;
25155
+ if (values["--last-build"] !== void 0) {
25156
+ const value = values["--last-build"];
25157
+ if (!/^(0|[1-9]\d*)$/.test(value) || !Number.isSafeInteger(Number(value))) throw new Error("--last-build must be a non-negative safe integer");
25158
+ lastBuild = Number(value);
25159
+ }
25160
+ return {
25161
+ type: "service" /* Service */,
25162
+ action: "ios-build",
25163
+ namespace: parsed.namespace,
25164
+ name: parsed.objectId,
25165
+ scheme: values["--scheme"],
25166
+ project: values["--project"],
25167
+ workspace: values["--workspace"],
25168
+ team: values["--team"],
25169
+ cwd: values["--cwd"],
25170
+ lastBuild,
25171
+ baseUrl: values["--base-url"],
25172
+ json
25173
+ };
25174
+ }
24968
25175
 
24969
25176
  // src/logsCommandParser.ts
24970
25177
  init_namespacedRef();
@@ -26433,7 +26640,7 @@ function requireValue11(tokens, option) {
26433
26640
 
26434
26641
  // src/index.ts
26435
26642
  var pkg = JSON.parse(
26436
- (0, import_node_fs28.readFileSync)(import_node_path33.default.resolve(__dirname, "..", "package.json"), "utf8")
26643
+ (0, import_node_fs29.readFileSync)(import_node_path34.default.resolve(__dirname, "..", "package.json"), "utf8")
26437
26644
  );
26438
26645
  var HELP_TEXT = [
26439
26646
  import_chalk4.default.bold(`pmate v${pkg.version}`),
@@ -26598,6 +26805,7 @@ var HELP_TEXT = [
26598
26805
  ` ${import_chalk4.default.gray(" delete <ns:objectId> [--base-url <url>] [--json]")}`,
26599
26806
  ` ${import_chalk4.default.gray(" default base URL: https://apps-api.pmate.chat")}`,
26600
26807
  ` ${import_chalk4.default.cyan("service")} Manage service registry entries via apps-api`,
26808
+ " ios build <ns:service> --scheme <name> [--team <id>] [--last-build <n>] [--cwd <repo>] [--project <path>|--workspace <path>] [--json]",
26601
26809
  ` ${import_chalk4.default.gray(" list [--namespace <ns>] [--repo <owner/name|https://github.com/owner/name>] [--server <name>] [--base-url <url>] [--json]")}`,
26602
26810
  ` ${import_chalk4.default.gray(" get <ns:objectId> [--base-url <url>] [--json]")}`,
26603
26811
  ` ${import_chalk4.default.gray(" view <ns:objectId> [--base-url <url>]")}`,
@@ -98,4 +98,5 @@ function createDesktopCalls({ appId, instanceId, origin, BrowserWindow, ipcMain,
98
98
  stop() { generation++; unsubscribe?.(); unsubscribe = undefined; client?.close(); client = undefined; identity = undefined; destroy(); people.clear() },
99
99
  }
100
100
  }
101
- module.exports = { createDesktopCalls }
101
+ function supportsDesktopCalls(appId) { return ['pmate:hello', 'pmate:merry'].includes(appId) }
102
+ module.exports = { createDesktopCalls, supportsDesktopCalls }
@@ -7,7 +7,7 @@ const { installMediaPermissions } = require('./media.cjs')
7
7
  const { createDesktopNotifications } = require('./notifications.cjs')
8
8
  const { desktopInstanceId, createDesktopReach } = require('./reach.cjs')
9
9
  const { createRendererReach } = require('./reach-renderer.cjs')
10
- const { createDesktopCalls } = require('./calls.cjs')
10
+ const { createDesktopCalls, supportsDesktopCalls } = require('./calls.cjs')
11
11
  const config = require('./desktop-config.json')
12
12
  const manifest = require('./resource-manifest.json')
13
13
  app.setName(config.productName)
@@ -75,7 +75,7 @@ else {
75
75
  installUpdateHandlers({ ipcMain, app, shell, appId: config.app, trusted: event => desktopNotifications.trusted(event) })
76
76
  const instanceId = desktopInstanceId(app.getPath('userData'))
77
77
  const rendererReach = createRendererReach({ ipcMain, getWindow: () => mainWindow, trusted: event => desktopNotifications.trusted(event) })
78
- if (config.app === 'pmate:hello') desktopCalls = createDesktopCalls({ appId: config.app, instanceId, origin: config.origin, BrowserWindow, ipcMain, dialog })
78
+ if (supportsDesktopCalls(config.app)) desktopCalls = createDesktopCalls({ appId: config.app, instanceId, origin: config.origin, BrowserWindow, ipcMain, dialog })
79
79
  desktopReach = createDesktopReach({ appId: config.app, instanceId, present: input => desktopNotifications.display(input),
80
80
  configureCalls: identity => { rendererReach.reset(); return desktopCalls?.configure(identity) }, stopCalls: () => { rendererReach.reset(); desktopCalls?.stop() },
81
81
  incomingCall: (callId, teamId) => { if (!desktopCalls) throw new Error('Call handler unavailable'); return desktopCalls.incoming(callId, teamId) },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/cli",
3
- "version": "0.36.1",
3
+ "version": "0.37.1",
4
4
  "description": "CLI utilities for the pMate project",
5
5
  "license": "MIT",
6
6
  "bin": {