@postman-cse/onboarding-repo-sync 2.1.4 → 2.1.6

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/cli.cjs CHANGED
@@ -92334,11 +92334,11 @@ var require_valid = __commonJS({
92334
92334
  var require_clean = __commonJS({
92335
92335
  "node_modules/postman-collection/node_modules/semver/functions/clean.js"(exports2, module2) {
92336
92336
  var parse2 = require_parse5();
92337
- var clean = (version2, options) => {
92337
+ var clean2 = (version2, options) => {
92338
92338
  const s = parse2(version2.trim().replace(/^[=v]+/, ""), options);
92339
92339
  return s ? s.version : null;
92340
92340
  };
92341
- module2.exports = clean;
92341
+ module2.exports = clean2;
92342
92342
  }
92343
92343
  });
92344
92344
 
@@ -93649,7 +93649,7 @@ var require_semver2 = __commonJS({
93649
93649
  var identifiers = require_identifiers();
93650
93650
  var parse2 = require_parse5();
93651
93651
  var valid = require_valid();
93652
- var clean = require_clean();
93652
+ var clean2 = require_clean();
93653
93653
  var inc = require_inc();
93654
93654
  var diff = require_diff();
93655
93655
  var major = require_major();
@@ -93687,7 +93687,7 @@ var require_semver2 = __commonJS({
93687
93687
  module2.exports = {
93688
93688
  parse: parse2,
93689
93689
  valid,
93690
- clean,
93690
+ clean: clean2,
93691
93691
  inc,
93692
93692
  diff,
93693
93693
  major,
@@ -119798,7 +119798,7 @@ __export(cli_exports, {
119798
119798
  });
119799
119799
  module.exports = __toCommonJS(cli_exports);
119800
119800
  var import_node_child_process = require("node:child_process");
119801
- var import_node_fs4 = require("node:fs");
119801
+ var import_node_fs5 = require("node:fs");
119802
119802
  var import_promises = require("node:fs/promises");
119803
119803
  var import_node_path3 = __toESM(require("node:path"), 1);
119804
119804
  var import_node_util = require("node:util");
@@ -120166,7 +120166,7 @@ var ExitCode;
120166
120166
  })(ExitCode || (ExitCode = {}));
120167
120167
 
120168
120168
  // src/index.ts
120169
- var import_node_fs3 = require("node:fs");
120169
+ var import_node_fs4 = require("node:fs");
120170
120170
  var path3 = __toESM(require("node:path"), 1);
120171
120171
 
120172
120172
  // node_modules/js-yaml/dist/js-yaml.mjs
@@ -123289,6 +123289,9 @@ function buildCiWorkflowLines(installUrl, postmanRegion) {
123289
123289
  " branches: [main]",
123290
123290
  " schedule:",
123291
123291
  ' - cron: "0 */6 * * *"',
123292
+ "concurrency:",
123293
+ " group: postman-onboard-${{ github.head_ref || github.ref_name }}",
123294
+ " cancel-in-progress: false",
123292
123295
  "jobs:",
123293
123296
  " test:",
123294
123297
  " runs-on: ubuntu-latest",
@@ -123305,7 +123308,7 @@ function buildCiWorkflowLines(installUrl, postmanRegion) {
123305
123308
  " ruby <<'RUBY'",
123306
123309
  " require 'yaml'",
123307
123310
  " config = YAML.load_file('.postman/resources.yaml') || {}",
123308
- " cloud = config.fetch('cloudResources', {})",
123311
+ " cloud = config.fetch('canonical', config.fetch('cloudResources', {}))",
123309
123312
  " collections = cloud.fetch('collections', {})",
123310
123313
  " environments = cloud.fetch('environments', {})",
123311
123314
  " smoke = collections.find { |path, _| path.include?('[Smoke]') }&.last",
@@ -123503,6 +123506,58 @@ function buildAdoCiWorkflowLines(installUrl, postmanRegion) {
123503
123506
  ""
123504
123507
  ];
123505
123508
  }
123509
+ function renderGcWorkflowTemplate() {
123510
+ return [
123511
+ "name: Postman Preview GC",
123512
+ "on:",
123513
+ " delete:",
123514
+ ' branches: ["**"]',
123515
+ " pull_request:",
123516
+ " types: [closed]",
123517
+ " schedule:",
123518
+ ' - cron: "0 2 * * *"',
123519
+ " workflow_dispatch:",
123520
+ " inputs:",
123521
+ " branch:",
123522
+ " description: Branch name to GC (optional, otherwise sweep by TTL/branch-existence)",
123523
+ " required: false",
123524
+ "concurrency:",
123525
+ " group: postman-preview-gc-${{ github.ref_name }}",
123526
+ " cancel-in-progress: false",
123527
+ "jobs:",
123528
+ " gc:",
123529
+ " runs-on: ubuntu-latest",
123530
+ " if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository",
123531
+ " steps:",
123532
+ " - uses: actions/checkout@v5",
123533
+ " with:",
123534
+ " fetch-depth: 0",
123535
+ " - name: Run Postman preview GC (provider-neutral cli.cjs gc)",
123536
+ " env:",
123537
+ " POSTMAN_API_KEY: ${{ secrets.POSTMAN_API_KEY }}",
123538
+ " POSTMAN_ACCESS_TOKEN: ${{ secrets.POSTMAN_ACCESS_TOKEN }}",
123539
+ " POSTMAN_WORKSPACE_ID: ${{ vars.POSTMAN_WORKSPACE_ID }}",
123540
+ " REPO_URL: https://github.com/${{ github.repository }}",
123541
+ " run: |",
123542
+ " set -euo pipefail",
123543
+ " # The repo-sync action bundles a provider-neutral gc command (cli.cjs gc)",
123544
+ " # that uses the Postman access token for inventory/deletion and the",
123545
+ " # provider ambient credential (GITHUB_TOKEN via git ls-remote) for branch existence.",
123546
+ " # Daily scheduled run is the retention executor (TTL contract of last resort).",
123547
+ ' if [ -n "${{ inputs.branch }}" ]; then',
123548
+ ' npx @postman-cse/onboarding-repo-sync gc --branch "${{ inputs.branch }}" --workspace-id "$POSTMAN_WORKSPACE_ID" --repo-url "$REPO_URL"',
123549
+ " else",
123550
+ ' npx @postman-cse/onboarding-repo-sync gc --workspace-id "$POSTMAN_WORKSPACE_ID" --repo-url "$REPO_URL"',
123551
+ " fi",
123552
+ " - name: Orphan audit summary (job summary)",
123553
+ " if: always()",
123554
+ " run: |",
123555
+ ' echo "### Preview GC orphan audit" >> "$GITHUB_STEP_SUMMARY"',
123556
+ ' echo "Marker-guarded deletion only \u2014 strangers (no marker) are never deleted. See gc-summary-json for structured counts." >> "$GITHUB_STEP_SUMMARY"',
123557
+ ""
123558
+ ].join("\n");
123559
+ }
123560
+ var GC_WORKFLOW_TEMPLATE = renderGcWorkflowTemplate();
123506
123561
  function getCiWorkflowTemplate(provider, options = {}) {
123507
123562
  if (provider === "azure-devops") {
123508
123563
  const rawUrl = String(options.postmanCliInstallUrl || "").trim() || DEFAULT_POSTMAN_CLI_INSTALL_URL;
@@ -125073,6 +125128,53 @@ function createInternalIntegrationAdapter(options) {
125073
125128
  return new BifrostInternalIntegrationAdapter(options);
125074
125129
  }
125075
125130
 
125131
+ // src/lib/retry.ts
125132
+ function sleep(delayMs) {
125133
+ return new Promise((resolve2) => {
125134
+ setTimeout(resolve2, delayMs);
125135
+ });
125136
+ }
125137
+ function normalizeRetryOptions(options) {
125138
+ return {
125139
+ maxAttempts: Math.max(1, options.maxAttempts ?? 3),
125140
+ delayMs: Math.max(0, options.delayMs ?? 2e3),
125141
+ backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
125142
+ maxDelayMs: options.maxDelayMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, options.maxDelayMs),
125143
+ onRetry: options.onRetry ?? (async () => void 0),
125144
+ shouldRetry: options.shouldRetry ?? (() => true),
125145
+ sleep: options.sleep ?? sleep
125146
+ };
125147
+ }
125148
+ async function retry(operation, options = {}) {
125149
+ const normalized = normalizeRetryOptions(options);
125150
+ let nextDelayMs = normalized.delayMs;
125151
+ for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
125152
+ try {
125153
+ return await operation();
125154
+ } catch (error) {
125155
+ const shouldRetry = attempt < normalized.maxAttempts && normalized.shouldRetry(error, {
125156
+ attempt,
125157
+ maxAttempts: normalized.maxAttempts
125158
+ });
125159
+ if (!shouldRetry) {
125160
+ throw error;
125161
+ }
125162
+ await normalized.onRetry({
125163
+ attempt,
125164
+ maxAttempts: normalized.maxAttempts,
125165
+ delayMs: nextDelayMs,
125166
+ error
125167
+ });
125168
+ await normalized.sleep(nextDelayMs);
125169
+ nextDelayMs = Math.min(
125170
+ normalized.maxDelayMs,
125171
+ Math.round(nextDelayMs * normalized.backoffMultiplier)
125172
+ );
125173
+ }
125174
+ }
125175
+ throw new Error("Retry exhausted without returning or throwing");
125176
+ }
125177
+
125076
125178
  // src/contracts.ts
125077
125179
  var postmanRepoSyncActionContract = {
125078
125180
  name: "postman-repo-sync-action",
@@ -125242,6 +125344,25 @@ var postmanRepoSyncActionContract = {
125242
125344
  default: "warn",
125243
125345
  allowedValues: ["enforce", "warn"]
125244
125346
  },
125347
+ "branch-strategy": {
125348
+ description: "Branch-aware sync strategy. legacy (default) keeps branch-blind behavior; publish-gate restricts canonical writes to the canonical branch and skips repo-sync on other branches; preview additionally maintains suffixed per-branch preview asset sets.",
125349
+ required: false,
125350
+ default: "legacy",
125351
+ allowedValues: ["legacy", "preview", "publish-gate"]
125352
+ },
125353
+ "canonical-branch": {
125354
+ description: "Explicit canonical branch (the sole writer of canonical assets and tracked state). Defaults to the provider-resolved default branch; required on providers without a default-branch variable (Bitbucket, Azure DevOps) when branch-strategy is not legacy.",
125355
+ required: false
125356
+ },
125357
+ "channels": {
125358
+ description: 'Comma-separated channel map for long-lived promotion branches, e.g. "develop=DEV, staging=STAGE, release/*=RC". Channel branches maintain prefix-named parallel asset sets and never mutate canonical assets.',
125359
+ required: false
125360
+ },
125361
+ "preview-ttl": {
125362
+ description: "Sliding TTL in days for preview asset sets (refreshed on every successful preview sync; the retention contract of last resort when no provider credential is available for branch-existence checks).",
125363
+ required: false,
125364
+ default: "30"
125365
+ },
125245
125366
  "github-token": {
125246
125367
  description: "GitHub token used for repo variable persistence and commits.",
125247
125368
  required: false
@@ -125279,6 +125400,11 @@ var postmanRepoSyncActionContract = {
125279
125400
  description: "Spec UID from bootstrap, persisted into .postman/resources.yaml cloudResources.",
125280
125401
  required: false
125281
125402
  },
125403
+ "spec-content-changed": {
125404
+ description: "Whether bootstrap changed canonical spec content; controls final native Spec Hub tag publication.",
125405
+ required: false,
125406
+ default: "true"
125407
+ },
125282
125408
  "spec-path": {
125283
125409
  description: "Optional repo-root-relative path to the local spec file for resources/workflows metadata.",
125284
125410
  required: false
@@ -125323,6 +125449,18 @@ var postmanRepoSyncActionContract = {
125323
125449
  },
125324
125450
  "commit-sha": {
125325
125451
  description: "Commit SHA produced by repo-write-mode, if any."
125452
+ },
125453
+ "sync-status": {
125454
+ description: "Branch-aware sync status: synced, skipped-branch-gate, or empty under branch-strategy legacy."
125455
+ },
125456
+ "branch-decision": {
125457
+ description: "Serialized BranchDecision JSON for downstream actions (also exported as POSTMAN_BRANCH_DECISION)."
125458
+ },
125459
+ "spec-version-tag": {
125460
+ description: "Native Spec Hub version tag created after successful canonical repo-sync finalization."
125461
+ },
125462
+ "spec-version-url": {
125463
+ description: "Read-only URL for the tagged Spec Hub snapshot."
125326
125464
  }
125327
125465
  },
125328
125466
  behavior: {
@@ -125390,53 +125528,6 @@ var PostmanAssetsClient = class {
125390
125528
  }
125391
125529
  };
125392
125530
 
125393
- // src/lib/retry.ts
125394
- function sleep(delayMs) {
125395
- return new Promise((resolve2) => {
125396
- setTimeout(resolve2, delayMs);
125397
- });
125398
- }
125399
- function normalizeRetryOptions(options) {
125400
- return {
125401
- maxAttempts: Math.max(1, options.maxAttempts ?? 3),
125402
- delayMs: Math.max(0, options.delayMs ?? 2e3),
125403
- backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
125404
- maxDelayMs: options.maxDelayMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, options.maxDelayMs),
125405
- onRetry: options.onRetry ?? (async () => void 0),
125406
- shouldRetry: options.shouldRetry ?? (() => true),
125407
- sleep: options.sleep ?? sleep
125408
- };
125409
- }
125410
- async function retry(operation, options = {}) {
125411
- const normalized = normalizeRetryOptions(options);
125412
- let nextDelayMs = normalized.delayMs;
125413
- for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
125414
- try {
125415
- return await operation();
125416
- } catch (error) {
125417
- const shouldRetry = attempt < normalized.maxAttempts && normalized.shouldRetry(error, {
125418
- attempt,
125419
- maxAttempts: normalized.maxAttempts
125420
- });
125421
- if (!shouldRetry) {
125422
- throw error;
125423
- }
125424
- await normalized.onRetry({
125425
- attempt,
125426
- maxAttempts: normalized.maxAttempts,
125427
- delayMs: nextDelayMs,
125428
- error
125429
- });
125430
- await normalized.sleep(nextDelayMs);
125431
- nextDelayMs = Math.min(
125432
- normalized.maxDelayMs,
125433
- Math.round(nextDelayMs * normalized.backoffMultiplier)
125434
- );
125435
- }
125436
- }
125437
- throw new Error("Retry exhausted without returning or throwing");
125438
- }
125439
-
125440
125531
  // src/lib/postman/postman-gateway-assets-client.ts
125441
125532
  var MAX_CREATE_FLIGHTS = 256;
125442
125533
  var createFlights = /* @__PURE__ */ new Map();
@@ -125462,6 +125553,84 @@ var PostmanGatewayAssetsClient = class {
125462
125553
  if (!envelope) return null;
125463
125554
  return this.asRecord(envelope.data) ?? envelope;
125464
125555
  }
125556
+ /** Native Spec Hub tags attach to the latest changelog group. */
125557
+ async tagSpecVersion(specId, name) {
125558
+ const trimmed = name.trim().slice(0, 255);
125559
+ const response = await this.gateway.requestJson({
125560
+ service: "specification",
125561
+ method: "post",
125562
+ path: `/specifications/${specId}/tags`,
125563
+ body: { name: trimmed }
125564
+ });
125565
+ const record = this.dataOf(response) ?? {};
125566
+ return { id: String(record.id ?? "").trim(), name: String(record.name ?? trimmed).trim() };
125567
+ }
125568
+ async listSpecVersionTags(specId) {
125569
+ const response = await this.gateway.requestJson({
125570
+ service: "specification",
125571
+ method: "get",
125572
+ path: `/specifications/${specId}/tags`,
125573
+ query: { limit: "50" }
125574
+ });
125575
+ const data = Array.isArray(response?.data) ? response.data : [];
125576
+ return data.map((entry) => this.asRecord(entry)).filter((entry) => entry !== null).map((entry) => ({
125577
+ id: String(entry.id ?? "").trim(),
125578
+ // listTags returns `message`; createTag returns `name`. Accept both.
125579
+ name: String(entry.name ?? entry.message ?? "").trim()
125580
+ })).filter((entry) => entry.id || entry.name);
125581
+ }
125582
+ async deleteCollection(collectionUid) {
125583
+ const bareId = String(collectionUid).split("-").slice(-5).join("-") || collectionUid;
125584
+ await this.gateway.requestJson({
125585
+ service: "collection",
125586
+ method: "delete",
125587
+ path: `/v3/collections/${bareId}`
125588
+ });
125589
+ }
125590
+ async listSpecifications(workspaceId) {
125591
+ const response = await this.gateway.requestJson({
125592
+ service: "specification",
125593
+ method: "get",
125594
+ path: `/specifications?containerType=workspace&containerId=${workspaceId}`
125595
+ });
125596
+ const data = Array.isArray(response?.data) ? response.data : [];
125597
+ return data.map((entry) => this.asRecord(entry)).filter((entry) => entry !== null).map((entry) => ({ uid: String(entry.id ?? entry.uid ?? "").trim(), name: String(entry.name ?? "").trim() })).filter((entry) => entry.uid && entry.name);
125598
+ }
125599
+ async getSpecContent(specId) {
125600
+ const files = await this.gateway.requestJson({
125601
+ service: "specification",
125602
+ method: "get",
125603
+ path: `/specifications/${specId}/files`
125604
+ });
125605
+ const entries = Array.isArray(files?.data) ? files.data : [];
125606
+ const root = entries.map((entry) => this.asRecord(entry)).find((entry) => entry?.type === "ROOT") ?? entries.map((entry) => this.asRecord(entry)).find((entry) => entry !== null);
125607
+ const fileId = String(root?.id ?? "").trim();
125608
+ if (!fileId) return void 0;
125609
+ const file = await this.gateway.requestJson({
125610
+ service: "specification",
125611
+ method: "get",
125612
+ path: `/specifications/${specId}/files/${fileId}`,
125613
+ query: { fields: "content" }
125614
+ });
125615
+ const record = this.dataOf(file);
125616
+ return typeof record?.content === "string" ? record.content : void 0;
125617
+ }
125618
+ async listSpecCollections(specId) {
125619
+ const response = await this.gateway.requestJson({
125620
+ service: "specification",
125621
+ method: "get",
125622
+ path: `/specifications/${specId}/collections`
125623
+ });
125624
+ const data = Array.isArray(response?.data) ? response.data : [];
125625
+ return data.map((entry) => this.asRecord(entry)).filter((entry) => entry !== null).map((entry) => ({ uid: String(entry.collection ?? entry.collectionId ?? entry.id ?? "").trim(), name: String(entry.name ?? "").trim() })).filter((entry) => entry.uid);
125626
+ }
125627
+ async deleteSpec(specId) {
125628
+ await this.gateway.requestJson({
125629
+ service: "specification",
125630
+ method: "delete",
125631
+ path: `/specifications/${specId}`
125632
+ });
125633
+ }
125465
125634
  idOf(record) {
125466
125635
  if (!record) return "";
125467
125636
  const id = record.uid ?? record.id;
@@ -125746,6 +125915,9 @@ var PostmanGatewayAssetsClient = class {
125746
125915
  path: `/mocks?workspace=${ws}`,
125747
125916
  body
125748
125917
  },
125918
+ // Unsafe create: never blind re-POST. An ESOCKETTIMEDOUT after accept
125919
+ // may still have created the mock; reconcile via discovery below and
125920
+ // let the orchestrator retry the whole create-or-adopt cycle.
125749
125921
  { retryTransient: false }
125750
125922
  );
125751
125923
  const record = this.dataOf(response);
@@ -125784,6 +125956,33 @@ var PostmanGatewayAssetsClient = class {
125784
125956
  environment: String(m.environment ?? "")
125785
125957
  }));
125786
125958
  }
125959
+ /**
125960
+ * Delete an environment through the sync service (GC path). The path id is
125961
+ * the bare model id (public uid tail), mirroring updateEnvironment.
125962
+ */
125963
+ async deleteEnvironment(uid) {
125964
+ await this.gateway.requestJson({
125965
+ service: "sync",
125966
+ method: "delete",
125967
+ path: `/environment/${this.toModelId(uid)}`
125968
+ });
125969
+ }
125970
+ /** Delete a mock server (GC path). */
125971
+ async deleteMock(uid) {
125972
+ await this.gateway.requestJson({
125973
+ service: "mock",
125974
+ method: "delete",
125975
+ path: `/mocks/${this.toModelId(uid)}`
125976
+ });
125977
+ }
125978
+ /** Delete a collection-based monitor (jobTemplate) (GC path). */
125979
+ async deleteMonitor(uid) {
125980
+ await this.gateway.requestJson({
125981
+ service: "monitors",
125982
+ method: "delete",
125983
+ path: `/jobTemplates/${this.toModelId(uid)}`
125984
+ });
125985
+ }
125787
125986
  async mockExists(uid) {
125788
125987
  try {
125789
125988
  await this.gateway.requestJson({
@@ -126238,6 +126437,331 @@ var AccessTokenGatewayClient = class {
126238
126437
  }
126239
126438
  };
126240
126439
 
126440
+ // src/lib/repo/branch-decision.ts
126441
+ var import_node_fs3 = require("node:fs");
126442
+ var import_node_crypto2 = require("node:crypto");
126443
+ var ContractError = class extends Error {
126444
+ code;
126445
+ constructor(code, message) {
126446
+ super(`${code}: ${message}`);
126447
+ this.code = code;
126448
+ this.name = "ContractError";
126449
+ }
126450
+ };
126451
+ function clean(value) {
126452
+ const trimmed = (value ?? "").trim();
126453
+ return trimmed.length > 0 ? trimmed : void 0;
126454
+ }
126455
+ function stripRefPrefix(ref) {
126456
+ const raw = clean(ref);
126457
+ if (!raw) {
126458
+ return { kind: "unknown" };
126459
+ }
126460
+ if (raw.startsWith("refs/heads/")) {
126461
+ return { name: raw.slice("refs/heads/".length), kind: "branch" };
126462
+ }
126463
+ if (raw.startsWith("refs/tags/")) {
126464
+ return { name: raw.slice("refs/tags/".length), kind: "tag" };
126465
+ }
126466
+ if (raw.startsWith("refs/pull/") || raw.startsWith("refs/merge")) {
126467
+ return { kind: "unknown" };
126468
+ }
126469
+ return { name: raw, kind: "branch" };
126470
+ }
126471
+ function detectProvider(env) {
126472
+ if (clean(env.GITHUB_ACTIONS) || clean(env.GITHUB_REPOSITORY)) return "github";
126473
+ if (clean(env.GITLAB_CI) || clean(env.CI_PROJECT_PATH)) return "gitlab";
126474
+ if (clean(env.BITBUCKET_REPO_SLUG) || clean(env.BITBUCKET_BRANCH)) return "bitbucket";
126475
+ if (clean(env.TF_BUILD) || clean(env.BUILD_REPOSITORY_URI)) return "azure-devops";
126476
+ return "unknown";
126477
+ }
126478
+ function readGithubEvent(env) {
126479
+ const path5 = clean(env.GITHUB_EVENT_PATH);
126480
+ if (!path5) return void 0;
126481
+ try {
126482
+ return JSON.parse((0, import_node_fs3.readFileSync)(path5, "utf8"));
126483
+ } catch {
126484
+ return void 0;
126485
+ }
126486
+ }
126487
+ function resolveBranchIdentity(env = process.env, overrides = {}) {
126488
+ const provider = detectProvider(env);
126489
+ const explicitDefault = clean(overrides.defaultBranch);
126490
+ let headBranch;
126491
+ let rawRef;
126492
+ let refKind;
126493
+ let isPrContext = false;
126494
+ let isForkPr = false;
126495
+ let defaultBranch = explicitDefault;
126496
+ let headSha;
126497
+ switch (provider) {
126498
+ case "github": {
126499
+ const event = readGithubEvent(env);
126500
+ headSha = clean(env.GITHUB_SHA);
126501
+ defaultBranch ??= clean(event?.repository?.default_branch);
126502
+ const headRef = clean(env.GITHUB_HEAD_REF);
126503
+ if (headRef) {
126504
+ isPrContext = true;
126505
+ headBranch = headRef;
126506
+ rawRef = clean(env.GITHUB_REF) ?? headRef;
126507
+ refKind = "branch";
126508
+ const headRepo = event?.pull_request?.head?.repo?.full_name;
126509
+ const baseRepo = event?.pull_request?.base?.repo?.full_name ?? event?.repository?.full_name;
126510
+ isForkPr = Boolean(headRepo && baseRepo && headRepo !== baseRepo);
126511
+ headSha = clean(event?.pull_request?.head?.sha) ?? headSha;
126512
+ } else {
126513
+ rawRef = clean(env.GITHUB_REF) ?? clean(env.GITHUB_REF_NAME);
126514
+ const parsed = stripRefPrefix(rawRef);
126515
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
126516
+ refKind = parsed.kind;
126517
+ }
126518
+ break;
126519
+ }
126520
+ case "gitlab": {
126521
+ headSha = clean(env.CI_COMMIT_SHA);
126522
+ defaultBranch ??= clean(env.CI_DEFAULT_BRANCH);
126523
+ const mrSource = clean(env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME);
126524
+ if (mrSource) {
126525
+ isPrContext = true;
126526
+ headBranch = mrSource;
126527
+ rawRef = mrSource;
126528
+ refKind = "branch";
126529
+ const sourceProject = clean(env.CI_MERGE_REQUEST_SOURCE_PROJECT_ID);
126530
+ const targetProject = clean(env.CI_MERGE_REQUEST_PROJECT_ID);
126531
+ isForkPr = Boolean(sourceProject && targetProject && sourceProject !== targetProject);
126532
+ } else if (clean(env.CI_COMMIT_TAG)) {
126533
+ rawRef = clean(env.CI_COMMIT_TAG);
126534
+ refKind = "tag";
126535
+ } else {
126536
+ headBranch = clean(env.CI_COMMIT_BRANCH) ?? clean(env.CI_COMMIT_REF_NAME);
126537
+ rawRef = headBranch;
126538
+ refKind = headBranch ? "branch" : "unknown";
126539
+ }
126540
+ break;
126541
+ }
126542
+ case "bitbucket": {
126543
+ headSha = clean(env.BITBUCKET_COMMIT);
126544
+ if (clean(env.BITBUCKET_TAG)) {
126545
+ rawRef = clean(env.BITBUCKET_TAG);
126546
+ refKind = "tag";
126547
+ } else {
126548
+ headBranch = clean(env.BITBUCKET_BRANCH);
126549
+ rawRef = headBranch;
126550
+ refKind = headBranch ? "branch" : "unknown";
126551
+ isPrContext = Boolean(clean(env.BITBUCKET_PR_ID));
126552
+ }
126553
+ break;
126554
+ }
126555
+ case "azure-devops": {
126556
+ headSha = clean(env.BUILD_SOURCEVERSION);
126557
+ const prSource = clean(env.SYSTEM_PULLREQUEST_SOURCEBRANCH);
126558
+ if (prSource) {
126559
+ isPrContext = true;
126560
+ const parsed = stripRefPrefix(prSource);
126561
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
126562
+ rawRef = prSource;
126563
+ refKind = parsed.kind;
126564
+ const forkFlag = clean(env.SYSTEM_PULLREQUEST_ISFORK);
126565
+ isForkPr = forkFlag?.toLowerCase() === "true";
126566
+ } else {
126567
+ rawRef = clean(env.BUILD_SOURCEBRANCH);
126568
+ const parsed = stripRefPrefix(rawRef);
126569
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
126570
+ refKind = parsed.kind;
126571
+ }
126572
+ break;
126573
+ }
126574
+ default: {
126575
+ refKind = "unknown";
126576
+ }
126577
+ }
126578
+ if (refKind === "branch" && headBranch && defaultBranch && headBranch === defaultBranch) {
126579
+ refKind = "default-branch";
126580
+ }
126581
+ return { provider, headBranch, rawRef, defaultBranch, refKind, isPrContext, isForkPr, headSha };
126582
+ }
126583
+ function parseChannelRules(input) {
126584
+ const raw = clean(input);
126585
+ const rules = [];
126586
+ for (const part of raw ? raw.split(",") : []) {
126587
+ const entry = part.trim();
126588
+ if (!entry) continue;
126589
+ const eq = entry.indexOf("=");
126590
+ if (eq <= 0 || eq === entry.length - 1) {
126591
+ throw new ContractError(
126592
+ "CONTRACT_CHANNELS_INPUT_INVALID",
126593
+ `channels entry "${entry}" must be <branch-or-glob>=<CODE>`
126594
+ );
126595
+ }
126596
+ const pattern = entry.slice(0, eq).trim();
126597
+ const code = entry.slice(eq + 1).trim().toUpperCase();
126598
+ if (!/^[A-Z][A-Z0-9_-]{0,15}$/.test(code)) {
126599
+ throw new ContractError(
126600
+ "CONTRACT_CHANNELS_INPUT_INVALID",
126601
+ `channel code "${code}" must be 1-16 chars, A-Z 0-9 _ -, starting with a letter`
126602
+ );
126603
+ }
126604
+ rules.push({ pattern, code });
126605
+ }
126606
+ if (!rules.some((rule) => rule.pattern === "release/*")) {
126607
+ rules.push({ pattern: "release/*", code: "RC" });
126608
+ }
126609
+ return rules;
126610
+ }
126611
+ function matchChannel(branch, rules) {
126612
+ for (const rule of rules) {
126613
+ if (rule.pattern.endsWith("*")) {
126614
+ const prefix = rule.pattern.slice(0, -1);
126615
+ if (branch.startsWith(prefix)) return rule;
126616
+ } else if (branch === rule.pattern) {
126617
+ return rule;
126618
+ }
126619
+ }
126620
+ return void 0;
126621
+ }
126622
+ function resolveBranchDecision(options) {
126623
+ const { strategy, identity } = options;
126624
+ const channels = options.channels ?? [];
126625
+ if (strategy === "legacy") {
126626
+ return {
126627
+ tier: "legacy",
126628
+ strategy,
126629
+ identity,
126630
+ canonicalBranch: clean(options.canonicalBranch) ?? identity.defaultBranch,
126631
+ reason: "branch-strategy legacy: branch-blind pre-v2 behavior"
126632
+ };
126633
+ }
126634
+ const canonicalBranch = clean(options.canonicalBranch) ?? identity.defaultBranch;
126635
+ if (!canonicalBranch) {
126636
+ throw new ContractError(
126637
+ "CONTRACT_DEFAULT_BRANCH_UNRESOLVED",
126638
+ `cannot resolve the canonical branch on ${identity.provider} (no explicit canonical-branch input and the provider exposes no default-branch variable). Set the canonical-branch input.`
126639
+ );
126640
+ }
126641
+ if (identity.refKind === "tag" || identity.refKind === "unknown" || !identity.headBranch) {
126642
+ return {
126643
+ tier: "gated",
126644
+ strategy,
126645
+ identity,
126646
+ canonicalBranch,
126647
+ reason: `ref kind ${identity.refKind}: never canonical/preview-eligible; no-op with annotation`
126648
+ };
126649
+ }
126650
+ if (identity.headBranch === canonicalBranch) {
126651
+ return {
126652
+ tier: "canonical",
126653
+ strategy,
126654
+ identity,
126655
+ canonicalBranch,
126656
+ reason: `head branch equals canonical branch ${canonicalBranch}`
126657
+ };
126658
+ }
126659
+ const channel = matchChannel(identity.headBranch, channels);
126660
+ if (channel) {
126661
+ return {
126662
+ tier: "channel",
126663
+ strategy,
126664
+ identity,
126665
+ canonicalBranch,
126666
+ channel,
126667
+ reason: `branch ${identity.headBranch} matches channel ${channel.pattern}=${channel.code}`
126668
+ };
126669
+ }
126670
+ if (strategy === "preview") {
126671
+ if (identity.isForkPr) {
126672
+ return {
126673
+ tier: "gated",
126674
+ strategy,
126675
+ identity,
126676
+ canonicalBranch,
126677
+ reason: "fork PR: preview-ineligible (same-repo gate), gated instead"
126678
+ };
126679
+ }
126680
+ return {
126681
+ tier: "preview",
126682
+ strategy,
126683
+ identity,
126684
+ canonicalBranch,
126685
+ reason: `branch ${identity.headBranch} under branch-strategy preview`
126686
+ };
126687
+ }
126688
+ return {
126689
+ tier: "gated",
126690
+ strategy,
126691
+ identity,
126692
+ canonicalBranch,
126693
+ reason: `branch ${identity.headBranch} under branch-strategy publish-gate`
126694
+ };
126695
+ }
126696
+ var BRANCH_DECISION_ENV = "POSTMAN_BRANCH_DECISION";
126697
+ function serializeBranchDecision(decision) {
126698
+ return JSON.stringify(decision);
126699
+ }
126700
+ function parseBranchDecision(raw) {
126701
+ const value = clean(raw);
126702
+ if (!value) return void 0;
126703
+ let parsed;
126704
+ try {
126705
+ parsed = JSON.parse(value);
126706
+ } catch {
126707
+ throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION is not valid JSON");
126708
+ }
126709
+ const candidate = parsed;
126710
+ const tiers = ["canonical", "channel", "preview", "gated", "legacy"];
126711
+ if (!candidate || typeof candidate !== "object" || !tiers.includes(candidate.tier) || !candidate.identity) {
126712
+ throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION does not carry a valid BranchDecision");
126713
+ }
126714
+ return candidate;
126715
+ }
126716
+ function resolveEffectiveBranchDecision(options, env = process.env) {
126717
+ const inherited = parseBranchDecision(env[BRANCH_DECISION_ENV]);
126718
+ if (inherited) return inherited;
126719
+ return resolveBranchDecision(options);
126720
+ }
126721
+ var PREVIEW_SLUG_MAX = 30;
126722
+ function buildBranchSlug(rawBranch) {
126723
+ const sanitized = rawBranch.replace(/^refs\/heads\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
126724
+ const truncated = sanitized.slice(0, PREVIEW_SLUG_MAX);
126725
+ const lossy = truncated !== rawBranch.replace(/^refs\/heads\//, "");
126726
+ if (!lossy) {
126727
+ return { suffix: truncated, slug: truncated, lossy };
126728
+ }
126729
+ const hash = (0, import_node_crypto2.createHash)("sha256").update(rawBranch).digest("hex").slice(0, 6);
126730
+ return { suffix: `${truncated}-${hash}`, slug: truncated, lossy };
126731
+ }
126732
+ function previewAssetName(baseName, rawBranch) {
126733
+ return `${baseName} @${buildBranchSlug(rawBranch).suffix}`;
126734
+ }
126735
+ function channelAssetName(baseName, code) {
126736
+ return `[${code}] ${baseName}`;
126737
+ }
126738
+ var MARKER_KEY = "x-pm-onboarding";
126739
+ function parseAssetMarker(description) {
126740
+ if (!description) return void 0;
126741
+ const index = description.indexOf(`${MARKER_KEY}:`);
126742
+ if (index === -1) return void 0;
126743
+ const jsonStart = description.indexOf("{", index);
126744
+ if (jsonStart === -1) return void 0;
126745
+ let depth = 0;
126746
+ for (let i = jsonStart; i < description.length; i += 1) {
126747
+ const ch = description[i];
126748
+ if (ch === "{") depth += 1;
126749
+ else if (ch === "}") {
126750
+ depth -= 1;
126751
+ if (depth === 0) {
126752
+ try {
126753
+ const parsed = JSON.parse(description.slice(jsonStart, i + 1));
126754
+ if (parsed && typeof parsed === "object" && parsed.repo && parsed.role) return parsed;
126755
+ } catch {
126756
+ return void 0;
126757
+ }
126758
+ return void 0;
126759
+ }
126760
+ }
126761
+ }
126762
+ return void 0;
126763
+ }
126764
+
126241
126765
  // src/index.ts
126242
126766
  function parseBooleanInput(value, defaultValue) {
126243
126767
  const normalized = String(value || "").trim().toLowerCase();
@@ -126256,16 +126780,16 @@ function getInput(name, env = process.env) {
126256
126780
  const runnerRaw = runnerName === normalizedName ? void 0 : env[runnerName];
126257
126781
  const hasNormalized = normalizedRaw !== void 0;
126258
126782
  const hasRunner = runnerRaw !== void 0;
126783
+ const normalizedValue = normalizeInputValue(normalizedRaw);
126784
+ const runnerValue = normalizeInputValue(runnerRaw);
126259
126785
  if (hasNormalized && hasRunner) {
126260
- const normalizedValue = normalizeInputValue(normalizedRaw);
126261
- const runnerValue = normalizeInputValue(runnerRaw);
126262
- if (normalizedValue !== runnerValue) {
126786
+ if (normalizedValue && runnerValue && normalizedValue !== runnerValue) {
126263
126787
  throw new Error(
126264
126788
  `Conflicting values for ${name}: ${normalizedName}=${JSON.stringify(normalizedValue)} vs ${runnerName}=${JSON.stringify(runnerValue)}`
126265
126789
  );
126266
126790
  }
126267
126791
  }
126268
- return normalizeInputValue(hasNormalized ? normalizedRaw : runnerRaw);
126792
+ return normalizedValue || runnerValue;
126269
126793
  }
126270
126794
  function hasInput(name, env = process.env) {
126271
126795
  const normalizedName = `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
@@ -126324,6 +126848,17 @@ function parseCredentialPreflight(value) {
126324
126848
  `Unsupported credential-preflight "${normalized}". Supported values: ${allowed.join(", ")}`
126325
126849
  );
126326
126850
  }
126851
+ function parseBranchStrategy(value) {
126852
+ const definition = postmanRepoSyncActionContract.inputs["branch-strategy"];
126853
+ const allowed = definition.allowedValues ?? [];
126854
+ const normalized = String(value || "").trim() || (definition.default ?? "legacy");
126855
+ if (allowed.includes(normalized)) {
126856
+ return normalized;
126857
+ }
126858
+ throw new Error(
126859
+ `Unsupported branch-strategy "${normalized}". Supported values: ${allowed.join(", ")}`
126860
+ );
126861
+ }
126327
126862
  function normalizeReleaseLabel(value) {
126328
126863
  const cleaned = normalizeInputValue(value).replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
126329
126864
  return cleaned;
@@ -126368,6 +126903,7 @@ function resolveInputs(env = process.env) {
126368
126903
  smokeCollectionId: getInput("smoke-collection-id", env),
126369
126904
  contractCollectionId: getInput("contract-collection-id", env),
126370
126905
  specId: getInput("spec-id", env),
126906
+ specContentChanged: parseBooleanInput(getInput("spec-content-changed", env), true),
126371
126907
  specPath: getInput("spec-path", env),
126372
126908
  collectionSyncMode: normalizeCollectionSyncMode(getInput("collection-sync-mode", env) || "refresh"),
126373
126909
  specSyncMode: normalizeSpecSyncMode(getInput("spec-sync-mode", env) || "update"),
@@ -126390,6 +126926,10 @@ function resolveInputs(env = process.env) {
126390
126926
  postmanApiKey: getInput("postman-api-key", env),
126391
126927
  postmanAccessToken: getInput("postman-access-token", env),
126392
126928
  credentialPreflight: parseCredentialPreflight(getInput("credential-preflight", env)),
126929
+ branchStrategy: parseBranchStrategy(getInput("branch-strategy", env)),
126930
+ canonicalBranch: getInput("canonical-branch", env) || void 0,
126931
+ channels: getInput("channels", env) || void 0,
126932
+ previewTtlDays: Math.max(1, Number.parseInt(getInput("preview-ttl", env) || "30", 10) || 30),
126393
126933
  adoToken: getInput("ado-token", env) || normalizeInputValue(env.SYSTEM_ACCESSTOKEN),
126394
126934
  githubToken: getInput("github-token", env),
126395
126935
  ghFallbackToken: getInput("gh-fallback-token", env),
@@ -126416,6 +126956,40 @@ function resolveInputs(env = process.env) {
126416
126956
  postmanIapubBase: endpointProfile.iapubBaseUrl
126417
126957
  };
126418
126958
  }
126959
+ function buildBranchAssetMarker(decision, inputs, now = /* @__PURE__ */ new Date()) {
126960
+ if (decision.tier !== "preview" && decision.tier !== "channel") {
126961
+ return void 0;
126962
+ }
126963
+ const rawBranch = decision.identity.headBranch;
126964
+ const repo = inputs.repoUrl || inputs.repository;
126965
+ if (!rawBranch || !repo) {
126966
+ return void 0;
126967
+ }
126968
+ const ttlMs = inputs.previewTtlDays * 24 * 60 * 60 * 1e3;
126969
+ return {
126970
+ repo,
126971
+ rawBranch,
126972
+ sanitizedBranch: buildBranchSlug(rawBranch).suffix,
126973
+ role: decision.tier,
126974
+ headSha: decision.identity.headSha,
126975
+ createdAt: now.toISOString(),
126976
+ lastSyncedAt: now.toISOString(),
126977
+ ...decision.tier === "preview" ? { expiresAt: new Date(now.getTime() + ttlMs).toISOString() } : {}
126978
+ };
126979
+ }
126980
+ function assertBranchAssetIds(inputs, decision, branchOwnedIds = process.env.POSTMAN_BRANCH_ASSET_IDS === "owned") {
126981
+ if (decision.tier === "legacy" || decision.tier === "canonical" || branchOwnedIds) return;
126982
+ const provided = [
126983
+ ["baseline-collection-id", inputs.baselineCollectionId],
126984
+ ["smoke-collection-id", inputs.smokeCollectionId],
126985
+ ["contract-collection-id", inputs.contractCollectionId]
126986
+ ].filter(([, value]) => Boolean(value));
126987
+ if (provided.length > 0) {
126988
+ throw new Error(
126989
+ `CONTRACT_BRANCH_CANONICAL_WRITE: a ${decision.tier} repo-sync run cannot accept explicit collection IDs (${provided.map(([name]) => name).join(", ")}). Run bootstrap in the same branch-aware pipeline so it can produce branch-owned IDs.`
126990
+ );
126991
+ }
126992
+ }
126419
126993
  function buildEnvironmentValues(envName, baseUrl) {
126420
126994
  return [
126421
126995
  { key: "baseUrl", value: baseUrl, type: "default" },
@@ -126428,12 +127002,48 @@ function buildEnvironmentValues(envName, baseUrl) {
126428
127002
  ];
126429
127003
  }
126430
127004
  var LEGACY_BASELINE_COLLECTION_PREFIX = "[Baseline]";
127005
+ var RESOURCES_STATE_VERSION = 2;
127006
+ var SUPPORTED_STATE_VERSIONS = /* @__PURE__ */ new Set([1, RESOURCES_STATE_VERSION]);
127007
+ var StateUnreadableError = class extends Error {
127008
+ code = "CONTRACT_STATE_UNREADABLE";
127009
+ constructor(message) {
127010
+ super(`CONTRACT_STATE_UNREADABLE: ${message}`);
127011
+ this.name = "StateUnreadableError";
127012
+ }
127013
+ };
126431
127014
  function readResourcesState() {
127015
+ let raw;
126432
127016
  try {
126433
- return load((0, import_node_fs3.readFileSync)(".postman/resources.yaml", "utf8"));
127017
+ raw = (0, import_node_fs4.readFileSync)(".postman/resources.yaml", "utf8");
126434
127018
  } catch {
126435
127019
  return null;
126436
127020
  }
127021
+ let parsed;
127022
+ try {
127023
+ parsed = load(raw);
127024
+ } catch (error) {
127025
+ throw new StateUnreadableError(
127026
+ `.postman/resources.yaml exists but is not parseable YAML (${error instanceof Error ? error.message : String(error)}). Fix or delete the file; refusing to treat tracked state as absent.`
127027
+ );
127028
+ }
127029
+ if (parsed === null || parsed === void 0) {
127030
+ return null;
127031
+ }
127032
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
127033
+ throw new StateUnreadableError(
127034
+ ".postman/resources.yaml exists but does not contain a YAML mapping. Fix or delete the file; refusing to treat tracked state as absent."
127035
+ );
127036
+ }
127037
+ const state = parsed;
127038
+ if (state.version !== void 0 && !SUPPORTED_STATE_VERSIONS.has(Number(state.version))) {
127039
+ throw new StateUnreadableError(
127040
+ `.postman/resources.yaml declares unsupported state version ${String(state.version)} (supported: 1, ${RESOURCES_STATE_VERSION}). Upgrade the action or fix the file.`
127041
+ );
127042
+ }
127043
+ if (state.canonical && !state.cloudResources) {
127044
+ state.cloudResources = { ...state.canonical };
127045
+ }
127046
+ return state;
126437
127047
  }
126438
127048
  function findCloudResourceId(map, matcher) {
126439
127049
  if (!map) {
@@ -126468,7 +127078,7 @@ function isOpenApiSpecFile(filePath) {
126468
127078
  return false;
126469
127079
  }
126470
127080
  try {
126471
- const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8");
127081
+ const raw = (0, import_node_fs4.readFileSync)(filePath, "utf8");
126472
127082
  const parsed = filePath.endsWith(".json") ? JSON.parse(raw) : load(raw);
126473
127083
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
126474
127084
  return false;
@@ -126497,7 +127107,7 @@ function scanLocalSpecReferences(baseDir = ".") {
126497
127107
  const found = /* @__PURE__ */ new Set();
126498
127108
  const refs = [];
126499
127109
  const visit2 = (currentDir) => {
126500
- for (const entry of (0, import_node_fs3.readdirSync)(currentDir, { withFileTypes: true })) {
127110
+ for (const entry of (0, import_node_fs4.readdirSync)(currentDir, { withFileTypes: true })) {
126501
127111
  if (ignoredDirs.has(entry.name)) {
126502
127112
  continue;
126503
127113
  }
@@ -126527,7 +127137,7 @@ function resolveMappedSpecReference(explicitSpecPath, discoveredSpecs) {
126527
127137
  const normalizedExplicitPath = normalizeToPosix(explicitSpecPath.trim());
126528
127138
  if (normalizedExplicitPath) {
126529
127139
  const explicitFullPath = path3.resolve(normalizedExplicitPath);
126530
- if ((0, import_node_fs3.existsSync)(explicitFullPath) && (0, import_node_fs3.statSync)(explicitFullPath).isFile()) {
127140
+ if ((0, import_node_fs4.existsSync)(explicitFullPath) && (0, import_node_fs4.statSync)(explicitFullPath).isFile()) {
126531
127141
  return {
126532
127142
  repoRelativePath: normalizedExplicitPath,
126533
127143
  configRelativePath: normalizeToPosix(path3.join("..", normalizedExplicitPath))
@@ -126554,7 +127164,11 @@ function createOutputs(inputs) {
126554
127164
  "mock-url": "",
126555
127165
  "monitor-id": "",
126556
127166
  "repo-sync-summary-json": "{}",
126557
- "commit-sha": ""
127167
+ "commit-sha": "",
127168
+ "sync-status": "",
127169
+ "branch-decision": "",
127170
+ "spec-version-tag": "",
127171
+ "spec-version-url": ""
126558
127172
  };
126559
127173
  }
126560
127174
  function buildGhCliEnv(env, token) {
@@ -126579,7 +127193,7 @@ function buildGhCliEnv(env, token) {
126579
127193
  }
126580
127194
  return filtered;
126581
127195
  }
126582
- async function upsertEnvironments(inputs, dependencies, resourcesState) {
127196
+ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMarker) {
126583
127197
  const envUids = {
126584
127198
  ...getEnvironmentUidsFromResources(resourcesState),
126585
127199
  ...inputs.environmentUids
@@ -126589,7 +127203,6 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
126589
127203
  }
126590
127204
  for (const envName of inputs.environments) {
126591
127205
  const runtimeUrl = String(inputs.envRuntimeUrls[envName] || "").trim();
126592
- const values = buildEnvironmentValues(envName, runtimeUrl);
126593
127206
  const displayName = `${inputs.projectName} - ${envName}`;
126594
127207
  let existingUid = String(envUids[envName] || "").trim();
126595
127208
  if (!existingUid) {
@@ -126605,10 +127218,27 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
126605
127218
  }
126606
127219
  }
126607
127220
  if (existingUid) {
126608
- await dependencies.postman.updateEnvironment(existingUid, displayName, values);
127221
+ let marker = assetMarker;
127222
+ if (marker) {
127223
+ try {
127224
+ const existing = await dependencies.postman.getEnvironment(existingUid);
127225
+ const values3 = existing.data?.values ?? existing.values ?? [];
127226
+ const prior = values3.find((value) => value.key === "x-pm-onboarding")?.value;
127227
+ const priorMarker = parseAssetMarker(prior ? `x-pm-onboarding: ${prior}` : void 0);
127228
+ if (priorMarker?.repo === marker.repo && priorMarker.rawBranch === marker.rawBranch) {
127229
+ marker = { ...marker, createdAt: priorMarker.createdAt };
127230
+ }
127231
+ } catch {
127232
+ }
127233
+ }
127234
+ const values2 = buildEnvironmentValues(envName, runtimeUrl);
127235
+ if (marker) values2.push({ key: "x-pm-onboarding", value: JSON.stringify(marker), type: "default" });
127236
+ await dependencies.postman.updateEnvironment(existingUid, displayName, values2);
126609
127237
  envUids[envName] = existingUid;
126610
127238
  continue;
126611
127239
  }
127240
+ const values = buildEnvironmentValues(envName, runtimeUrl);
127241
+ if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
126612
127242
  envUids[envName] = await dependencies.postman.createEnvironment(
126613
127243
  inputs.workspaceId,
126614
127244
  displayName,
@@ -126618,7 +127248,7 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
126618
127248
  return envUids;
126619
127249
  }
126620
127250
  function ensureDir(path5) {
126621
- (0, import_node_fs3.mkdirSync)(path5, { recursive: true });
127251
+ (0, import_node_fs4.mkdirSync)(path5, { recursive: true });
126622
127252
  }
126623
127253
  function getCollectionDirectoryName(kind, projectName) {
126624
127254
  if (kind === "Baseline") {
@@ -126655,7 +127285,7 @@ function stripVolatileFields(obj) {
126655
127285
  }
126656
127286
  function writeJsonFile(path5, content, normalize3 = false) {
126657
127287
  const data = normalize3 ? stripVolatileFields(content) : content;
126658
- (0, import_node_fs3.writeFileSync)(path5, JSON.stringify(data, null, 2));
127288
+ (0, import_node_fs4.writeFileSync)(path5, JSON.stringify(data, null, 2));
126659
127289
  }
126660
127290
  function buildMappedSpecCloudKey(mappedSource, specSyncMode, releaseLabel) {
126661
127291
  if (specSyncMode !== "version") {
@@ -126679,31 +127309,30 @@ function resolveDurableWorkspaceId(options) {
126679
127309
  }
126680
127310
  return prior === candidate ? prior : void 0;
126681
127311
  }
126682
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs) {
126683
- const manifest = {};
127312
+ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs, priorState) {
127313
+ const manifest = { ...priorState ?? {} };
127314
+ delete manifest.version;
127315
+ delete manifest.workspace;
127316
+ delete manifest.localResources;
127317
+ delete manifest.cloudResources;
127318
+ delete manifest.canonical;
127319
+ manifest.version = RESOURCES_STATE_VERSION;
126684
127320
  if (workspaceId) {
126685
127321
  manifest.workspace = { id: workspaceId };
126686
127322
  }
126687
- const localResources = {};
126688
127323
  const cloudResources = {};
126689
127324
  const collectionKeys = Object.keys(collectionMap);
126690
127325
  if (collectionKeys.length > 0) {
126691
- localResources.collections = collectionKeys;
126692
127326
  cloudResources.collections = collectionMap;
126693
127327
  }
126694
127328
  const envEntries = Object.entries(envMap);
126695
127329
  if (envEntries.length > 0) {
126696
- localResources.environments = envEntries.map(
126697
- ([envName]) => `../${artifactDir}/environments/${envName}.postman_environment.json`
126698
- );
126699
127330
  cloudResources.environments = {};
126700
127331
  for (const [envName, envUid] of envEntries) {
126701
127332
  cloudResources.environments[`../${artifactDir}/environments/${envName}.postman_environment.json`] = envUid;
126702
127333
  }
126703
127334
  }
126704
- if (localSpecRefs.length > 0) {
126705
- localResources.specs = localSpecRefs;
126706
- }
127335
+ void localSpecRefs;
126707
127336
  const specs = { ...existingSpecs || {} };
126708
127337
  if (mappedSpecRef && specId) {
126709
127338
  specs[mappedSpecRef] = specId;
@@ -126711,11 +127340,8 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
126711
127340
  if (Object.keys(specs).length > 0) {
126712
127341
  cloudResources.specs = specs;
126713
127342
  }
126714
- if (Object.keys(localResources).length > 0) {
126715
- manifest.localResources = localResources;
126716
- }
126717
127343
  if (Object.keys(cloudResources).length > 0) {
126718
- manifest.cloudResources = cloudResources;
127344
+ manifest.canonical = cloudResources;
126719
127345
  }
126720
127346
  return dump(manifest, {
126721
127347
  lineWidth: -1,
@@ -126756,21 +127382,21 @@ function assertPathWithinCwd(targetPath, fieldName) {
126756
127382
  if (!rawPath || hasControlCharacter2(originalPath) || path3.isAbsolute(rawPath) || path3.win32.isAbsolute(rawPath) || segments.includes("..") || rawPath.startsWith(":") || hasControlCharacter2(rawPath)) {
126757
127383
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
126758
127384
  }
126759
- const base = (0, import_node_fs3.realpathSync)(process.cwd());
127385
+ const base = (0, import_node_fs4.realpathSync)(process.cwd());
126760
127386
  const resolved = path3.resolve(base, rawPath);
126761
127387
  const relative3 = path3.relative(base, resolved);
126762
127388
  if (relative3.startsWith("..") || path3.isAbsolute(relative3)) {
126763
127389
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
126764
127390
  }
126765
127391
  let existingPath = resolved;
126766
- while (!(0, import_node_fs3.existsSync)(existingPath)) {
127392
+ while (!(0, import_node_fs4.existsSync)(existingPath)) {
126767
127393
  const parent = path3.dirname(existingPath);
126768
127394
  if (parent === existingPath) {
126769
127395
  break;
126770
127396
  }
126771
127397
  existingPath = parent;
126772
127398
  }
126773
- const realExistingPath = (0, import_node_fs3.realpathSync)(existingPath);
127399
+ const realExistingPath = (0, import_node_fs4.realpathSync)(existingPath);
126774
127400
  const realRelative = path3.relative(base, realExistingPath);
126775
127401
  if (realRelative.startsWith("..") || path3.isAbsolute(realRelative)) {
126776
127402
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
@@ -126798,8 +127424,8 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
126798
127424
  ensureDir(specsDir);
126799
127425
  ensureDir(".postman");
126800
127426
  const globalsFilePath = `${globalsDir}/workspace.globals.yaml`;
126801
- if (!(0, import_node_fs3.existsSync)(globalsFilePath)) {
126802
- (0, import_node_fs3.writeFileSync)(globalsFilePath, "name: Globals\nvalues: []\n");
127427
+ if (!(0, import_node_fs4.existsSync)(globalsFilePath)) {
127428
+ (0, import_node_fs4.writeFileSync)(globalsFilePath, "name: Globals\nvalues: []\n");
126803
127429
  }
126804
127430
  if (inputs.generateCiWorkflow) {
126805
127431
  const ciDir = inputs.ciWorkflowPath.split("/").slice(0, -1).join("/");
@@ -126846,7 +127472,7 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
126846
127472
  workspaceLinkEnabled: inputs.workspaceLinkEnabled,
126847
127473
  workspaceLinkStatus: options.workspaceLinkStatus
126848
127474
  });
126849
- (0, import_node_fs3.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
127475
+ (0, import_node_fs4.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
126850
127476
  durableWorkspaceId,
126851
127477
  manifestCollections,
126852
127478
  envUids,
@@ -126854,10 +127480,11 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
126854
127480
  discoveredSpecs.map((spec) => spec.configRelativePath),
126855
127481
  mappedSpecCloudKey,
126856
127482
  inputs.specId || void 0,
126857
- options.existingSpecs
127483
+ options.existingSpecs,
127484
+ options.priorState
126858
127485
  ));
126859
127486
  if (mappedSpec && Object.keys(manifestCollections).length > 0) {
126860
- (0, import_node_fs3.writeFileSync)(
127487
+ (0, import_node_fs4.writeFileSync)(
126861
127488
  ".postman/workflows.yaml",
126862
127489
  buildSpecCollectionWorkflowManifest(
126863
127490
  mappedSpec.configRelativePath,
@@ -126902,19 +127529,29 @@ async function commitAndPushGeneratedFiles(inputs, dependencies) {
126902
127529
  const dir = parts.slice(0, -1).join("/");
126903
127530
  ensureDir(dir);
126904
127531
  }
126905
- (0, import_node_fs3.writeFileSync)(inputs.ciWorkflowPath, ciWorkflow);
127532
+ (0, import_node_fs4.writeFileSync)(inputs.ciWorkflowPath, ciWorkflow);
127533
+ if (inputs.provider === "github" || inputs.provider === "unknown") {
127534
+ const gcPath = ".github/workflows/postman-preview-gc.yml";
127535
+ if (inputs.ciWorkflowPath.endsWith(".github/workflows/ci.yml") || inputs.ciWorkflowPath === ".github/workflows/ci.yml") {
127536
+ ensureDir(".github/workflows");
127537
+ (0, import_node_fs4.writeFileSync)(gcPath, renderGcWorkflowTemplate());
127538
+ }
127539
+ }
126906
127540
  }
126907
127541
  if (!dependencies.repoMutation || inputs.repoWriteMode === "none") {
126908
127542
  return { commitSha: "", resolvedCurrentRef: "", pushed: false };
126909
127543
  }
126910
127544
  const provisionPath = ".github/workflows/provision.yml";
126911
- const provisionExists = inputs.provider === "github" && (0, import_node_fs3.existsSync)(provisionPath);
127545
+ const provisionExists = inputs.provider === "github" && (0, import_node_fs4.existsSync)(provisionPath);
127546
+ const gcWorkflowPath = ".github/workflows/postman-preview-gc.yml";
127547
+ const gcExists = inputs.generateCiWorkflow && (0, import_node_fs4.existsSync)(gcWorkflowPath);
126912
127548
  const stagePaths = [
126913
127549
  inputs.artifactDir,
126914
127550
  ".postman",
126915
127551
  inputs.generateCiWorkflow ? inputs.ciWorkflowPath : null,
127552
+ gcExists ? gcWorkflowPath : null,
126916
127553
  provisionExists ? provisionPath : null
126917
- ].filter((entry) => typeof entry === "string" && ((0, import_node_fs3.existsSync)(entry) || entry === provisionPath));
127554
+ ].filter((entry) => typeof entry === "string" && ((0, import_node_fs4.existsSync)(entry) || entry === provisionPath));
126918
127555
  if (stagePaths.length === 0) {
126919
127556
  dependencies.core.info("No generated repository paths were found; skipping repo mutation.");
126920
127557
  return {
@@ -126962,6 +127599,31 @@ async function runRepoSync(inputs, dependencies) {
126962
127599
  }
126963
127600
  }
126964
127601
  async function runRepoSyncInner(inputs, dependencies) {
127602
+ const branchDecision = decideBranchTier(inputs);
127603
+ assertBranchAssetIds(inputs, branchDecision);
127604
+ const isCanonicalWriter = branchDecision.tier === "legacy" || branchDecision.tier === "canonical";
127605
+ if (!isCanonicalWriter) {
127606
+ if (branchDecision.tier === "preview" && branchDecision.identity.headBranch) {
127607
+ inputs = {
127608
+ ...inputs,
127609
+ projectName: previewAssetName(inputs.projectName, branchDecision.identity.headBranch),
127610
+ workspaceLinkEnabled: false,
127611
+ environmentSyncEnabled: false,
127612
+ repoWriteMode: "none"
127613
+ };
127614
+ } else if (branchDecision.tier === "channel" && branchDecision.channel) {
127615
+ inputs = {
127616
+ ...inputs,
127617
+ projectName: channelAssetName(inputs.projectName, branchDecision.channel.code),
127618
+ workspaceLinkEnabled: false,
127619
+ environmentSyncEnabled: false,
127620
+ repoWriteMode: "none"
127621
+ };
127622
+ }
127623
+ dependencies.core.info(
127624
+ `branch-aware sync: ${branchDecision.tier} run \u2014 branch-scoped asset set "${inputs.projectName}", no workspace repo-link mutation, no state write-back`
127625
+ );
127626
+ }
126965
127627
  const outputs = createOutputs(inputs);
126966
127628
  const versionRequested = inputs.collectionSyncMode === "version" || inputs.specSyncMode === "version";
126967
127629
  const releaseLabel = deriveReleaseLabel(inputs);
@@ -126969,7 +127631,8 @@ async function runRepoSyncInner(inputs, dependencies) {
126969
127631
  throw new Error("release-label is required when collection-sync-mode or spec-sync-mode is version");
126970
127632
  }
126971
127633
  const assetProjectName = createAssetProjectName(inputs, releaseLabel);
126972
- const resourcesState = readResourcesState();
127634
+ const trackedState = readResourcesState();
127635
+ const resourcesState = isCanonicalWriter ? trackedState : trackedState?.workspace ? { workspace: trackedState.workspace } : null;
126973
127636
  if (resourcesState) {
126974
127637
  if (!inputs.workspaceId && resourcesState.workspace?.id) {
126975
127638
  inputs.workspaceId = resourcesState.workspace.id;
@@ -126995,7 +127658,8 @@ async function runRepoSyncInner(inputs, dependencies) {
126995
127658
  }
126996
127659
  }
126997
127660
  }
126998
- const envUids = await upsertEnvironments(inputs, dependencies, resourcesState);
127661
+ const branchAssetMarker = buildBranchAssetMarker(branchDecision, inputs);
127662
+ const envUids = await upsertEnvironments(inputs, dependencies, resourcesState, branchAssetMarker);
126999
127663
  outputs["environment-uids-json"] = JSON.stringify(envUids);
127000
127664
  if (inputs.environmentSyncEnabled && inputs.workspaceId && dependencies.internalIntegration) {
127001
127665
  const associations = Object.entries(envUids).map(([envName, envUid]) => ({
@@ -127015,6 +127679,19 @@ async function runRepoSyncInner(inputs, dependencies) {
127015
127679
  `System environment association failed: ${error instanceof Error ? error.message : String(error)}`
127016
127680
  );
127017
127681
  }
127682
+ } else if (Object.keys(envUids).length > 0) {
127683
+ const configuredKeys = Object.keys(inputs.systemEnvMap).filter(
127684
+ (key) => Boolean(String(inputs.systemEnvMap[key] ?? "").trim())
127685
+ );
127686
+ if (configuredKeys.length === 0) {
127687
+ dependencies.core.warning(
127688
+ 'system-env-map-json is empty while environment-sync-enabled is true. Workspace\u2194git linking still registers the service in API Catalog, but Catalog system-environment filters (Prod/Stage/Dev) hide services until Postman environments are associated. Pass system-env-map-json like {"prod":"<system-env-uuid>"} or clear the Catalog system-environment filter to see the service.'
127689
+ );
127690
+ } else {
127691
+ dependencies.core.warning(
127692
+ `system-env-map-json keys (${configuredKeys.join(", ")}) did not match any synced environment (${Object.keys(envUids).join(", ")}). No system-environment associations were made; Catalog system-environment filters may hide this service.`
127693
+ );
127694
+ }
127018
127695
  }
127019
127696
  }
127020
127697
  if (inputs.workspaceId && inputs.baselineCollectionId && Object.keys(envUids).length > 0) {
@@ -127038,11 +127715,23 @@ async function runRepoSyncInner(inputs, dependencies) {
127038
127715
  }
127039
127716
  }
127040
127717
  if (!resolvedMockUrl) {
127041
- const mock = await dependencies.postman.createMock(
127042
- inputs.workspaceId,
127043
- mockName,
127044
- inputs.baselineCollectionId,
127045
- mockEnvUid
127718
+ const mock = await retry(
127719
+ () => dependencies.postman.createMock(
127720
+ inputs.workspaceId,
127721
+ mockName,
127722
+ inputs.baselineCollectionId,
127723
+ mockEnvUid
127724
+ ),
127725
+ {
127726
+ maxAttempts: 3,
127727
+ delayMs: 2e3,
127728
+ backoffMultiplier: 2,
127729
+ onRetry: ({ attempt, error }) => {
127730
+ dependencies.core.warning(
127731
+ `Mock create attempt ${attempt} failed (${error instanceof Error ? error.message : String(error)}); retrying`
127732
+ );
127733
+ }
127734
+ }
127046
127735
  );
127047
127736
  resolvedMockUrl = mock.url;
127048
127737
  dependencies.core.info(`Created new mock: ${resolvedMockUrl}`);
@@ -127106,18 +127795,24 @@ async function runRepoSyncInner(inputs, dependencies) {
127106
127795
  inputs.repoUrl
127107
127796
  );
127108
127797
  outputs["workspace-link-status"] = "success";
127798
+ dependencies.core.info(
127799
+ `workspace-link-status=success workspace-id=${inputs.workspaceId} repo=${inputs.repoUrl}`
127800
+ );
127109
127801
  } catch (error) {
127110
127802
  outputs["workspace-link-status"] = "failed";
127111
- dependencies.core.warning(
127112
- `Workspace link failed: ${error instanceof Error ? error.message : String(error)}`
127113
- );
127803
+ const message = `Workspace link failed: ${error instanceof Error ? error.message : String(error)}`;
127804
+ if (branchDecision.tier === "canonical") {
127805
+ throw new Error(message, { cause: error });
127806
+ }
127807
+ dependencies.core.warning(message);
127114
127808
  }
127115
127809
  }
127116
127810
  await exportArtifacts(inputs, dependencies, envUids, assetProjectName, {
127117
127811
  workspaceLinkStatus: outputs["workspace-link-status"],
127118
127812
  priorWorkspaceId: resourcesState?.workspace?.id,
127119
127813
  existingSpecs: resourcesState?.cloudResources?.specs,
127120
- releaseLabel
127814
+ releaseLabel,
127815
+ priorState: resourcesState
127121
127816
  });
127122
127817
  const commit = await commitAndPushGeneratedFiles(inputs, dependencies);
127123
127818
  outputs["commit-sha"] = commit.commitSha;
@@ -127125,6 +127820,38 @@ async function runRepoSyncInner(inputs, dependencies) {
127125
127820
  outputs["resolved-current-ref"] = commit.resolvedCurrentRef;
127126
127821
  }
127127
127822
  outputs["repo-sync-summary-json"] = createRepoSummary(outputs, envUids, commit.pushed);
127823
+ if (branchDecision.tier === "canonical" && inputs.specId && inputs.specContentChanged !== false && dependencies.postman.tagSpecVersion) {
127824
+ const shortSha = (branchDecision.identity.headSha ?? "").slice(0, 7);
127825
+ const tagName = inputs.releaseLabel ? `${inputs.releaseLabel}${shortSha ? ` (${shortSha})` : ""}` : shortSha || `sync-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
127826
+ try {
127827
+ const tag = await dependencies.postman.tagSpecVersion(inputs.specId, tagName);
127828
+ outputs["spec-version-tag"] = tag.name || tagName;
127829
+ if (tag.id) {
127830
+ outputs["spec-version-url"] = `https://web.postman.co/workspace/${encodeURIComponent(inputs.workspaceId)}/specification/${encodeURIComponent(inputs.specId)}?tagId=${encodeURIComponent(tag.id)}&versionLabel=${encodeURIComponent(tag.name || tagName)}`;
127831
+ }
127832
+ dependencies.core.info(`Tagged spec version at finalize: ${tag.name || tagName}`);
127833
+ } catch (error) {
127834
+ const status = error.status;
127835
+ if (status === 409) {
127836
+ const tags = await dependencies.postman.listSpecVersionTags?.(inputs.specId).catch(() => []) ?? [];
127837
+ const latest = tags[0];
127838
+ if (latest && (latest.name === tagName || /\([0-9a-f]{7}\)$/.test(latest.name) || /^[0-9a-f]{7}$/.test(latest.name))) {
127839
+ outputs["spec-version-tag"] = latest.name;
127840
+ outputs["spec-version-url"] = `https://web.postman.co/workspace/${encodeURIComponent(inputs.workspaceId)}/specification/${encodeURIComponent(inputs.specId)}?tagId=${encodeURIComponent(latest.id)}&versionLabel=${encodeURIComponent(latest.name)}`;
127841
+ dependencies.core.info(`Latest changelog group already tagged as "${latest.name}"; adopting.`);
127842
+ } else {
127843
+ dependencies.core.warning("Latest changelog group already carries a hand-applied tag; leaving it in place.");
127844
+ }
127845
+ } else {
127846
+ dependencies.core.warning(`Spec version tagging failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);
127847
+ }
127848
+ }
127849
+ }
127850
+ if (inputs.branchStrategy !== "legacy" || process.env[BRANCH_DECISION_ENV]) {
127851
+ const decision = decideBranchTier(inputs);
127852
+ outputs["sync-status"] = "synced";
127853
+ outputs["branch-decision"] = serializeBranchDecision(decision);
127854
+ }
127128
127855
  for (const [name, value] of Object.entries(outputs)) {
127129
127856
  dependencies.core.setOutput(name, value);
127130
127857
  }
@@ -127314,7 +128041,19 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
127314
128041
  listMonitors: gatewayAssets.listMonitors.bind(gatewayAssets),
127315
128042
  monitorExists: gatewayAssets.monitorExists.bind(gatewayAssets),
127316
128043
  findMonitorByCollection: gatewayAssets.findMonitorByCollection.bind(gatewayAssets),
127317
- runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets)
128044
+ runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets),
128045
+ listEnvironments: gatewayAssets.listEnvironments.bind(gatewayAssets),
128046
+ // GC path (preview/channel retention machine — lib/repo/preview-gc.ts).
128047
+ deleteEnvironment: gatewayAssets.deleteEnvironment.bind(gatewayAssets),
128048
+ deleteMock: gatewayAssets.deleteMock.bind(gatewayAssets),
128049
+ deleteMonitor: gatewayAssets.deleteMonitor.bind(gatewayAssets),
128050
+ deleteCollection: gatewayAssets.deleteCollection.bind(gatewayAssets),
128051
+ listSpecifications: gatewayAssets.listSpecifications.bind(gatewayAssets),
128052
+ getSpecContent: gatewayAssets.getSpecContent.bind(gatewayAssets),
128053
+ listSpecCollections: gatewayAssets.listSpecCollections.bind(gatewayAssets),
128054
+ deleteSpec: gatewayAssets.deleteSpec.bind(gatewayAssets),
128055
+ tagSpecVersion: gatewayAssets.tagSpecVersion.bind(gatewayAssets),
128056
+ listSpecVersionTags: gatewayAssets.listSpecVersionTags.bind(gatewayAssets)
127318
128057
  };
127319
128058
  const repoMutation = repository && (inputs.repoWriteMode === "commit-only" || inputs.repoWriteMode === "commit-and-push") ? new RepoMutationService({
127320
128059
  provider: inputs.provider,
@@ -127351,6 +128090,400 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
127351
128090
  repoMutation
127352
128091
  };
127353
128092
  }
128093
+ function decideBranchTier(inputs, env = process.env) {
128094
+ return resolveEffectiveBranchDecision(
128095
+ {
128096
+ strategy: inputs.branchStrategy,
128097
+ identity: resolveBranchIdentity(env, { defaultBranch: inputs.canonicalBranch }),
128098
+ canonicalBranch: inputs.canonicalBranch,
128099
+ channels: parseChannelRules(inputs.channels)
128100
+ },
128101
+ env
128102
+ );
128103
+ }
128104
+ function runGatedSkip(inputs, decision, actionCore) {
128105
+ actionCore.info(
128106
+ `branch-aware sync: gated run (${decision.reason}) \u2014 repo-sync skipped, zero workspace writes`
128107
+ );
128108
+ const outputs = createOutputs(inputs);
128109
+ outputs["sync-status"] = "skipped-branch-gate";
128110
+ outputs["branch-decision"] = serializeBranchDecision(decision);
128111
+ outputs["repo-sync-summary-json"] = JSON.stringify({
128112
+ status: "skipped-branch-gate",
128113
+ reason: decision.reason
128114
+ });
128115
+ for (const [name, value] of Object.entries(outputs)) {
128116
+ actionCore.setOutput(name, value);
128117
+ }
128118
+ return outputs;
128119
+ }
128120
+
128121
+ // src/lib/repo/preview-gc.ts
128122
+ function looksGenerated(name) {
128123
+ return / @[A-Za-z0-9._-]+/.test(name) || /^\[[A-Z][A-Z0-9]*\] /.test(name);
128124
+ }
128125
+ function resolveMarker(candidate) {
128126
+ return candidate.marker ?? parseAssetMarker(candidate.description);
128127
+ }
128128
+ function stampChannelRetirement(marker, reason, now, previewTtlDays) {
128129
+ const deleteAfter = new Date(now.getTime() + previewTtlDays * 24 * 60 * 60 * 1e3);
128130
+ return {
128131
+ ...marker,
128132
+ retirementDetectedAt: now.toISOString(),
128133
+ retirementReason: reason,
128134
+ deleteAfter: deleteAfter.toISOString()
128135
+ };
128136
+ }
128137
+ function clearChannelRetirement(marker) {
128138
+ const next = { ...marker };
128139
+ delete next.retirementDetectedAt;
128140
+ delete next.retirementReason;
128141
+ delete next.deleteAfter;
128142
+ return next;
128143
+ }
128144
+ function decideRetention(candidate, context) {
128145
+ const marker = resolveMarker(candidate);
128146
+ if (!marker) {
128147
+ if (looksGenerated(candidate.name)) {
128148
+ return { action: "orphan-audit", reason: "generated-looking name with missing or invalid marker" };
128149
+ }
128150
+ return { action: "stranger", reason: "no marker; not our asset" };
128151
+ }
128152
+ if (marker.repo !== context.repo) {
128153
+ return { action: "stranger", reason: `marker repo ${marker.repo} does not match ${context.repo}` };
128154
+ }
128155
+ if (context.triggerGeneration && marker.createdAt) {
128156
+ const created = new Date(marker.createdAt);
128157
+ if (!Number.isNaN(created.getTime()) && created.getTime() > context.triggerGeneration.getTime()) {
128158
+ return { action: "retain", reason: "marker generation is newer than the GC trigger (name reuse)" };
128159
+ }
128160
+ }
128161
+ if (marker.role === "channel") {
128162
+ if (marker.retirementReason && marker.deleteAfter) {
128163
+ const existence3 = context.branchExists(marker.rawBranch);
128164
+ const mapped = context.channelMapped?.(marker.rawBranch);
128165
+ const restored = marker.retirementReason === "branch-deleted" ? existence3 === "exists" && mapped !== false : existence3 === "exists" && mapped === true;
128166
+ if (restored) {
128167
+ return { action: "restore", reason: "retired channel branch and mapping are active again", branchProbe: "exists" };
128168
+ }
128169
+ const deleteAfter = new Date(marker.deleteAfter);
128170
+ if (!Number.isNaN(deleteAfter.getTime()) && context.now.getTime() >= deleteAfter.getTime()) {
128171
+ return { action: "delete", reason: `retired channel set past deleteAfter (${marker.retirementReason})` };
128172
+ }
128173
+ return { action: "retain", reason: "retired channel set inside deleteAfter window" };
128174
+ }
128175
+ const existence2 = context.branchExists(marker.rawBranch);
128176
+ if (existence2 === "deleted") {
128177
+ return { action: "retire", reason: "channel branch deleted; start retirement window", branchProbe: "deleted", retirementReason: "branch-deleted" };
128178
+ }
128179
+ if (context.channelMapped?.(marker.rawBranch) === false) {
128180
+ return { action: "retire", reason: "channel mapping removed; start retirement window", branchProbe: existence2, retirementReason: "mapping-removed" };
128181
+ }
128182
+ return { action: "retain", reason: "active channel set (channels are never TTL-swept)", branchProbe: existence2 };
128183
+ }
128184
+ if (context.onlyBranch !== void 0) {
128185
+ if (marker.rawBranch !== context.onlyBranch && marker.sanitizedBranch !== context.onlyBranch) {
128186
+ return { action: "retain", reason: "outside manual --branch scope" };
128187
+ }
128188
+ return { action: "delete", reason: `manual gc --branch ${context.onlyBranch}` };
128189
+ }
128190
+ if (context.allPreviews) {
128191
+ return { action: "delete", reason: "manual gc --all-previews" };
128192
+ }
128193
+ const existence = context.branchExists(marker.rawBranch);
128194
+ if (existence === "exists") {
128195
+ return { action: "retain", reason: "branch still exists", branchProbe: "exists" };
128196
+ }
128197
+ if (existence === "deleted") {
128198
+ return { action: "delete", reason: "branch deleted", branchProbe: "deleted" };
128199
+ }
128200
+ if (marker.expiresAt) {
128201
+ const expires = new Date(marker.expiresAt);
128202
+ if (!Number.isNaN(expires.getTime()) && context.now.getTime() >= expires.getTime()) {
128203
+ return { action: "delete", reason: "sliding TTL expired", branchProbe: "skipped" };
128204
+ }
128205
+ }
128206
+ return { action: "retain", reason: "branch existence unknown and TTL not expired", branchProbe: "skipped" };
128207
+ }
128208
+ async function runPreviewGc(options) {
128209
+ const { context, candidates, deleters, dryRun } = options;
128210
+ const log = options.log ?? (() => void 0);
128211
+ const summary2 = {
128212
+ repo: context.repo,
128213
+ scannedAt: context.now.toISOString(),
128214
+ degraded: Boolean(options.degraded),
128215
+ counts: { delete: 0, retire: 0, restore: 0, retain: 0, stranger: 0, "orphan-audit": 0, errors: 0 },
128216
+ entries: []
128217
+ };
128218
+ for (const candidate of candidates) {
128219
+ const decision = decideRetention(candidate, context);
128220
+ const entry = {
128221
+ kind: candidate.kind,
128222
+ uid: candidate.uid,
128223
+ name: candidate.name,
128224
+ action: decision.action,
128225
+ reason: decision.reason
128226
+ };
128227
+ summary2.counts[decision.action] += 1;
128228
+ if (decision.action === "retire" && !dryRun && candidate.kind === "environment") {
128229
+ const retire = options.retirers?.environment;
128230
+ if (!retire) {
128231
+ entry.error = "no channel retirement writer wired for environment";
128232
+ summary2.counts.errors += 1;
128233
+ } else {
128234
+ try {
128235
+ await retire(candidate, decision.retirementReason ?? "branch-deleted");
128236
+ log(`gc: retired channel ${candidate.name} (${candidate.uid}) \u2014 ${decision.reason}`);
128237
+ } catch (error) {
128238
+ entry.error = error instanceof Error ? error.message : String(error);
128239
+ summary2.counts.errors += 1;
128240
+ }
128241
+ }
128242
+ } else if (decision.action === "restore" && !dryRun && candidate.kind === "environment") {
128243
+ const restore = options.retirers?.restoreEnvironment;
128244
+ if (!restore) {
128245
+ entry.error = "no channel retirement restore writer wired for environment";
128246
+ summary2.counts.errors += 1;
128247
+ } else {
128248
+ try {
128249
+ await restore(candidate);
128250
+ log(`gc: restored channel ${candidate.name} (${candidate.uid})`);
128251
+ } catch (error) {
128252
+ entry.error = error instanceof Error ? error.message : String(error);
128253
+ summary2.counts.errors += 1;
128254
+ }
128255
+ }
128256
+ } else if (decision.action === "delete" && !dryRun) {
128257
+ const remove = deleters[candidate.kind];
128258
+ if (!remove) {
128259
+ entry.error = `no deleter wired for kind ${candidate.kind}`;
128260
+ summary2.counts.errors += 1;
128261
+ } else {
128262
+ try {
128263
+ await remove(candidate.uid);
128264
+ entry.deleted = true;
128265
+ log(`gc: deleted ${candidate.kind} ${candidate.name} (${candidate.uid}) \u2014 ${decision.reason}`);
128266
+ } catch (error) {
128267
+ entry.error = error instanceof Error ? error.message : String(error);
128268
+ summary2.counts.errors += 1;
128269
+ log(`gc: FAILED deleting ${candidate.kind} ${candidate.name} (${candidate.uid}): ${entry.error}`);
128270
+ }
128271
+ }
128272
+ } else if (decision.action === "delete" && dryRun) {
128273
+ log(`gc (dry-run): would delete ${candidate.kind} ${candidate.name} (${candidate.uid}) \u2014 ${decision.reason}`);
128274
+ }
128275
+ summary2.entries.push(entry);
128276
+ }
128277
+ return summary2;
128278
+ }
128279
+ function renderGcSummary(summary2) {
128280
+ const lines = [];
128281
+ lines.push(`Preview GC summary for ${summary2.repo} at ${summary2.scannedAt}${summary2.degraded ? " (degraded: branch probes skipped)" : ""}`);
128282
+ lines.push(
128283
+ ` deleted=${summary2.counts.delete} retired=${summary2.counts.retire} restored=${summary2.counts.restore} retained=${summary2.counts.retain} strangers=${summary2.counts.stranger} orphans=${summary2.counts["orphan-audit"]} errors=${summary2.counts.errors}`
128284
+ );
128285
+ for (const entry of summary2.entries) {
128286
+ if (entry.action === "retain" || entry.action === "stranger") continue;
128287
+ const status = entry.error ? `ERROR: ${entry.error}` : entry.deleted ? "deleted" : entry.action;
128288
+ lines.push(` [${entry.kind}] ${entry.name} (${entry.uid}): ${status} \u2014 ${entry.reason}`);
128289
+ }
128290
+ return lines.join("\n");
128291
+ }
128292
+
128293
+ // src/lib/repo/gc-runner.ts
128294
+ var GC_MARKER_ENV_KEY = "x-pm-onboarding";
128295
+ async function inventoryRemoteBranches(exec) {
128296
+ try {
128297
+ const result = await exec.getExecOutput("git", ["ls-remote", "--heads", "origin"], {
128298
+ ignoreReturnCode: true
128299
+ });
128300
+ if (result.exitCode !== 0) {
128301
+ return void 0;
128302
+ }
128303
+ const branches = /* @__PURE__ */ new Set();
128304
+ for (const line of result.stdout.split("\n")) {
128305
+ const match = line.match(/\trefs\/heads\/(.+)$/);
128306
+ if (match) branches.add(match[1].trim());
128307
+ }
128308
+ return branches;
128309
+ } catch {
128310
+ return void 0;
128311
+ }
128312
+ }
128313
+ function markerFromEnvironment(envelope) {
128314
+ const record = envelope && typeof envelope === "object" ? envelope : null;
128315
+ const data = record && typeof record.data === "object" && record.data !== null ? record.data : record;
128316
+ const values = data && Array.isArray(data.values) ? data.values : [];
128317
+ for (const value of values) {
128318
+ if (String(value?.key ?? "") === GC_MARKER_ENV_KEY) {
128319
+ return parseAssetMarker(`${GC_MARKER_ENV_KEY}: ${String(value?.value ?? "")}`);
128320
+ }
128321
+ }
128322
+ return void 0;
128323
+ }
128324
+ function environmentValues(envelope) {
128325
+ const record = envelope && typeof envelope === "object" ? envelope : null;
128326
+ const data = record && typeof record.data === "object" && record.data !== null ? record.data : record;
128327
+ const values = data && Array.isArray(data.values) ? data.values : [];
128328
+ return values.map((raw) => {
128329
+ const value = raw && typeof raw === "object" ? raw : {};
128330
+ return {
128331
+ key: String(value.key ?? ""),
128332
+ value: String(value.value ?? ""),
128333
+ enabled: value.enabled !== false,
128334
+ type: String(value.type ?? "default")
128335
+ };
128336
+ });
128337
+ }
128338
+ function markerFromSpecContent(content) {
128339
+ if (!content) return void 0;
128340
+ try {
128341
+ const parsed = load(content);
128342
+ const marker = parsed?.["x-postman-onboarding"];
128343
+ return marker && typeof marker === "object" ? parseAssetMarker(`${GC_MARKER_ENV_KEY}: ${JSON.stringify(marker)}`) : void 0;
128344
+ } catch {
128345
+ return void 0;
128346
+ }
128347
+ }
128348
+ function isGcCandidateName(name) {
128349
+ return / @[A-Za-z0-9._-]+/.test(name) || /^\[[A-Z][A-Z0-9]*\] /.test(name);
128350
+ }
128351
+ async function collectGcCandidates(postman, workspaceId) {
128352
+ const candidates = [];
128353
+ const environments = await postman.listEnvironments(workspaceId);
128354
+ for (const env of environments) {
128355
+ if (!isGcCandidateName(env.name)) continue;
128356
+ let marker;
128357
+ try {
128358
+ marker = markerFromEnvironment(await postman.getEnvironment(env.uid));
128359
+ } catch {
128360
+ marker = void 0;
128361
+ }
128362
+ candidates.push({ kind: "environment", uid: env.uid, name: env.name, marker });
128363
+ }
128364
+ const mocks = await postman.listMocks();
128365
+ for (const mock of mocks) {
128366
+ if (!isGcCandidateName(mock.name)) continue;
128367
+ const envMatch = candidates.find(
128368
+ (entry) => entry.kind === "environment" && entry.uid === mock.environment
128369
+ );
128370
+ candidates.push({ kind: "mock", uid: mock.uid, name: mock.name, marker: envMatch?.marker });
128371
+ }
128372
+ const monitors = await postman.listMonitors();
128373
+ for (const monitor of monitors) {
128374
+ if (!isGcCandidateName(monitor.name)) continue;
128375
+ const envMatch = candidates.find(
128376
+ (entry) => entry.kind === "environment" && entry.uid === monitor.environmentUid
128377
+ );
128378
+ candidates.push({ kind: "monitor", uid: monitor.uid, name: monitor.name, marker: envMatch?.marker });
128379
+ }
128380
+ const ownedCollections = /* @__PURE__ */ new Map();
128381
+ for (const mock of mocks) {
128382
+ const marker = candidates.find((entry) => entry.kind === "environment" && entry.uid === mock.environment)?.marker;
128383
+ if (marker && mock.collection) ownedCollections.set(mock.collection, { name: `${mock.name} collection`, marker });
128384
+ }
128385
+ for (const monitor of monitors) {
128386
+ const marker = candidates.find((entry) => entry.kind === "environment" && entry.uid === monitor.environmentUid)?.marker;
128387
+ if (marker && monitor.collectionUid) ownedCollections.set(monitor.collectionUid, { name: `${monitor.name} collection`, marker });
128388
+ }
128389
+ for (const [uid, collection] of ownedCollections) {
128390
+ candidates.push({ kind: "collection", uid, name: collection.name, marker: collection.marker });
128391
+ }
128392
+ const specifications = await postman.listSpecifications(workspaceId);
128393
+ for (const spec of specifications) {
128394
+ if (!isGcCandidateName(spec.name)) continue;
128395
+ let marker;
128396
+ try {
128397
+ marker = markerFromSpecContent(await postman.getSpecContent(spec.uid));
128398
+ } catch {
128399
+ marker = void 0;
128400
+ }
128401
+ candidates.push({ kind: "spec", uid: spec.uid, name: spec.name, marker });
128402
+ if (marker) {
128403
+ try {
128404
+ for (const collection of await postman.listSpecCollections(spec.uid)) {
128405
+ if (!ownedCollections.has(collection.uid)) {
128406
+ candidates.push({ kind: "collection", uid: collection.uid, name: collection.name || `${spec.name} collection`, marker });
128407
+ }
128408
+ }
128409
+ } catch {
128410
+ }
128411
+ }
128412
+ }
128413
+ return candidates;
128414
+ }
128415
+ async function runGc(options) {
128416
+ const now = options.now ?? /* @__PURE__ */ new Date();
128417
+ const log = options.log ?? (() => void 0);
128418
+ const remoteBranches = options.onlyBranch || options.allPreviews ? void 0 : await inventoryRemoteBranches(options.exec);
128419
+ const degraded = remoteBranches === void 0 && !options.onlyBranch && !options.allPreviews;
128420
+ if (degraded) {
128421
+ log("gc: branch inventory unavailable (credential absent/denied?) \u2014 degraded sweep, TTL only");
128422
+ }
128423
+ const branchExists = (rawBranch) => {
128424
+ if (!remoteBranches) return "unknown";
128425
+ return remoteBranches.has(rawBranch) ? "exists" : "deleted";
128426
+ };
128427
+ const channelRules = options.channels === void 0 ? void 0 : parseChannelRules(options.channels);
128428
+ const channelMapped = channelRules ? (rawBranch) => channelRules.some(
128429
+ (rule) => rule.pattern.endsWith("*") ? rawBranch.startsWith(rule.pattern.slice(0, -1)) : rawBranch === rule.pattern
128430
+ ) : void 0;
128431
+ const candidates = await collectGcCandidates(options.postman, options.workspaceId);
128432
+ const retiredChannels = candidates.filter((candidate) => candidate.kind === "environment" && candidate.marker?.role === "channel" && candidate.marker.retirementReason).map((candidate) => candidate.marker);
128433
+ for (const candidate of candidates) {
128434
+ const marker = candidate.marker;
128435
+ if (!marker || marker.role !== "channel" || marker.retirementReason) continue;
128436
+ const retired = retiredChannels.find((entry) => entry.repo === marker.repo && entry.rawBranch === marker.rawBranch);
128437
+ if (retired) candidate.marker = retired;
128438
+ }
128439
+ return runPreviewGc({
128440
+ context: {
128441
+ repo: options.repo,
128442
+ now,
128443
+ branchExists,
128444
+ channelMapped,
128445
+ onlyBranch: options.onlyBranch,
128446
+ allPreviews: options.allPreviews
128447
+ },
128448
+ candidates,
128449
+ deleters: {
128450
+ environment: (uid) => options.postman.deleteEnvironment(uid),
128451
+ mock: (uid) => options.postman.deleteMock(uid),
128452
+ monitor: (uid) => options.postman.deleteMonitor(uid),
128453
+ collection: (uid) => options.postman.deleteCollection(uid),
128454
+ spec: (uid) => options.postman.deleteSpec(uid)
128455
+ },
128456
+ retirers: {
128457
+ environment: async (candidate, reason) => {
128458
+ if (!candidate.marker) return;
128459
+ const envelope = await options.postman.getEnvironment(candidate.uid);
128460
+ const values = environmentValues(envelope);
128461
+ const retired = stampChannelRetirement(
128462
+ candidate.marker,
128463
+ reason,
128464
+ now,
128465
+ options.previewTtlDays ?? 30
128466
+ );
128467
+ const markerValue = values.find((value) => value.key === GC_MARKER_ENV_KEY);
128468
+ if (markerValue) markerValue.value = JSON.stringify(retired);
128469
+ else values.push({ key: GC_MARKER_ENV_KEY, value: JSON.stringify(retired), enabled: true, type: "default" });
128470
+ await options.postman.updateEnvironment(candidate.uid, candidate.name, values);
128471
+ },
128472
+ restoreEnvironment: async (candidate) => {
128473
+ if (!candidate.marker) return;
128474
+ const envelope = await options.postman.getEnvironment(candidate.uid);
128475
+ const values = environmentValues(envelope);
128476
+ const restored = clearChannelRetirement(candidate.marker);
128477
+ const markerValue = values.find((value) => value.key === GC_MARKER_ENV_KEY);
128478
+ if (markerValue) markerValue.value = JSON.stringify(restored);
128479
+ await options.postman.updateEnvironment(candidate.uid, candidate.name, values);
128480
+ }
128481
+ },
128482
+ degraded,
128483
+ dryRun: options.dryRun,
128484
+ log
128485
+ });
128486
+ }
127354
128487
 
127355
128488
  // src/cli.ts
127356
128489
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
@@ -127399,15 +128532,25 @@ var CLI_INPUT_NAMES = [
127399
128532
  "ssl-client-passphrase",
127400
128533
  "ssl-extra-ca-certs",
127401
128534
  "spec-id",
128535
+ "spec-content-changed",
127402
128536
  "spec-path",
127403
128537
  "team-id",
127404
128538
  "postman-region",
127405
- "postman-stack"
128539
+ "postman-stack",
128540
+ "branch-strategy",
128541
+ "canonical-branch",
128542
+ "channels",
128543
+ "preview-ttl"
127406
128544
  ];
127407
128545
  var HELP_TEXT = `Usage: postman-repo-sync [options]
127408
128546
 
127409
128547
  Sync Postman artifacts into a git repository.
127410
128548
 
128549
+ Subcommands:
128550
+ gc [--branch <name> | --all-previews] [--dry-run]
128551
+ Garbage-collect preview/channel asset sets
128552
+ (marker-guarded; strangers are never deleted)
128553
+
127411
128554
  Options:
127412
128555
  --help Show this help and exit
127413
128556
  --version Show version and exit
@@ -127501,7 +128644,7 @@ function resolvePackageVersion() {
127501
128644
  candidates.push(import_node_path3.default.join(process.cwd(), "package.json"));
127502
128645
  for (const packageJsonPath of candidates) {
127503
128646
  try {
127504
- const packageJson = JSON.parse((0, import_node_fs4.readFileSync)(packageJsonPath, "utf8"));
128647
+ const packageJson = JSON.parse((0, import_node_fs5.readFileSync)(packageJsonPath, "utf8"));
127505
128648
  if (packageJson.name === "@postman-cse/onboarding-repo-sync" && packageJson.version) {
127506
128649
  return String(packageJson.version).trim();
127507
128650
  }
@@ -127666,9 +128809,22 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
127666
128809
  return;
127667
128810
  }
127668
128811
  const env = runtime.env ?? process.env;
128812
+ if (argv[0] === "gc") {
128813
+ await runGcCommand(argv.slice(1), env, writeStdout);
128814
+ return;
128815
+ }
127669
128816
  const config = parseCliArgs(argv, env);
127670
128817
  const inputs = resolveInputs(config.inputEnv);
127671
128818
  const reporter = new ConsoleReporter();
128819
+ const branchDecision = decideBranchTier(inputs, config.inputEnv);
128820
+ if (branchDecision.tier === "gated") {
128821
+ const result2 = runGatedSkip(inputs, branchDecision, reporter);
128822
+ await writeOptionalFile(config.resultJsonPath, JSON.stringify(result2, null, 2));
128823
+ await writeOptionalFile(config.dotenvPath, toDotenv(result2));
128824
+ writeStdout(`${JSON.stringify(result2, null, 2)}
128825
+ `);
128826
+ return;
128827
+ }
127672
128828
  await mintAccessTokenIfNeeded(inputs, reporter);
127673
128829
  const initialMasker = createSecretMasker([
127674
128830
  inputs.postmanApiKey,
@@ -127719,6 +128875,80 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
127719
128875
  writeStdout(`${JSON.stringify(result, null, 2)}
127720
128876
  `);
127721
128877
  }
128878
+ async function runGcCommand(argv, env, writeStdout) {
128879
+ let onlyBranch;
128880
+ let allPreviews = false;
128881
+ let dryRun = false;
128882
+ const passthrough = [];
128883
+ for (let index = 0; index < argv.length; index += 1) {
128884
+ const arg = argv[index];
128885
+ if (arg === "--branch") {
128886
+ onlyBranch = argv[index + 1];
128887
+ if (!onlyBranch || onlyBranch.startsWith("--")) {
128888
+ throw new Error("Missing value for --branch");
128889
+ }
128890
+ index += 1;
128891
+ } else if (arg === "--all-previews") {
128892
+ allPreviews = true;
128893
+ } else if (arg === "--dry-run") {
128894
+ dryRun = true;
128895
+ } else {
128896
+ passthrough.push(arg);
128897
+ }
128898
+ }
128899
+ if (onlyBranch && allPreviews) {
128900
+ throw new Error("Use either --branch <name> or --all-previews, not both");
128901
+ }
128902
+ const config = parseCliArgs(passthrough, env);
128903
+ const inputs = resolveInputs(config.inputEnv);
128904
+ if (!inputs.workspaceId) {
128905
+ throw new Error("gc requires --workspace-id (the workspace holding the preview/channel sets)");
128906
+ }
128907
+ const repo = inputs.repoUrl || inputs.repository;
128908
+ if (!repo) {
128909
+ throw new Error("gc requires --repo-url or --repository to scope marker ownership");
128910
+ }
128911
+ const reporter = new ConsoleReporter();
128912
+ await mintAccessTokenIfNeeded(inputs, reporter);
128913
+ const initialMasker = createSecretMasker([
128914
+ inputs.postmanApiKey,
128915
+ inputs.postmanAccessToken,
128916
+ inputs.githubToken,
128917
+ inputs.ghFallbackToken
128918
+ ]);
128919
+ const resolved = await resolvePostmanApiKeyAndTeamId(
128920
+ inputs,
128921
+ reporter,
128922
+ createCliExec(initialMasker),
128923
+ initialMasker,
128924
+ { persistGeneratedApiKeySecret: false, env }
128925
+ );
128926
+ const dependencies = createCliDependencies(inputs, resolved);
128927
+ if (!dependencies.postman.deleteCollection || !dependencies.postman.listSpecifications || !dependencies.postman.getSpecContent || !dependencies.postman.listSpecCollections || !dependencies.postman.deleteSpec) {
128928
+ throw new Error("gc requires the full branch-aware inventory client; this runtime is missing a spec or collection GC capability.");
128929
+ }
128930
+ const summary2 = await runGc({
128931
+ workspaceId: inputs.workspaceId,
128932
+ repo,
128933
+ postman: {
128934
+ ...dependencies.postman,
128935
+ deleteCollection: dependencies.postman.deleteCollection,
128936
+ listSpecifications: dependencies.postman.listSpecifications,
128937
+ getSpecContent: dependencies.postman.getSpecContent,
128938
+ listSpecCollections: dependencies.postman.listSpecCollections,
128939
+ deleteSpec: dependencies.postman.deleteSpec
128940
+ },
128941
+ exec: createCliExec(initialMasker),
128942
+ onlyBranch,
128943
+ allPreviews,
128944
+ dryRun,
128945
+ previewTtlDays: inputs.previewTtlDays,
128946
+ channels: inputs.channels,
128947
+ log: (message) => reporter.info(message)
128948
+ });
128949
+ reporter.info(renderGcSummary(summary2));
128950
+ writeStdout(JSON.stringify(summary2, null, 2) + "\n");
128951
+ }
127722
128952
  var currentModulePath = typeof __filename === "string" ? __filename : "";
127723
128953
  var entrypoint = process.argv[1];
127724
128954
  function isEntrypoint(currentPath, entrypointPath) {
@@ -127726,7 +128956,7 @@ function isEntrypoint(currentPath, entrypointPath) {
127726
128956
  return false;
127727
128957
  }
127728
128958
  try {
127729
- return (0, import_node_fs4.realpathSync)(currentPath) === (0, import_node_fs4.realpathSync)(entrypointPath);
128959
+ return (0, import_node_fs5.realpathSync)(currentPath) === (0, import_node_fs5.realpathSync)(entrypointPath);
127730
128960
  } catch {
127731
128961
  return import_node_path3.default.resolve(currentPath) === import_node_path3.default.resolve(entrypointPath);
127732
128962
  }