@postman-cse/onboarding-repo-sync 2.1.5 → 2.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/action.yml +27 -1
- package/dist/action.cjs +1003 -138
- package/dist/cli.cjs +1459 -142
- package/dist/index.cjs +1013 -138
- package/package.json +1 -1
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
@@ -123217,12 +123217,14 @@ var POSTMAN_ENDPOINT_PROFILES = {
|
|
|
123217
123217
|
prod: {
|
|
123218
123218
|
apiBaseUrl: "https://api.getpostman.com",
|
|
123219
123219
|
bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
|
|
123220
|
+
fallbackBaseUrl: "https://go.postman.co/_api",
|
|
123220
123221
|
cliInstallUrl: "https://dl-cli.pstmn.io/install/unix.sh",
|
|
123221
123222
|
iapubBaseUrl: "https://iapub.postman.co"
|
|
123222
123223
|
},
|
|
123223
123224
|
beta: {
|
|
123224
123225
|
apiBaseUrl: "https://api.getpostman-beta.com",
|
|
123225
123226
|
bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
|
|
123227
|
+
fallbackBaseUrl: "https://go.postman-beta.co/_api",
|
|
123226
123228
|
cliInstallUrl: "https://dl-cli.pstmn-beta.io/install/unix.sh",
|
|
123227
123229
|
iapubBaseUrl: "https://iapub.postman.co"
|
|
123228
123230
|
}
|
|
@@ -123289,6 +123291,9 @@ function buildCiWorkflowLines(installUrl, postmanRegion) {
|
|
|
123289
123291
|
" branches: [main]",
|
|
123290
123292
|
" schedule:",
|
|
123291
123293
|
' - cron: "0 */6 * * *"',
|
|
123294
|
+
"concurrency:",
|
|
123295
|
+
" group: postman-onboard-${{ github.head_ref || github.ref_name }}",
|
|
123296
|
+
" cancel-in-progress: false",
|
|
123292
123297
|
"jobs:",
|
|
123293
123298
|
" test:",
|
|
123294
123299
|
" runs-on: ubuntu-latest",
|
|
@@ -123305,7 +123310,7 @@ function buildCiWorkflowLines(installUrl, postmanRegion) {
|
|
|
123305
123310
|
" ruby <<'RUBY'",
|
|
123306
123311
|
" require 'yaml'",
|
|
123307
123312
|
" config = YAML.load_file('.postman/resources.yaml') || {}",
|
|
123308
|
-
" cloud = config.fetch('cloudResources', {})",
|
|
123313
|
+
" cloud = config.fetch('canonical', config.fetch('cloudResources', {}))",
|
|
123309
123314
|
" collections = cloud.fetch('collections', {})",
|
|
123310
123315
|
" environments = cloud.fetch('environments', {})",
|
|
123311
123316
|
" smoke = collections.find { |path, _| path.include?('[Smoke]') }&.last",
|
|
@@ -123503,6 +123508,58 @@ function buildAdoCiWorkflowLines(installUrl, postmanRegion) {
|
|
|
123503
123508
|
""
|
|
123504
123509
|
];
|
|
123505
123510
|
}
|
|
123511
|
+
function renderGcWorkflowTemplate() {
|
|
123512
|
+
return [
|
|
123513
|
+
"name: Postman Preview GC",
|
|
123514
|
+
"on:",
|
|
123515
|
+
" delete:",
|
|
123516
|
+
' branches: ["**"]',
|
|
123517
|
+
" pull_request:",
|
|
123518
|
+
" types: [closed]",
|
|
123519
|
+
" schedule:",
|
|
123520
|
+
' - cron: "0 2 * * *"',
|
|
123521
|
+
" workflow_dispatch:",
|
|
123522
|
+
" inputs:",
|
|
123523
|
+
" branch:",
|
|
123524
|
+
" description: Branch name to GC (optional, otherwise sweep by TTL/branch-existence)",
|
|
123525
|
+
" required: false",
|
|
123526
|
+
"concurrency:",
|
|
123527
|
+
" group: postman-preview-gc-${{ github.ref_name }}",
|
|
123528
|
+
" cancel-in-progress: false",
|
|
123529
|
+
"jobs:",
|
|
123530
|
+
" gc:",
|
|
123531
|
+
" runs-on: ubuntu-latest",
|
|
123532
|
+
" if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository",
|
|
123533
|
+
" steps:",
|
|
123534
|
+
" - uses: actions/checkout@v5",
|
|
123535
|
+
" with:",
|
|
123536
|
+
" fetch-depth: 0",
|
|
123537
|
+
" - name: Run Postman preview GC (provider-neutral cli.cjs gc)",
|
|
123538
|
+
" env:",
|
|
123539
|
+
" POSTMAN_API_KEY: ${{ secrets.POSTMAN_API_KEY }}",
|
|
123540
|
+
" POSTMAN_ACCESS_TOKEN: ${{ secrets.POSTMAN_ACCESS_TOKEN }}",
|
|
123541
|
+
" POSTMAN_WORKSPACE_ID: ${{ vars.POSTMAN_WORKSPACE_ID }}",
|
|
123542
|
+
" REPO_URL: https://github.com/${{ github.repository }}",
|
|
123543
|
+
" run: |",
|
|
123544
|
+
" set -euo pipefail",
|
|
123545
|
+
" # The repo-sync action bundles a provider-neutral gc command (cli.cjs gc)",
|
|
123546
|
+
" # that uses the Postman access token for inventory/deletion and the",
|
|
123547
|
+
" # provider ambient credential (GITHUB_TOKEN via git ls-remote) for branch existence.",
|
|
123548
|
+
" # Daily scheduled run is the retention executor (TTL contract of last resort).",
|
|
123549
|
+
' if [ -n "${{ inputs.branch }}" ]; then',
|
|
123550
|
+
' npx @postman-cse/onboarding-repo-sync gc --branch "${{ inputs.branch }}" --workspace-id "$POSTMAN_WORKSPACE_ID" --repo-url "$REPO_URL"',
|
|
123551
|
+
" else",
|
|
123552
|
+
' npx @postman-cse/onboarding-repo-sync gc --workspace-id "$POSTMAN_WORKSPACE_ID" --repo-url "$REPO_URL"',
|
|
123553
|
+
" fi",
|
|
123554
|
+
" - name: Orphan audit summary (job summary)",
|
|
123555
|
+
" if: always()",
|
|
123556
|
+
" run: |",
|
|
123557
|
+
' echo "### Preview GC orphan audit" >> "$GITHUB_STEP_SUMMARY"',
|
|
123558
|
+
' echo "Marker-guarded deletion only \u2014 strangers (no marker) are never deleted. See gc-summary-json for structured counts." >> "$GITHUB_STEP_SUMMARY"',
|
|
123559
|
+
""
|
|
123560
|
+
].join("\n");
|
|
123561
|
+
}
|
|
123562
|
+
var GC_WORKFLOW_TEMPLATE = renderGcWorkflowTemplate();
|
|
123506
123563
|
function getCiWorkflowTemplate(provider, options = {}) {
|
|
123507
123564
|
if (provider === "azure-devops") {
|
|
123508
123565
|
const rawUrl = String(options.postmanCliInstallUrl || "").trim() || DEFAULT_POSTMAN_CLI_INSTALL_URL;
|
|
@@ -125073,6 +125130,53 @@ function createInternalIntegrationAdapter(options) {
|
|
|
125073
125130
|
return new BifrostInternalIntegrationAdapter(options);
|
|
125074
125131
|
}
|
|
125075
125132
|
|
|
125133
|
+
// src/lib/retry.ts
|
|
125134
|
+
function sleep(delayMs) {
|
|
125135
|
+
return new Promise((resolve2) => {
|
|
125136
|
+
setTimeout(resolve2, delayMs);
|
|
125137
|
+
});
|
|
125138
|
+
}
|
|
125139
|
+
function normalizeRetryOptions(options) {
|
|
125140
|
+
return {
|
|
125141
|
+
maxAttempts: Math.max(1, options.maxAttempts ?? 3),
|
|
125142
|
+
delayMs: Math.max(0, options.delayMs ?? 2e3),
|
|
125143
|
+
backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
|
|
125144
|
+
maxDelayMs: options.maxDelayMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, options.maxDelayMs),
|
|
125145
|
+
onRetry: options.onRetry ?? (async () => void 0),
|
|
125146
|
+
shouldRetry: options.shouldRetry ?? (() => true),
|
|
125147
|
+
sleep: options.sleep ?? sleep
|
|
125148
|
+
};
|
|
125149
|
+
}
|
|
125150
|
+
async function retry(operation, options = {}) {
|
|
125151
|
+
const normalized = normalizeRetryOptions(options);
|
|
125152
|
+
let nextDelayMs = normalized.delayMs;
|
|
125153
|
+
for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
|
|
125154
|
+
try {
|
|
125155
|
+
return await operation();
|
|
125156
|
+
} catch (error) {
|
|
125157
|
+
const shouldRetry = attempt < normalized.maxAttempts && normalized.shouldRetry(error, {
|
|
125158
|
+
attempt,
|
|
125159
|
+
maxAttempts: normalized.maxAttempts
|
|
125160
|
+
});
|
|
125161
|
+
if (!shouldRetry) {
|
|
125162
|
+
throw error;
|
|
125163
|
+
}
|
|
125164
|
+
await normalized.onRetry({
|
|
125165
|
+
attempt,
|
|
125166
|
+
maxAttempts: normalized.maxAttempts,
|
|
125167
|
+
delayMs: nextDelayMs,
|
|
125168
|
+
error
|
|
125169
|
+
});
|
|
125170
|
+
await normalized.sleep(nextDelayMs);
|
|
125171
|
+
nextDelayMs = Math.min(
|
|
125172
|
+
normalized.maxDelayMs,
|
|
125173
|
+
Math.round(nextDelayMs * normalized.backoffMultiplier)
|
|
125174
|
+
);
|
|
125175
|
+
}
|
|
125176
|
+
}
|
|
125177
|
+
throw new Error("Retry exhausted without returning or throwing");
|
|
125178
|
+
}
|
|
125179
|
+
|
|
125076
125180
|
// src/contracts.ts
|
|
125077
125181
|
var postmanRepoSyncActionContract = {
|
|
125078
125182
|
name: "postman-repo-sync-action",
|
|
@@ -125242,6 +125346,25 @@ var postmanRepoSyncActionContract = {
|
|
|
125242
125346
|
default: "warn",
|
|
125243
125347
|
allowedValues: ["enforce", "warn"]
|
|
125244
125348
|
},
|
|
125349
|
+
"branch-strategy": {
|
|
125350
|
+
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.",
|
|
125351
|
+
required: false,
|
|
125352
|
+
default: "legacy",
|
|
125353
|
+
allowedValues: ["legacy", "preview", "publish-gate"]
|
|
125354
|
+
},
|
|
125355
|
+
"canonical-branch": {
|
|
125356
|
+
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.",
|
|
125357
|
+
required: false
|
|
125358
|
+
},
|
|
125359
|
+
"channels": {
|
|
125360
|
+
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.',
|
|
125361
|
+
required: false
|
|
125362
|
+
},
|
|
125363
|
+
"preview-ttl": {
|
|
125364
|
+
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).",
|
|
125365
|
+
required: false,
|
|
125366
|
+
default: "30"
|
|
125367
|
+
},
|
|
125245
125368
|
"github-token": {
|
|
125246
125369
|
description: "GitHub token used for repo variable persistence and commits.",
|
|
125247
125370
|
required: false
|
|
@@ -125279,6 +125402,11 @@ var postmanRepoSyncActionContract = {
|
|
|
125279
125402
|
description: "Spec UID from bootstrap, persisted into .postman/resources.yaml cloudResources.",
|
|
125280
125403
|
required: false
|
|
125281
125404
|
},
|
|
125405
|
+
"spec-content-changed": {
|
|
125406
|
+
description: "Whether bootstrap changed canonical spec content; controls final native Spec Hub tag publication.",
|
|
125407
|
+
required: false,
|
|
125408
|
+
default: "true"
|
|
125409
|
+
},
|
|
125282
125410
|
"spec-path": {
|
|
125283
125411
|
description: "Optional repo-root-relative path to the local spec file for resources/workflows metadata.",
|
|
125284
125412
|
required: false
|
|
@@ -125323,6 +125451,18 @@ var postmanRepoSyncActionContract = {
|
|
|
125323
125451
|
},
|
|
125324
125452
|
"commit-sha": {
|
|
125325
125453
|
description: "Commit SHA produced by repo-write-mode, if any."
|
|
125454
|
+
},
|
|
125455
|
+
"sync-status": {
|
|
125456
|
+
description: "Branch-aware sync status: synced, skipped-branch-gate, or empty under branch-strategy legacy."
|
|
125457
|
+
},
|
|
125458
|
+
"branch-decision": {
|
|
125459
|
+
description: "Serialized BranchDecision JSON for downstream actions (also exported as POSTMAN_BRANCH_DECISION)."
|
|
125460
|
+
},
|
|
125461
|
+
"spec-version-tag": {
|
|
125462
|
+
description: "Native Spec Hub version tag created after successful canonical repo-sync finalization."
|
|
125463
|
+
},
|
|
125464
|
+
"spec-version-url": {
|
|
125465
|
+
description: "Read-only URL for the tagged Spec Hub snapshot."
|
|
125326
125466
|
}
|
|
125327
125467
|
},
|
|
125328
125468
|
behavior: {
|
|
@@ -125390,53 +125530,6 @@ var PostmanAssetsClient = class {
|
|
|
125390
125530
|
}
|
|
125391
125531
|
};
|
|
125392
125532
|
|
|
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
125533
|
// src/lib/postman/postman-gateway-assets-client.ts
|
|
125441
125534
|
var MAX_CREATE_FLIGHTS = 256;
|
|
125442
125535
|
var createFlights = /* @__PURE__ */ new Map();
|
|
@@ -125462,6 +125555,84 @@ var PostmanGatewayAssetsClient = class {
|
|
|
125462
125555
|
if (!envelope) return null;
|
|
125463
125556
|
return this.asRecord(envelope.data) ?? envelope;
|
|
125464
125557
|
}
|
|
125558
|
+
/** Native Spec Hub tags attach to the latest changelog group. */
|
|
125559
|
+
async tagSpecVersion(specId, name) {
|
|
125560
|
+
const trimmed = name.trim().slice(0, 255);
|
|
125561
|
+
const response = await this.gateway.requestJson({
|
|
125562
|
+
service: "specification",
|
|
125563
|
+
method: "post",
|
|
125564
|
+
path: `/specifications/${specId}/tags`,
|
|
125565
|
+
body: { name: trimmed }
|
|
125566
|
+
});
|
|
125567
|
+
const record = this.dataOf(response) ?? {};
|
|
125568
|
+
return { id: String(record.id ?? "").trim(), name: String(record.name ?? trimmed).trim() };
|
|
125569
|
+
}
|
|
125570
|
+
async listSpecVersionTags(specId) {
|
|
125571
|
+
const response = await this.gateway.requestJson({
|
|
125572
|
+
service: "specification",
|
|
125573
|
+
method: "get",
|
|
125574
|
+
path: `/specifications/${specId}/tags`,
|
|
125575
|
+
query: { limit: "50" }
|
|
125576
|
+
});
|
|
125577
|
+
const data = Array.isArray(response?.data) ? response.data : [];
|
|
125578
|
+
return data.map((entry) => this.asRecord(entry)).filter((entry) => entry !== null).map((entry) => ({
|
|
125579
|
+
id: String(entry.id ?? "").trim(),
|
|
125580
|
+
// listTags returns `message`; createTag returns `name`. Accept both.
|
|
125581
|
+
name: String(entry.name ?? entry.message ?? "").trim()
|
|
125582
|
+
})).filter((entry) => entry.id || entry.name);
|
|
125583
|
+
}
|
|
125584
|
+
async deleteCollection(collectionUid) {
|
|
125585
|
+
const bareId = String(collectionUid).split("-").slice(-5).join("-") || collectionUid;
|
|
125586
|
+
await this.gateway.requestJson({
|
|
125587
|
+
service: "collection",
|
|
125588
|
+
method: "delete",
|
|
125589
|
+
path: `/v3/collections/${bareId}`
|
|
125590
|
+
});
|
|
125591
|
+
}
|
|
125592
|
+
async listSpecifications(workspaceId) {
|
|
125593
|
+
const response = await this.gateway.requestJson({
|
|
125594
|
+
service: "specification",
|
|
125595
|
+
method: "get",
|
|
125596
|
+
path: `/specifications?containerType=workspace&containerId=${workspaceId}`
|
|
125597
|
+
});
|
|
125598
|
+
const data = Array.isArray(response?.data) ? response.data : [];
|
|
125599
|
+
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);
|
|
125600
|
+
}
|
|
125601
|
+
async getSpecContent(specId) {
|
|
125602
|
+
const files = await this.gateway.requestJson({
|
|
125603
|
+
service: "specification",
|
|
125604
|
+
method: "get",
|
|
125605
|
+
path: `/specifications/${specId}/files`
|
|
125606
|
+
});
|
|
125607
|
+
const entries = Array.isArray(files?.data) ? files.data : [];
|
|
125608
|
+
const root = entries.map((entry) => this.asRecord(entry)).find((entry) => entry?.type === "ROOT") ?? entries.map((entry) => this.asRecord(entry)).find((entry) => entry !== null);
|
|
125609
|
+
const fileId = String(root?.id ?? "").trim();
|
|
125610
|
+
if (!fileId) return void 0;
|
|
125611
|
+
const file = await this.gateway.requestJson({
|
|
125612
|
+
service: "specification",
|
|
125613
|
+
method: "get",
|
|
125614
|
+
path: `/specifications/${specId}/files/${fileId}`,
|
|
125615
|
+
query: { fields: "content" }
|
|
125616
|
+
});
|
|
125617
|
+
const record = this.dataOf(file);
|
|
125618
|
+
return typeof record?.content === "string" ? record.content : void 0;
|
|
125619
|
+
}
|
|
125620
|
+
async listSpecCollections(specId) {
|
|
125621
|
+
const response = await this.gateway.requestJson({
|
|
125622
|
+
service: "specification",
|
|
125623
|
+
method: "get",
|
|
125624
|
+
path: `/specifications/${specId}/collections`
|
|
125625
|
+
});
|
|
125626
|
+
const data = Array.isArray(response?.data) ? response.data : [];
|
|
125627
|
+
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);
|
|
125628
|
+
}
|
|
125629
|
+
async deleteSpec(specId) {
|
|
125630
|
+
await this.gateway.requestJson({
|
|
125631
|
+
service: "specification",
|
|
125632
|
+
method: "delete",
|
|
125633
|
+
path: `/specifications/${specId}`
|
|
125634
|
+
});
|
|
125635
|
+
}
|
|
125465
125636
|
idOf(record) {
|
|
125466
125637
|
if (!record) return "";
|
|
125467
125638
|
const id = record.uid ?? record.id;
|
|
@@ -125521,6 +125692,14 @@ var PostmanGatewayAssetsClient = class {
|
|
|
125521
125692
|
createFlights.set(key, { fingerprint, promise: pending });
|
|
125522
125693
|
return pending;
|
|
125523
125694
|
}
|
|
125695
|
+
/**
|
|
125696
|
+
* Reconcile an ambiguous create. Returns the adopted match, `undefined`
|
|
125697
|
+
* when every discovery read succeeded and found nothing (the create is
|
|
125698
|
+
* conclusively absent, so a fallback resend cannot duplicate), or `null`
|
|
125699
|
+
* when the outcome is inconclusive (non-ambiguous error, or a discovery
|
|
125700
|
+
* read itself failed — in that case a resend could duplicate a committed
|
|
125701
|
+
* twin and must not fire).
|
|
125702
|
+
*/
|
|
125524
125703
|
async discoverAfterAmbiguousCreate(discover, error) {
|
|
125525
125704
|
if (!this.isAmbiguousCreateOutcome(error)) {
|
|
125526
125705
|
return null;
|
|
@@ -125529,12 +125708,31 @@ var PostmanGatewayAssetsClient = class {
|
|
|
125529
125708
|
if (attempt > 0) {
|
|
125530
125709
|
await this.sleep(this.reconcileDelayMs);
|
|
125531
125710
|
}
|
|
125532
|
-
|
|
125711
|
+
let found;
|
|
125712
|
+
try {
|
|
125713
|
+
found = await discover();
|
|
125714
|
+
} catch {
|
|
125715
|
+
return null;
|
|
125716
|
+
}
|
|
125533
125717
|
if (found) {
|
|
125534
125718
|
return found;
|
|
125535
125719
|
}
|
|
125536
125720
|
}
|
|
125537
|
-
return
|
|
125721
|
+
return void 0;
|
|
125722
|
+
}
|
|
125723
|
+
/**
|
|
125724
|
+
* After an ambiguous create failed AND discovery proved the asset absent,
|
|
125725
|
+
* re-attempt the create once with the cold `/_api` fallback enabled: the
|
|
125726
|
+
* reconcile above guarantees no committed twin, so the resend cannot
|
|
125727
|
+
* duplicate. Returns null when the resend also fails (caller rethrows the
|
|
125728
|
+
* original error).
|
|
125729
|
+
*/
|
|
125730
|
+
async resendAbsentCreate(operation) {
|
|
125731
|
+
try {
|
|
125732
|
+
return await operation();
|
|
125733
|
+
} catch {
|
|
125734
|
+
return null;
|
|
125735
|
+
}
|
|
125538
125736
|
}
|
|
125539
125737
|
publicEnvironmentUid(data, bareId) {
|
|
125540
125738
|
const owner = String(data?.owner ?? "").trim();
|
|
@@ -125582,17 +125780,18 @@ var PostmanGatewayAssetsClient = class {
|
|
|
125582
125780
|
name: envName,
|
|
125583
125781
|
values: normalizedValues
|
|
125584
125782
|
};
|
|
125783
|
+
const send2 = (fallback) => this.gateway.requestJson(
|
|
125784
|
+
{
|
|
125785
|
+
service: "sync",
|
|
125786
|
+
method: "post",
|
|
125787
|
+
path: `/environment/import?workspace=${ws}`,
|
|
125788
|
+
body,
|
|
125789
|
+
...fallback ? { fallback } : {}
|
|
125790
|
+
},
|
|
125791
|
+
{ retryTransient: false }
|
|
125792
|
+
);
|
|
125585
125793
|
try {
|
|
125586
|
-
const
|
|
125587
|
-
{
|
|
125588
|
-
service: "sync",
|
|
125589
|
-
method: "post",
|
|
125590
|
-
path: `/environment/import?workspace=${ws}`,
|
|
125591
|
-
body
|
|
125592
|
-
},
|
|
125593
|
-
{ retryTransient: false }
|
|
125594
|
-
);
|
|
125595
|
-
const data = this.dataOf(response);
|
|
125794
|
+
const data = this.dataOf(await send2());
|
|
125596
125795
|
const bareId = this.idOf(data);
|
|
125597
125796
|
if (!bareId) {
|
|
125598
125797
|
throw new Error("Environment import did not return a UID");
|
|
@@ -125607,6 +125806,15 @@ var PostmanGatewayAssetsClient = class {
|
|
|
125607
125806
|
await this.updateEnvironment(adopted, envName, values);
|
|
125608
125807
|
return adopted;
|
|
125609
125808
|
}
|
|
125809
|
+
if (adopted === void 0) {
|
|
125810
|
+
const retried = await this.resendAbsentCreate(async () => {
|
|
125811
|
+
const data = this.dataOf(await send2("auto"));
|
|
125812
|
+
const bareId = this.idOf(data);
|
|
125813
|
+
if (!bareId) throw new Error("Environment import did not return a UID");
|
|
125814
|
+
return this.publicEnvironmentUid(data, bareId);
|
|
125815
|
+
});
|
|
125816
|
+
if (retried) return retried;
|
|
125817
|
+
}
|
|
125610
125818
|
throw error;
|
|
125611
125819
|
}
|
|
125612
125820
|
});
|
|
@@ -125738,16 +125946,20 @@ var PostmanGatewayAssetsClient = class {
|
|
|
125738
125946
|
private: false,
|
|
125739
125947
|
...environment ? { environment } : {}
|
|
125740
125948
|
};
|
|
125741
|
-
|
|
125742
|
-
|
|
125743
|
-
|
|
125744
|
-
|
|
125745
|
-
|
|
125746
|
-
|
|
125747
|
-
|
|
125748
|
-
|
|
125749
|
-
|
|
125750
|
-
|
|
125949
|
+
const send2 = (fallback) => this.gateway.requestJson(
|
|
125950
|
+
{
|
|
125951
|
+
service: "mock",
|
|
125952
|
+
method: "post",
|
|
125953
|
+
path: `/mocks?workspace=${ws}`,
|
|
125954
|
+
body,
|
|
125955
|
+
...fallback ? { fallback } : {}
|
|
125956
|
+
},
|
|
125957
|
+
// Unsafe create: never blind re-POST. An ESOCKETTIMEDOUT after accept
|
|
125958
|
+
// may still have created the mock; reconcile via discovery below and
|
|
125959
|
+
// let the orchestrator retry the whole create-or-adopt cycle.
|
|
125960
|
+
{ retryTransient: false }
|
|
125961
|
+
);
|
|
125962
|
+
const parseMock = (response) => {
|
|
125751
125963
|
const record = this.dataOf(response);
|
|
125752
125964
|
const uid = this.idOf(record);
|
|
125753
125965
|
if (!uid) {
|
|
@@ -125757,6 +125969,9 @@ var PostmanGatewayAssetsClient = class {
|
|
|
125757
125969
|
uid,
|
|
125758
125970
|
url: String(record?.url ?? record?.mockUrl ?? "").trim()
|
|
125759
125971
|
};
|
|
125972
|
+
};
|
|
125973
|
+
try {
|
|
125974
|
+
return parseMock(await send2());
|
|
125760
125975
|
} catch (error) {
|
|
125761
125976
|
const adopted = await this.discoverAfterAmbiguousCreate(
|
|
125762
125977
|
() => this.findMockByCollection(collection, environment, mockName),
|
|
@@ -125765,6 +125980,10 @@ var PostmanGatewayAssetsClient = class {
|
|
|
125765
125980
|
if (adopted) {
|
|
125766
125981
|
return { uid: adopted.uid, url: adopted.mockUrl };
|
|
125767
125982
|
}
|
|
125983
|
+
if (adopted === void 0) {
|
|
125984
|
+
const retried = await this.resendAbsentCreate(async () => parseMock(await send2("auto")));
|
|
125985
|
+
if (retried) return retried;
|
|
125986
|
+
}
|
|
125768
125987
|
throw error;
|
|
125769
125988
|
}
|
|
125770
125989
|
});
|
|
@@ -125784,6 +126003,33 @@ var PostmanGatewayAssetsClient = class {
|
|
|
125784
126003
|
environment: String(m.environment ?? "")
|
|
125785
126004
|
}));
|
|
125786
126005
|
}
|
|
126006
|
+
/**
|
|
126007
|
+
* Delete an environment through the sync service (GC path). The path id is
|
|
126008
|
+
* the bare model id (public uid tail), mirroring updateEnvironment.
|
|
126009
|
+
*/
|
|
126010
|
+
async deleteEnvironment(uid) {
|
|
126011
|
+
await this.gateway.requestJson({
|
|
126012
|
+
service: "sync",
|
|
126013
|
+
method: "delete",
|
|
126014
|
+
path: `/environment/${this.toModelId(uid)}`
|
|
126015
|
+
});
|
|
126016
|
+
}
|
|
126017
|
+
/** Delete a mock server (GC path). */
|
|
126018
|
+
async deleteMock(uid) {
|
|
126019
|
+
await this.gateway.requestJson({
|
|
126020
|
+
service: "mock",
|
|
126021
|
+
method: "delete",
|
|
126022
|
+
path: `/mocks/${this.toModelId(uid)}`
|
|
126023
|
+
});
|
|
126024
|
+
}
|
|
126025
|
+
/** Delete a collection-based monitor (jobTemplate) (GC path). */
|
|
126026
|
+
async deleteMonitor(uid) {
|
|
126027
|
+
await this.gateway.requestJson({
|
|
126028
|
+
service: "monitors",
|
|
126029
|
+
method: "delete",
|
|
126030
|
+
path: `/jobTemplates/${this.toModelId(uid)}`
|
|
126031
|
+
});
|
|
126032
|
+
}
|
|
125787
126033
|
async mockExists(uid) {
|
|
125788
126034
|
try {
|
|
125789
126035
|
await this.gateway.requestJson({
|
|
@@ -125844,21 +126090,25 @@ var PostmanGatewayAssetsClient = class {
|
|
|
125844
126090
|
distribution: null,
|
|
125845
126091
|
...environment ? { environment } : {}
|
|
125846
126092
|
};
|
|
125847
|
-
|
|
125848
|
-
|
|
125849
|
-
|
|
125850
|
-
|
|
125851
|
-
|
|
125852
|
-
|
|
125853
|
-
|
|
125854
|
-
|
|
125855
|
-
|
|
125856
|
-
|
|
126093
|
+
const send2 = (fallback) => this.gateway.requestJson(
|
|
126094
|
+
{
|
|
126095
|
+
service: "monitors",
|
|
126096
|
+
method: "post",
|
|
126097
|
+
path: `/jobTemplates?workspace=${ws}`,
|
|
126098
|
+
body,
|
|
126099
|
+
...fallback ? { fallback } : {}
|
|
126100
|
+
},
|
|
126101
|
+
{ retryTransient: false }
|
|
126102
|
+
);
|
|
126103
|
+
const parseMonitor = (response) => {
|
|
125857
126104
|
const uid = this.idOf(this.dataOf(response));
|
|
125858
126105
|
if (!uid) {
|
|
125859
126106
|
throw new Error("Monitor create did not return a UID");
|
|
125860
126107
|
}
|
|
125861
126108
|
return uid;
|
|
126109
|
+
};
|
|
126110
|
+
try {
|
|
126111
|
+
return parseMonitor(await send2());
|
|
125862
126112
|
} catch (error) {
|
|
125863
126113
|
const adopted = await this.discoverAfterAmbiguousCreate(
|
|
125864
126114
|
() => this.findMonitorByCollection(collection, environment, monitorName),
|
|
@@ -125867,6 +126117,10 @@ var PostmanGatewayAssetsClient = class {
|
|
|
125867
126117
|
if (adopted?.uid) {
|
|
125868
126118
|
return adopted.uid;
|
|
125869
126119
|
}
|
|
126120
|
+
if (adopted === void 0) {
|
|
126121
|
+
const retried = await this.resendAbsentCreate(async () => parseMonitor(await send2("auto")));
|
|
126122
|
+
if (retried) return retried;
|
|
126123
|
+
}
|
|
125870
126124
|
throw error;
|
|
125871
126125
|
}
|
|
125872
126126
|
});
|
|
@@ -126095,6 +126349,7 @@ var AccessTokenGatewayClient = class {
|
|
|
126095
126349
|
fetchImpl;
|
|
126096
126350
|
secretMasker;
|
|
126097
126351
|
maxRetries;
|
|
126352
|
+
fallbackBaseUrl;
|
|
126098
126353
|
retryBaseDelayMs;
|
|
126099
126354
|
sleepImpl;
|
|
126100
126355
|
constructor(options) {
|
|
@@ -126107,6 +126362,8 @@ var AccessTokenGatewayClient = class {
|
|
|
126107
126362
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
126108
126363
|
this.secretMasker = options.secretMasker ?? createSecretMasker([this.tokenProvider.current()]);
|
|
126109
126364
|
this.maxRetries = options.maxRetries ?? 3;
|
|
126365
|
+
const fallbackEnv = typeof process !== "undefined" ? process.env?.POSTMAN_ITEM_CREATE_FALLBACK : void 0;
|
|
126366
|
+
this.fallbackBaseUrl = fallbackEnv === "off" ? void 0 : options.fallbackBaseUrl?.replace(/\/+$/, "");
|
|
126110
126367
|
this.retryBaseDelayMs = options.retryBaseDelayMs ?? 400;
|
|
126111
126368
|
this.sleepImpl = options.sleepImpl ?? defaultSleep;
|
|
126112
126369
|
}
|
|
@@ -126125,8 +126382,8 @@ var AccessTokenGatewayClient = class {
|
|
|
126125
126382
|
}
|
|
126126
126383
|
return headers;
|
|
126127
126384
|
}
|
|
126128
|
-
async send(request) {
|
|
126129
|
-
const url = `${this.bifrostBaseUrl}/ws/proxy`;
|
|
126385
|
+
async send(request, baseUrl) {
|
|
126386
|
+
const url = `${baseUrl ?? this.bifrostBaseUrl}/ws/proxy`;
|
|
126130
126387
|
return this.fetchImpl(url, {
|
|
126131
126388
|
method: "POST",
|
|
126132
126389
|
headers: this.buildHeaders(request.headers),
|
|
@@ -126139,6 +126396,43 @@ var AccessTokenGatewayClient = class {
|
|
|
126139
126396
|
})
|
|
126140
126397
|
});
|
|
126141
126398
|
}
|
|
126399
|
+
/**
|
|
126400
|
+
* One cold, serial attempt against the fallback base URL after the primary
|
|
126401
|
+
* budget is exhausted on a transient failure. Never hedged in parallel with
|
|
126402
|
+
* the primary; only fires when the request would otherwise throw. Callers
|
|
126403
|
+
* with `retryTransient: false` still reconcile first — the fallback attempt
|
|
126404
|
+
* here is the resend, so it is only used for requests whose mutation is
|
|
126405
|
+
* known idempotent or already reconciled by the caller's adopt-on-ambiguous
|
|
126406
|
+
* loop.
|
|
126407
|
+
*/
|
|
126408
|
+
async tryFallback(request) {
|
|
126409
|
+
if (!this.fallbackBaseUrl) return null;
|
|
126410
|
+
try {
|
|
126411
|
+
return await this.send(request, this.fallbackBaseUrl);
|
|
126412
|
+
} catch {
|
|
126413
|
+
return null;
|
|
126414
|
+
}
|
|
126415
|
+
}
|
|
126416
|
+
/**
|
|
126417
|
+
* Run the fallback attempt and classify its response the same way the
|
|
126418
|
+
* primary path would. Returns the success response, or null when the
|
|
126419
|
+
* fallback also failed transiently (caller then throws the original error).
|
|
126420
|
+
* Non-transient fallback failures (4xx) surface as their own HttpError since
|
|
126421
|
+
* they are the freshest authoritative answer.
|
|
126422
|
+
*/
|
|
126423
|
+
fallbackEligible(request, retryTransient) {
|
|
126424
|
+
if (!this.fallbackBaseUrl) return false;
|
|
126425
|
+
return retryTransient || request.fallback === "auto";
|
|
126426
|
+
}
|
|
126427
|
+
async attemptFallback(request, retryTransient) {
|
|
126428
|
+
if (!this.fallbackEligible(request, retryTransient)) return null;
|
|
126429
|
+
const response = await this.tryFallback(request);
|
|
126430
|
+
if (!response) return null;
|
|
126431
|
+
if (response.ok) return response;
|
|
126432
|
+
const body = await response.text().catch(() => "");
|
|
126433
|
+
if (isRetryableSafeReadResponse(response.status)) return null;
|
|
126434
|
+
throw this.toHttpError(request, response, body);
|
|
126435
|
+
}
|
|
126142
126436
|
/**
|
|
126143
126437
|
* Send a gateway request, refreshing the token once on an auth failure and
|
|
126144
126438
|
* optionally retrying transient downstream failures (5xx / Bifrost read
|
|
@@ -126161,6 +126455,8 @@ var AccessTokenGatewayClient = class {
|
|
|
126161
126455
|
await this.sleepImpl(delay);
|
|
126162
126456
|
continue;
|
|
126163
126457
|
}
|
|
126458
|
+
const fallbackResponse2 = await this.attemptFallback(request, retryTransient);
|
|
126459
|
+
if (fallbackResponse2) return fallbackResponse2;
|
|
126164
126460
|
throw error;
|
|
126165
126461
|
}
|
|
126166
126462
|
if (response.ok) {
|
|
@@ -126182,6 +126478,8 @@ var AccessTokenGatewayClient = class {
|
|
|
126182
126478
|
await this.sleepImpl(delay);
|
|
126183
126479
|
continue;
|
|
126184
126480
|
}
|
|
126481
|
+
const fallbackResponse = await this.attemptFallback(request, retryTransient);
|
|
126482
|
+
if (fallbackResponse) return fallbackResponse;
|
|
126185
126483
|
throw this.toHttpError(request, response, body);
|
|
126186
126484
|
}
|
|
126187
126485
|
}
|
|
@@ -126238,6 +126536,331 @@ var AccessTokenGatewayClient = class {
|
|
|
126238
126536
|
}
|
|
126239
126537
|
};
|
|
126240
126538
|
|
|
126539
|
+
// src/lib/repo/branch-decision.ts
|
|
126540
|
+
var import_node_fs3 = require("node:fs");
|
|
126541
|
+
var import_node_crypto2 = require("node:crypto");
|
|
126542
|
+
var ContractError = class extends Error {
|
|
126543
|
+
code;
|
|
126544
|
+
constructor(code, message) {
|
|
126545
|
+
super(`${code}: ${message}`);
|
|
126546
|
+
this.code = code;
|
|
126547
|
+
this.name = "ContractError";
|
|
126548
|
+
}
|
|
126549
|
+
};
|
|
126550
|
+
function clean(value) {
|
|
126551
|
+
const trimmed = (value ?? "").trim();
|
|
126552
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
126553
|
+
}
|
|
126554
|
+
function stripRefPrefix(ref) {
|
|
126555
|
+
const raw = clean(ref);
|
|
126556
|
+
if (!raw) {
|
|
126557
|
+
return { kind: "unknown" };
|
|
126558
|
+
}
|
|
126559
|
+
if (raw.startsWith("refs/heads/")) {
|
|
126560
|
+
return { name: raw.slice("refs/heads/".length), kind: "branch" };
|
|
126561
|
+
}
|
|
126562
|
+
if (raw.startsWith("refs/tags/")) {
|
|
126563
|
+
return { name: raw.slice("refs/tags/".length), kind: "tag" };
|
|
126564
|
+
}
|
|
126565
|
+
if (raw.startsWith("refs/pull/") || raw.startsWith("refs/merge")) {
|
|
126566
|
+
return { kind: "unknown" };
|
|
126567
|
+
}
|
|
126568
|
+
return { name: raw, kind: "branch" };
|
|
126569
|
+
}
|
|
126570
|
+
function detectProvider(env) {
|
|
126571
|
+
if (clean(env.GITHUB_ACTIONS) || clean(env.GITHUB_REPOSITORY)) return "github";
|
|
126572
|
+
if (clean(env.GITLAB_CI) || clean(env.CI_PROJECT_PATH)) return "gitlab";
|
|
126573
|
+
if (clean(env.BITBUCKET_REPO_SLUG) || clean(env.BITBUCKET_BRANCH)) return "bitbucket";
|
|
126574
|
+
if (clean(env.TF_BUILD) || clean(env.BUILD_REPOSITORY_URI)) return "azure-devops";
|
|
126575
|
+
return "unknown";
|
|
126576
|
+
}
|
|
126577
|
+
function readGithubEvent(env) {
|
|
126578
|
+
const path5 = clean(env.GITHUB_EVENT_PATH);
|
|
126579
|
+
if (!path5) return void 0;
|
|
126580
|
+
try {
|
|
126581
|
+
return JSON.parse((0, import_node_fs3.readFileSync)(path5, "utf8"));
|
|
126582
|
+
} catch {
|
|
126583
|
+
return void 0;
|
|
126584
|
+
}
|
|
126585
|
+
}
|
|
126586
|
+
function resolveBranchIdentity(env = process.env, overrides = {}) {
|
|
126587
|
+
const provider = detectProvider(env);
|
|
126588
|
+
const explicitDefault = clean(overrides.defaultBranch);
|
|
126589
|
+
let headBranch;
|
|
126590
|
+
let rawRef;
|
|
126591
|
+
let refKind;
|
|
126592
|
+
let isPrContext = false;
|
|
126593
|
+
let isForkPr = false;
|
|
126594
|
+
let defaultBranch = explicitDefault;
|
|
126595
|
+
let headSha;
|
|
126596
|
+
switch (provider) {
|
|
126597
|
+
case "github": {
|
|
126598
|
+
const event = readGithubEvent(env);
|
|
126599
|
+
headSha = clean(env.GITHUB_SHA);
|
|
126600
|
+
defaultBranch ??= clean(event?.repository?.default_branch);
|
|
126601
|
+
const headRef = clean(env.GITHUB_HEAD_REF);
|
|
126602
|
+
if (headRef) {
|
|
126603
|
+
isPrContext = true;
|
|
126604
|
+
headBranch = headRef;
|
|
126605
|
+
rawRef = clean(env.GITHUB_REF) ?? headRef;
|
|
126606
|
+
refKind = "branch";
|
|
126607
|
+
const headRepo = event?.pull_request?.head?.repo?.full_name;
|
|
126608
|
+
const baseRepo = event?.pull_request?.base?.repo?.full_name ?? event?.repository?.full_name;
|
|
126609
|
+
isForkPr = Boolean(headRepo && baseRepo && headRepo !== baseRepo);
|
|
126610
|
+
headSha = clean(event?.pull_request?.head?.sha) ?? headSha;
|
|
126611
|
+
} else {
|
|
126612
|
+
rawRef = clean(env.GITHUB_REF) ?? clean(env.GITHUB_REF_NAME);
|
|
126613
|
+
const parsed = stripRefPrefix(rawRef);
|
|
126614
|
+
headBranch = parsed.kind === "branch" ? parsed.name : void 0;
|
|
126615
|
+
refKind = parsed.kind;
|
|
126616
|
+
}
|
|
126617
|
+
break;
|
|
126618
|
+
}
|
|
126619
|
+
case "gitlab": {
|
|
126620
|
+
headSha = clean(env.CI_COMMIT_SHA);
|
|
126621
|
+
defaultBranch ??= clean(env.CI_DEFAULT_BRANCH);
|
|
126622
|
+
const mrSource = clean(env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME);
|
|
126623
|
+
if (mrSource) {
|
|
126624
|
+
isPrContext = true;
|
|
126625
|
+
headBranch = mrSource;
|
|
126626
|
+
rawRef = mrSource;
|
|
126627
|
+
refKind = "branch";
|
|
126628
|
+
const sourceProject = clean(env.CI_MERGE_REQUEST_SOURCE_PROJECT_ID);
|
|
126629
|
+
const targetProject = clean(env.CI_MERGE_REQUEST_PROJECT_ID);
|
|
126630
|
+
isForkPr = Boolean(sourceProject && targetProject && sourceProject !== targetProject);
|
|
126631
|
+
} else if (clean(env.CI_COMMIT_TAG)) {
|
|
126632
|
+
rawRef = clean(env.CI_COMMIT_TAG);
|
|
126633
|
+
refKind = "tag";
|
|
126634
|
+
} else {
|
|
126635
|
+
headBranch = clean(env.CI_COMMIT_BRANCH) ?? clean(env.CI_COMMIT_REF_NAME);
|
|
126636
|
+
rawRef = headBranch;
|
|
126637
|
+
refKind = headBranch ? "branch" : "unknown";
|
|
126638
|
+
}
|
|
126639
|
+
break;
|
|
126640
|
+
}
|
|
126641
|
+
case "bitbucket": {
|
|
126642
|
+
headSha = clean(env.BITBUCKET_COMMIT);
|
|
126643
|
+
if (clean(env.BITBUCKET_TAG)) {
|
|
126644
|
+
rawRef = clean(env.BITBUCKET_TAG);
|
|
126645
|
+
refKind = "tag";
|
|
126646
|
+
} else {
|
|
126647
|
+
headBranch = clean(env.BITBUCKET_BRANCH);
|
|
126648
|
+
rawRef = headBranch;
|
|
126649
|
+
refKind = headBranch ? "branch" : "unknown";
|
|
126650
|
+
isPrContext = Boolean(clean(env.BITBUCKET_PR_ID));
|
|
126651
|
+
}
|
|
126652
|
+
break;
|
|
126653
|
+
}
|
|
126654
|
+
case "azure-devops": {
|
|
126655
|
+
headSha = clean(env.BUILD_SOURCEVERSION);
|
|
126656
|
+
const prSource = clean(env.SYSTEM_PULLREQUEST_SOURCEBRANCH);
|
|
126657
|
+
if (prSource) {
|
|
126658
|
+
isPrContext = true;
|
|
126659
|
+
const parsed = stripRefPrefix(prSource);
|
|
126660
|
+
headBranch = parsed.kind === "branch" ? parsed.name : void 0;
|
|
126661
|
+
rawRef = prSource;
|
|
126662
|
+
refKind = parsed.kind;
|
|
126663
|
+
const forkFlag = clean(env.SYSTEM_PULLREQUEST_ISFORK);
|
|
126664
|
+
isForkPr = forkFlag?.toLowerCase() === "true";
|
|
126665
|
+
} else {
|
|
126666
|
+
rawRef = clean(env.BUILD_SOURCEBRANCH);
|
|
126667
|
+
const parsed = stripRefPrefix(rawRef);
|
|
126668
|
+
headBranch = parsed.kind === "branch" ? parsed.name : void 0;
|
|
126669
|
+
refKind = parsed.kind;
|
|
126670
|
+
}
|
|
126671
|
+
break;
|
|
126672
|
+
}
|
|
126673
|
+
default: {
|
|
126674
|
+
refKind = "unknown";
|
|
126675
|
+
}
|
|
126676
|
+
}
|
|
126677
|
+
if (refKind === "branch" && headBranch && defaultBranch && headBranch === defaultBranch) {
|
|
126678
|
+
refKind = "default-branch";
|
|
126679
|
+
}
|
|
126680
|
+
return { provider, headBranch, rawRef, defaultBranch, refKind, isPrContext, isForkPr, headSha };
|
|
126681
|
+
}
|
|
126682
|
+
function parseChannelRules(input) {
|
|
126683
|
+
const raw = clean(input);
|
|
126684
|
+
const rules = [];
|
|
126685
|
+
for (const part of raw ? raw.split(",") : []) {
|
|
126686
|
+
const entry = part.trim();
|
|
126687
|
+
if (!entry) continue;
|
|
126688
|
+
const eq = entry.indexOf("=");
|
|
126689
|
+
if (eq <= 0 || eq === entry.length - 1) {
|
|
126690
|
+
throw new ContractError(
|
|
126691
|
+
"CONTRACT_CHANNELS_INPUT_INVALID",
|
|
126692
|
+
`channels entry "${entry}" must be <branch-or-glob>=<CODE>`
|
|
126693
|
+
);
|
|
126694
|
+
}
|
|
126695
|
+
const pattern = entry.slice(0, eq).trim();
|
|
126696
|
+
const code = entry.slice(eq + 1).trim().toUpperCase();
|
|
126697
|
+
if (!/^[A-Z][A-Z0-9_-]{0,15}$/.test(code)) {
|
|
126698
|
+
throw new ContractError(
|
|
126699
|
+
"CONTRACT_CHANNELS_INPUT_INVALID",
|
|
126700
|
+
`channel code "${code}" must be 1-16 chars, A-Z 0-9 _ -, starting with a letter`
|
|
126701
|
+
);
|
|
126702
|
+
}
|
|
126703
|
+
rules.push({ pattern, code });
|
|
126704
|
+
}
|
|
126705
|
+
if (!rules.some((rule) => rule.pattern === "release/*")) {
|
|
126706
|
+
rules.push({ pattern: "release/*", code: "RC" });
|
|
126707
|
+
}
|
|
126708
|
+
return rules;
|
|
126709
|
+
}
|
|
126710
|
+
function matchChannel(branch, rules) {
|
|
126711
|
+
for (const rule of rules) {
|
|
126712
|
+
if (rule.pattern.endsWith("*")) {
|
|
126713
|
+
const prefix = rule.pattern.slice(0, -1);
|
|
126714
|
+
if (branch.startsWith(prefix)) return rule;
|
|
126715
|
+
} else if (branch === rule.pattern) {
|
|
126716
|
+
return rule;
|
|
126717
|
+
}
|
|
126718
|
+
}
|
|
126719
|
+
return void 0;
|
|
126720
|
+
}
|
|
126721
|
+
function resolveBranchDecision(options) {
|
|
126722
|
+
const { strategy, identity } = options;
|
|
126723
|
+
const channels = options.channels ?? [];
|
|
126724
|
+
if (strategy === "legacy") {
|
|
126725
|
+
return {
|
|
126726
|
+
tier: "legacy",
|
|
126727
|
+
strategy,
|
|
126728
|
+
identity,
|
|
126729
|
+
canonicalBranch: clean(options.canonicalBranch) ?? identity.defaultBranch,
|
|
126730
|
+
reason: "branch-strategy legacy: branch-blind pre-v2 behavior"
|
|
126731
|
+
};
|
|
126732
|
+
}
|
|
126733
|
+
const canonicalBranch = clean(options.canonicalBranch) ?? identity.defaultBranch;
|
|
126734
|
+
if (!canonicalBranch) {
|
|
126735
|
+
throw new ContractError(
|
|
126736
|
+
"CONTRACT_DEFAULT_BRANCH_UNRESOLVED",
|
|
126737
|
+
`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.`
|
|
126738
|
+
);
|
|
126739
|
+
}
|
|
126740
|
+
if (identity.refKind === "tag" || identity.refKind === "unknown" || !identity.headBranch) {
|
|
126741
|
+
return {
|
|
126742
|
+
tier: "gated",
|
|
126743
|
+
strategy,
|
|
126744
|
+
identity,
|
|
126745
|
+
canonicalBranch,
|
|
126746
|
+
reason: `ref kind ${identity.refKind}: never canonical/preview-eligible; no-op with annotation`
|
|
126747
|
+
};
|
|
126748
|
+
}
|
|
126749
|
+
if (identity.headBranch === canonicalBranch) {
|
|
126750
|
+
return {
|
|
126751
|
+
tier: "canonical",
|
|
126752
|
+
strategy,
|
|
126753
|
+
identity,
|
|
126754
|
+
canonicalBranch,
|
|
126755
|
+
reason: `head branch equals canonical branch ${canonicalBranch}`
|
|
126756
|
+
};
|
|
126757
|
+
}
|
|
126758
|
+
const channel = matchChannel(identity.headBranch, channels);
|
|
126759
|
+
if (channel) {
|
|
126760
|
+
return {
|
|
126761
|
+
tier: "channel",
|
|
126762
|
+
strategy,
|
|
126763
|
+
identity,
|
|
126764
|
+
canonicalBranch,
|
|
126765
|
+
channel,
|
|
126766
|
+
reason: `branch ${identity.headBranch} matches channel ${channel.pattern}=${channel.code}`
|
|
126767
|
+
};
|
|
126768
|
+
}
|
|
126769
|
+
if (strategy === "preview") {
|
|
126770
|
+
if (identity.isForkPr) {
|
|
126771
|
+
return {
|
|
126772
|
+
tier: "gated",
|
|
126773
|
+
strategy,
|
|
126774
|
+
identity,
|
|
126775
|
+
canonicalBranch,
|
|
126776
|
+
reason: "fork PR: preview-ineligible (same-repo gate), gated instead"
|
|
126777
|
+
};
|
|
126778
|
+
}
|
|
126779
|
+
return {
|
|
126780
|
+
tier: "preview",
|
|
126781
|
+
strategy,
|
|
126782
|
+
identity,
|
|
126783
|
+
canonicalBranch,
|
|
126784
|
+
reason: `branch ${identity.headBranch} under branch-strategy preview`
|
|
126785
|
+
};
|
|
126786
|
+
}
|
|
126787
|
+
return {
|
|
126788
|
+
tier: "gated",
|
|
126789
|
+
strategy,
|
|
126790
|
+
identity,
|
|
126791
|
+
canonicalBranch,
|
|
126792
|
+
reason: `branch ${identity.headBranch} under branch-strategy publish-gate`
|
|
126793
|
+
};
|
|
126794
|
+
}
|
|
126795
|
+
var BRANCH_DECISION_ENV = "POSTMAN_BRANCH_DECISION";
|
|
126796
|
+
function serializeBranchDecision(decision) {
|
|
126797
|
+
return JSON.stringify(decision);
|
|
126798
|
+
}
|
|
126799
|
+
function parseBranchDecision(raw) {
|
|
126800
|
+
const value = clean(raw);
|
|
126801
|
+
if (!value) return void 0;
|
|
126802
|
+
let parsed;
|
|
126803
|
+
try {
|
|
126804
|
+
parsed = JSON.parse(value);
|
|
126805
|
+
} catch {
|
|
126806
|
+
throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION is not valid JSON");
|
|
126807
|
+
}
|
|
126808
|
+
const candidate = parsed;
|
|
126809
|
+
const tiers = ["canonical", "channel", "preview", "gated", "legacy"];
|
|
126810
|
+
if (!candidate || typeof candidate !== "object" || !tiers.includes(candidate.tier) || !candidate.identity) {
|
|
126811
|
+
throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION does not carry a valid BranchDecision");
|
|
126812
|
+
}
|
|
126813
|
+
return candidate;
|
|
126814
|
+
}
|
|
126815
|
+
function resolveEffectiveBranchDecision(options, env = process.env) {
|
|
126816
|
+
const inherited = parseBranchDecision(env[BRANCH_DECISION_ENV]);
|
|
126817
|
+
if (inherited) return inherited;
|
|
126818
|
+
return resolveBranchDecision(options);
|
|
126819
|
+
}
|
|
126820
|
+
var PREVIEW_SLUG_MAX = 30;
|
|
126821
|
+
function buildBranchSlug(rawBranch) {
|
|
126822
|
+
const sanitized = rawBranch.replace(/^refs\/heads\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
|
|
126823
|
+
const truncated = sanitized.slice(0, PREVIEW_SLUG_MAX);
|
|
126824
|
+
const lossy = truncated !== rawBranch.replace(/^refs\/heads\//, "");
|
|
126825
|
+
if (!lossy) {
|
|
126826
|
+
return { suffix: truncated, slug: truncated, lossy };
|
|
126827
|
+
}
|
|
126828
|
+
const hash = (0, import_node_crypto2.createHash)("sha256").update(rawBranch).digest("hex").slice(0, 6);
|
|
126829
|
+
return { suffix: `${truncated}-${hash}`, slug: truncated, lossy };
|
|
126830
|
+
}
|
|
126831
|
+
function previewAssetName(baseName, rawBranch) {
|
|
126832
|
+
return `${baseName} @${buildBranchSlug(rawBranch).suffix}`;
|
|
126833
|
+
}
|
|
126834
|
+
function channelAssetName(baseName, code) {
|
|
126835
|
+
return `[${code}] ${baseName}`;
|
|
126836
|
+
}
|
|
126837
|
+
var MARKER_KEY = "x-pm-onboarding";
|
|
126838
|
+
function parseAssetMarker(description) {
|
|
126839
|
+
if (!description) return void 0;
|
|
126840
|
+
const index = description.indexOf(`${MARKER_KEY}:`);
|
|
126841
|
+
if (index === -1) return void 0;
|
|
126842
|
+
const jsonStart = description.indexOf("{", index);
|
|
126843
|
+
if (jsonStart === -1) return void 0;
|
|
126844
|
+
let depth = 0;
|
|
126845
|
+
for (let i = jsonStart; i < description.length; i += 1) {
|
|
126846
|
+
const ch = description[i];
|
|
126847
|
+
if (ch === "{") depth += 1;
|
|
126848
|
+
else if (ch === "}") {
|
|
126849
|
+
depth -= 1;
|
|
126850
|
+
if (depth === 0) {
|
|
126851
|
+
try {
|
|
126852
|
+
const parsed = JSON.parse(description.slice(jsonStart, i + 1));
|
|
126853
|
+
if (parsed && typeof parsed === "object" && parsed.repo && parsed.role) return parsed;
|
|
126854
|
+
} catch {
|
|
126855
|
+
return void 0;
|
|
126856
|
+
}
|
|
126857
|
+
return void 0;
|
|
126858
|
+
}
|
|
126859
|
+
}
|
|
126860
|
+
}
|
|
126861
|
+
return void 0;
|
|
126862
|
+
}
|
|
126863
|
+
|
|
126241
126864
|
// src/index.ts
|
|
126242
126865
|
function parseBooleanInput(value, defaultValue) {
|
|
126243
126866
|
const normalized = String(value || "").trim().toLowerCase();
|
|
@@ -126256,16 +126879,16 @@ function getInput(name, env = process.env) {
|
|
|
126256
126879
|
const runnerRaw = runnerName === normalizedName ? void 0 : env[runnerName];
|
|
126257
126880
|
const hasNormalized = normalizedRaw !== void 0;
|
|
126258
126881
|
const hasRunner = runnerRaw !== void 0;
|
|
126882
|
+
const normalizedValue = normalizeInputValue(normalizedRaw);
|
|
126883
|
+
const runnerValue = normalizeInputValue(runnerRaw);
|
|
126259
126884
|
if (hasNormalized && hasRunner) {
|
|
126260
|
-
|
|
126261
|
-
const runnerValue = normalizeInputValue(runnerRaw);
|
|
126262
|
-
if (normalizedValue !== runnerValue) {
|
|
126885
|
+
if (normalizedValue && runnerValue && normalizedValue !== runnerValue) {
|
|
126263
126886
|
throw new Error(
|
|
126264
126887
|
`Conflicting values for ${name}: ${normalizedName}=${JSON.stringify(normalizedValue)} vs ${runnerName}=${JSON.stringify(runnerValue)}`
|
|
126265
126888
|
);
|
|
126266
126889
|
}
|
|
126267
126890
|
}
|
|
126268
|
-
return
|
|
126891
|
+
return normalizedValue || runnerValue;
|
|
126269
126892
|
}
|
|
126270
126893
|
function hasInput(name, env = process.env) {
|
|
126271
126894
|
const normalizedName = `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
|
|
@@ -126324,6 +126947,17 @@ function parseCredentialPreflight(value) {
|
|
|
126324
126947
|
`Unsupported credential-preflight "${normalized}". Supported values: ${allowed.join(", ")}`
|
|
126325
126948
|
);
|
|
126326
126949
|
}
|
|
126950
|
+
function parseBranchStrategy(value) {
|
|
126951
|
+
const definition = postmanRepoSyncActionContract.inputs["branch-strategy"];
|
|
126952
|
+
const allowed = definition.allowedValues ?? [];
|
|
126953
|
+
const normalized = String(value || "").trim() || (definition.default ?? "legacy");
|
|
126954
|
+
if (allowed.includes(normalized)) {
|
|
126955
|
+
return normalized;
|
|
126956
|
+
}
|
|
126957
|
+
throw new Error(
|
|
126958
|
+
`Unsupported branch-strategy "${normalized}". Supported values: ${allowed.join(", ")}`
|
|
126959
|
+
);
|
|
126960
|
+
}
|
|
126327
126961
|
function normalizeReleaseLabel(value) {
|
|
126328
126962
|
const cleaned = normalizeInputValue(value).replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
|
|
126329
126963
|
return cleaned;
|
|
@@ -126368,6 +127002,7 @@ function resolveInputs(env = process.env) {
|
|
|
126368
127002
|
smokeCollectionId: getInput("smoke-collection-id", env),
|
|
126369
127003
|
contractCollectionId: getInput("contract-collection-id", env),
|
|
126370
127004
|
specId: getInput("spec-id", env),
|
|
127005
|
+
specContentChanged: parseBooleanInput(getInput("spec-content-changed", env), true),
|
|
126371
127006
|
specPath: getInput("spec-path", env),
|
|
126372
127007
|
collectionSyncMode: normalizeCollectionSyncMode(getInput("collection-sync-mode", env) || "refresh"),
|
|
126373
127008
|
specSyncMode: normalizeSpecSyncMode(getInput("spec-sync-mode", env) || "update"),
|
|
@@ -126390,6 +127025,10 @@ function resolveInputs(env = process.env) {
|
|
|
126390
127025
|
postmanApiKey: getInput("postman-api-key", env),
|
|
126391
127026
|
postmanAccessToken: getInput("postman-access-token", env),
|
|
126392
127027
|
credentialPreflight: parseCredentialPreflight(getInput("credential-preflight", env)),
|
|
127028
|
+
branchStrategy: parseBranchStrategy(getInput("branch-strategy", env)),
|
|
127029
|
+
canonicalBranch: getInput("canonical-branch", env) || void 0,
|
|
127030
|
+
channels: getInput("channels", env) || void 0,
|
|
127031
|
+
previewTtlDays: Math.max(1, Number.parseInt(getInput("preview-ttl", env) || "30", 10) || 30),
|
|
126393
127032
|
adoToken: getInput("ado-token", env) || normalizeInputValue(env.SYSTEM_ACCESSTOKEN),
|
|
126394
127033
|
githubToken: getInput("github-token", env),
|
|
126395
127034
|
ghFallbackToken: getInput("gh-fallback-token", env),
|
|
@@ -126412,10 +127051,45 @@ function resolveInputs(env = process.env) {
|
|
|
126412
127051
|
postmanStack,
|
|
126413
127052
|
postmanApiBase: endpointProfile.apiBaseUrl,
|
|
126414
127053
|
postmanBifrostBase: endpointProfile.bifrostBaseUrl,
|
|
127054
|
+
postmanFallbackBase: endpointProfile.fallbackBaseUrl,
|
|
126415
127055
|
postmanCliInstallUrl: endpointProfile.cliInstallUrl,
|
|
126416
127056
|
postmanIapubBase: endpointProfile.iapubBaseUrl
|
|
126417
127057
|
};
|
|
126418
127058
|
}
|
|
127059
|
+
function buildBranchAssetMarker(decision, inputs, now = /* @__PURE__ */ new Date()) {
|
|
127060
|
+
if (decision.tier !== "preview" && decision.tier !== "channel") {
|
|
127061
|
+
return void 0;
|
|
127062
|
+
}
|
|
127063
|
+
const rawBranch = decision.identity.headBranch;
|
|
127064
|
+
const repo = inputs.repoUrl || inputs.repository;
|
|
127065
|
+
if (!rawBranch || !repo) {
|
|
127066
|
+
return void 0;
|
|
127067
|
+
}
|
|
127068
|
+
const ttlMs = inputs.previewTtlDays * 24 * 60 * 60 * 1e3;
|
|
127069
|
+
return {
|
|
127070
|
+
repo,
|
|
127071
|
+
rawBranch,
|
|
127072
|
+
sanitizedBranch: buildBranchSlug(rawBranch).suffix,
|
|
127073
|
+
role: decision.tier,
|
|
127074
|
+
headSha: decision.identity.headSha,
|
|
127075
|
+
createdAt: now.toISOString(),
|
|
127076
|
+
lastSyncedAt: now.toISOString(),
|
|
127077
|
+
...decision.tier === "preview" ? { expiresAt: new Date(now.getTime() + ttlMs).toISOString() } : {}
|
|
127078
|
+
};
|
|
127079
|
+
}
|
|
127080
|
+
function assertBranchAssetIds(inputs, decision, branchOwnedIds = process.env.POSTMAN_BRANCH_ASSET_IDS === "owned") {
|
|
127081
|
+
if (decision.tier === "legacy" || decision.tier === "canonical" || branchOwnedIds) return;
|
|
127082
|
+
const provided = [
|
|
127083
|
+
["baseline-collection-id", inputs.baselineCollectionId],
|
|
127084
|
+
["smoke-collection-id", inputs.smokeCollectionId],
|
|
127085
|
+
["contract-collection-id", inputs.contractCollectionId]
|
|
127086
|
+
].filter(([, value]) => Boolean(value));
|
|
127087
|
+
if (provided.length > 0) {
|
|
127088
|
+
throw new Error(
|
|
127089
|
+
`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.`
|
|
127090
|
+
);
|
|
127091
|
+
}
|
|
127092
|
+
}
|
|
126419
127093
|
function buildEnvironmentValues(envName, baseUrl) {
|
|
126420
127094
|
return [
|
|
126421
127095
|
{ key: "baseUrl", value: baseUrl, type: "default" },
|
|
@@ -126428,12 +127102,48 @@ function buildEnvironmentValues(envName, baseUrl) {
|
|
|
126428
127102
|
];
|
|
126429
127103
|
}
|
|
126430
127104
|
var LEGACY_BASELINE_COLLECTION_PREFIX = "[Baseline]";
|
|
127105
|
+
var RESOURCES_STATE_VERSION = 2;
|
|
127106
|
+
var SUPPORTED_STATE_VERSIONS = /* @__PURE__ */ new Set([1, RESOURCES_STATE_VERSION]);
|
|
127107
|
+
var StateUnreadableError = class extends Error {
|
|
127108
|
+
code = "CONTRACT_STATE_UNREADABLE";
|
|
127109
|
+
constructor(message) {
|
|
127110
|
+
super(`CONTRACT_STATE_UNREADABLE: ${message}`);
|
|
127111
|
+
this.name = "StateUnreadableError";
|
|
127112
|
+
}
|
|
127113
|
+
};
|
|
126431
127114
|
function readResourcesState() {
|
|
127115
|
+
let raw;
|
|
126432
127116
|
try {
|
|
126433
|
-
|
|
127117
|
+
raw = (0, import_node_fs4.readFileSync)(".postman/resources.yaml", "utf8");
|
|
126434
127118
|
} catch {
|
|
126435
127119
|
return null;
|
|
126436
127120
|
}
|
|
127121
|
+
let parsed;
|
|
127122
|
+
try {
|
|
127123
|
+
parsed = load(raw);
|
|
127124
|
+
} catch (error) {
|
|
127125
|
+
throw new StateUnreadableError(
|
|
127126
|
+
`.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.`
|
|
127127
|
+
);
|
|
127128
|
+
}
|
|
127129
|
+
if (parsed === null || parsed === void 0) {
|
|
127130
|
+
return null;
|
|
127131
|
+
}
|
|
127132
|
+
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
127133
|
+
throw new StateUnreadableError(
|
|
127134
|
+
".postman/resources.yaml exists but does not contain a YAML mapping. Fix or delete the file; refusing to treat tracked state as absent."
|
|
127135
|
+
);
|
|
127136
|
+
}
|
|
127137
|
+
const state = parsed;
|
|
127138
|
+
if (state.version !== void 0 && !SUPPORTED_STATE_VERSIONS.has(Number(state.version))) {
|
|
127139
|
+
throw new StateUnreadableError(
|
|
127140
|
+
`.postman/resources.yaml declares unsupported state version ${String(state.version)} (supported: 1, ${RESOURCES_STATE_VERSION}). Upgrade the action or fix the file.`
|
|
127141
|
+
);
|
|
127142
|
+
}
|
|
127143
|
+
if (state.canonical && !state.cloudResources) {
|
|
127144
|
+
state.cloudResources = { ...state.canonical };
|
|
127145
|
+
}
|
|
127146
|
+
return state;
|
|
126437
127147
|
}
|
|
126438
127148
|
function findCloudResourceId(map, matcher) {
|
|
126439
127149
|
if (!map) {
|
|
@@ -126468,7 +127178,7 @@ function isOpenApiSpecFile(filePath) {
|
|
|
126468
127178
|
return false;
|
|
126469
127179
|
}
|
|
126470
127180
|
try {
|
|
126471
|
-
const raw = (0,
|
|
127181
|
+
const raw = (0, import_node_fs4.readFileSync)(filePath, "utf8");
|
|
126472
127182
|
const parsed = filePath.endsWith(".json") ? JSON.parse(raw) : load(raw);
|
|
126473
127183
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
126474
127184
|
return false;
|
|
@@ -126497,7 +127207,7 @@ function scanLocalSpecReferences(baseDir = ".") {
|
|
|
126497
127207
|
const found = /* @__PURE__ */ new Set();
|
|
126498
127208
|
const refs = [];
|
|
126499
127209
|
const visit2 = (currentDir) => {
|
|
126500
|
-
for (const entry of (0,
|
|
127210
|
+
for (const entry of (0, import_node_fs4.readdirSync)(currentDir, { withFileTypes: true })) {
|
|
126501
127211
|
if (ignoredDirs.has(entry.name)) {
|
|
126502
127212
|
continue;
|
|
126503
127213
|
}
|
|
@@ -126527,7 +127237,7 @@ function resolveMappedSpecReference(explicitSpecPath, discoveredSpecs) {
|
|
|
126527
127237
|
const normalizedExplicitPath = normalizeToPosix(explicitSpecPath.trim());
|
|
126528
127238
|
if (normalizedExplicitPath) {
|
|
126529
127239
|
const explicitFullPath = path3.resolve(normalizedExplicitPath);
|
|
126530
|
-
if ((0,
|
|
127240
|
+
if ((0, import_node_fs4.existsSync)(explicitFullPath) && (0, import_node_fs4.statSync)(explicitFullPath).isFile()) {
|
|
126531
127241
|
return {
|
|
126532
127242
|
repoRelativePath: normalizedExplicitPath,
|
|
126533
127243
|
configRelativePath: normalizeToPosix(path3.join("..", normalizedExplicitPath))
|
|
@@ -126554,7 +127264,11 @@ function createOutputs(inputs) {
|
|
|
126554
127264
|
"mock-url": "",
|
|
126555
127265
|
"monitor-id": "",
|
|
126556
127266
|
"repo-sync-summary-json": "{}",
|
|
126557
|
-
"commit-sha": ""
|
|
127267
|
+
"commit-sha": "",
|
|
127268
|
+
"sync-status": "",
|
|
127269
|
+
"branch-decision": "",
|
|
127270
|
+
"spec-version-tag": "",
|
|
127271
|
+
"spec-version-url": ""
|
|
126558
127272
|
};
|
|
126559
127273
|
}
|
|
126560
127274
|
function buildGhCliEnv(env, token) {
|
|
@@ -126579,7 +127293,7 @@ function buildGhCliEnv(env, token) {
|
|
|
126579
127293
|
}
|
|
126580
127294
|
return filtered;
|
|
126581
127295
|
}
|
|
126582
|
-
async function upsertEnvironments(inputs, dependencies, resourcesState) {
|
|
127296
|
+
async function upsertEnvironments(inputs, dependencies, resourcesState, assetMarker) {
|
|
126583
127297
|
const envUids = {
|
|
126584
127298
|
...getEnvironmentUidsFromResources(resourcesState),
|
|
126585
127299
|
...inputs.environmentUids
|
|
@@ -126589,7 +127303,6 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
|
|
|
126589
127303
|
}
|
|
126590
127304
|
for (const envName of inputs.environments) {
|
|
126591
127305
|
const runtimeUrl = String(inputs.envRuntimeUrls[envName] || "").trim();
|
|
126592
|
-
const values = buildEnvironmentValues(envName, runtimeUrl);
|
|
126593
127306
|
const displayName = `${inputs.projectName} - ${envName}`;
|
|
126594
127307
|
let existingUid = String(envUids[envName] || "").trim();
|
|
126595
127308
|
if (!existingUid) {
|
|
@@ -126605,10 +127318,27 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
|
|
|
126605
127318
|
}
|
|
126606
127319
|
}
|
|
126607
127320
|
if (existingUid) {
|
|
126608
|
-
|
|
127321
|
+
let marker = assetMarker;
|
|
127322
|
+
if (marker) {
|
|
127323
|
+
try {
|
|
127324
|
+
const existing = await dependencies.postman.getEnvironment(existingUid);
|
|
127325
|
+
const values3 = existing.data?.values ?? existing.values ?? [];
|
|
127326
|
+
const prior = values3.find((value) => value.key === "x-pm-onboarding")?.value;
|
|
127327
|
+
const priorMarker = parseAssetMarker(prior ? `x-pm-onboarding: ${prior}` : void 0);
|
|
127328
|
+
if (priorMarker?.repo === marker.repo && priorMarker.rawBranch === marker.rawBranch) {
|
|
127329
|
+
marker = { ...marker, createdAt: priorMarker.createdAt };
|
|
127330
|
+
}
|
|
127331
|
+
} catch {
|
|
127332
|
+
}
|
|
127333
|
+
}
|
|
127334
|
+
const values2 = buildEnvironmentValues(envName, runtimeUrl);
|
|
127335
|
+
if (marker) values2.push({ key: "x-pm-onboarding", value: JSON.stringify(marker), type: "default" });
|
|
127336
|
+
await dependencies.postman.updateEnvironment(existingUid, displayName, values2);
|
|
126609
127337
|
envUids[envName] = existingUid;
|
|
126610
127338
|
continue;
|
|
126611
127339
|
}
|
|
127340
|
+
const values = buildEnvironmentValues(envName, runtimeUrl);
|
|
127341
|
+
if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
|
|
126612
127342
|
envUids[envName] = await dependencies.postman.createEnvironment(
|
|
126613
127343
|
inputs.workspaceId,
|
|
126614
127344
|
displayName,
|
|
@@ -126618,7 +127348,7 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
|
|
|
126618
127348
|
return envUids;
|
|
126619
127349
|
}
|
|
126620
127350
|
function ensureDir(path5) {
|
|
126621
|
-
(0,
|
|
127351
|
+
(0, import_node_fs4.mkdirSync)(path5, { recursive: true });
|
|
126622
127352
|
}
|
|
126623
127353
|
function getCollectionDirectoryName(kind, projectName) {
|
|
126624
127354
|
if (kind === "Baseline") {
|
|
@@ -126655,7 +127385,7 @@ function stripVolatileFields(obj) {
|
|
|
126655
127385
|
}
|
|
126656
127386
|
function writeJsonFile(path5, content, normalize3 = false) {
|
|
126657
127387
|
const data = normalize3 ? stripVolatileFields(content) : content;
|
|
126658
|
-
(0,
|
|
127388
|
+
(0, import_node_fs4.writeFileSync)(path5, JSON.stringify(data, null, 2));
|
|
126659
127389
|
}
|
|
126660
127390
|
function buildMappedSpecCloudKey(mappedSource, specSyncMode, releaseLabel) {
|
|
126661
127391
|
if (specSyncMode !== "version") {
|
|
@@ -126679,31 +127409,30 @@ function resolveDurableWorkspaceId(options) {
|
|
|
126679
127409
|
}
|
|
126680
127410
|
return prior === candidate ? prior : void 0;
|
|
126681
127411
|
}
|
|
126682
|
-
function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs) {
|
|
126683
|
-
const manifest = {};
|
|
127412
|
+
function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs, priorState) {
|
|
127413
|
+
const manifest = { ...priorState ?? {} };
|
|
127414
|
+
delete manifest.version;
|
|
127415
|
+
delete manifest.workspace;
|
|
127416
|
+
delete manifest.localResources;
|
|
127417
|
+
delete manifest.cloudResources;
|
|
127418
|
+
delete manifest.canonical;
|
|
127419
|
+
manifest.version = RESOURCES_STATE_VERSION;
|
|
126684
127420
|
if (workspaceId) {
|
|
126685
127421
|
manifest.workspace = { id: workspaceId };
|
|
126686
127422
|
}
|
|
126687
|
-
const localResources = {};
|
|
126688
127423
|
const cloudResources = {};
|
|
126689
127424
|
const collectionKeys = Object.keys(collectionMap);
|
|
126690
127425
|
if (collectionKeys.length > 0) {
|
|
126691
|
-
localResources.collections = collectionKeys;
|
|
126692
127426
|
cloudResources.collections = collectionMap;
|
|
126693
127427
|
}
|
|
126694
127428
|
const envEntries = Object.entries(envMap);
|
|
126695
127429
|
if (envEntries.length > 0) {
|
|
126696
|
-
localResources.environments = envEntries.map(
|
|
126697
|
-
([envName]) => `../${artifactDir}/environments/${envName}.postman_environment.json`
|
|
126698
|
-
);
|
|
126699
127430
|
cloudResources.environments = {};
|
|
126700
127431
|
for (const [envName, envUid] of envEntries) {
|
|
126701
127432
|
cloudResources.environments[`../${artifactDir}/environments/${envName}.postman_environment.json`] = envUid;
|
|
126702
127433
|
}
|
|
126703
127434
|
}
|
|
126704
|
-
|
|
126705
|
-
localResources.specs = localSpecRefs;
|
|
126706
|
-
}
|
|
127435
|
+
void localSpecRefs;
|
|
126707
127436
|
const specs = { ...existingSpecs || {} };
|
|
126708
127437
|
if (mappedSpecRef && specId) {
|
|
126709
127438
|
specs[mappedSpecRef] = specId;
|
|
@@ -126711,11 +127440,8 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
|
|
|
126711
127440
|
if (Object.keys(specs).length > 0) {
|
|
126712
127441
|
cloudResources.specs = specs;
|
|
126713
127442
|
}
|
|
126714
|
-
if (Object.keys(localResources).length > 0) {
|
|
126715
|
-
manifest.localResources = localResources;
|
|
126716
|
-
}
|
|
126717
127443
|
if (Object.keys(cloudResources).length > 0) {
|
|
126718
|
-
manifest.
|
|
127444
|
+
manifest.canonical = cloudResources;
|
|
126719
127445
|
}
|
|
126720
127446
|
return dump(manifest, {
|
|
126721
127447
|
lineWidth: -1,
|
|
@@ -126756,21 +127482,21 @@ function assertPathWithinCwd(targetPath, fieldName) {
|
|
|
126756
127482
|
if (!rawPath || hasControlCharacter2(originalPath) || path3.isAbsolute(rawPath) || path3.win32.isAbsolute(rawPath) || segments.includes("..") || rawPath.startsWith(":") || hasControlCharacter2(rawPath)) {
|
|
126757
127483
|
throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
|
|
126758
127484
|
}
|
|
126759
|
-
const base = (0,
|
|
127485
|
+
const base = (0, import_node_fs4.realpathSync)(process.cwd());
|
|
126760
127486
|
const resolved = path3.resolve(base, rawPath);
|
|
126761
127487
|
const relative3 = path3.relative(base, resolved);
|
|
126762
127488
|
if (relative3.startsWith("..") || path3.isAbsolute(relative3)) {
|
|
126763
127489
|
throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
|
|
126764
127490
|
}
|
|
126765
127491
|
let existingPath = resolved;
|
|
126766
|
-
while (!(0,
|
|
127492
|
+
while (!(0, import_node_fs4.existsSync)(existingPath)) {
|
|
126767
127493
|
const parent = path3.dirname(existingPath);
|
|
126768
127494
|
if (parent === existingPath) {
|
|
126769
127495
|
break;
|
|
126770
127496
|
}
|
|
126771
127497
|
existingPath = parent;
|
|
126772
127498
|
}
|
|
126773
|
-
const realExistingPath = (0,
|
|
127499
|
+
const realExistingPath = (0, import_node_fs4.realpathSync)(existingPath);
|
|
126774
127500
|
const realRelative = path3.relative(base, realExistingPath);
|
|
126775
127501
|
if (realRelative.startsWith("..") || path3.isAbsolute(realRelative)) {
|
|
126776
127502
|
throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
|
|
@@ -126798,8 +127524,8 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
|
|
|
126798
127524
|
ensureDir(specsDir);
|
|
126799
127525
|
ensureDir(".postman");
|
|
126800
127526
|
const globalsFilePath = `${globalsDir}/workspace.globals.yaml`;
|
|
126801
|
-
if (!(0,
|
|
126802
|
-
(0,
|
|
127527
|
+
if (!(0, import_node_fs4.existsSync)(globalsFilePath)) {
|
|
127528
|
+
(0, import_node_fs4.writeFileSync)(globalsFilePath, "name: Globals\nvalues: []\n");
|
|
126803
127529
|
}
|
|
126804
127530
|
if (inputs.generateCiWorkflow) {
|
|
126805
127531
|
const ciDir = inputs.ciWorkflowPath.split("/").slice(0, -1).join("/");
|
|
@@ -126846,7 +127572,7 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
|
|
|
126846
127572
|
workspaceLinkEnabled: inputs.workspaceLinkEnabled,
|
|
126847
127573
|
workspaceLinkStatus: options.workspaceLinkStatus
|
|
126848
127574
|
});
|
|
126849
|
-
(0,
|
|
127575
|
+
(0, import_node_fs4.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
|
|
126850
127576
|
durableWorkspaceId,
|
|
126851
127577
|
manifestCollections,
|
|
126852
127578
|
envUids,
|
|
@@ -126854,10 +127580,11 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
|
|
|
126854
127580
|
discoveredSpecs.map((spec) => spec.configRelativePath),
|
|
126855
127581
|
mappedSpecCloudKey,
|
|
126856
127582
|
inputs.specId || void 0,
|
|
126857
|
-
options.existingSpecs
|
|
127583
|
+
options.existingSpecs,
|
|
127584
|
+
options.priorState
|
|
126858
127585
|
));
|
|
126859
127586
|
if (mappedSpec && Object.keys(manifestCollections).length > 0) {
|
|
126860
|
-
(0,
|
|
127587
|
+
(0, import_node_fs4.writeFileSync)(
|
|
126861
127588
|
".postman/workflows.yaml",
|
|
126862
127589
|
buildSpecCollectionWorkflowManifest(
|
|
126863
127590
|
mappedSpec.configRelativePath,
|
|
@@ -126902,19 +127629,29 @@ async function commitAndPushGeneratedFiles(inputs, dependencies) {
|
|
|
126902
127629
|
const dir = parts.slice(0, -1).join("/");
|
|
126903
127630
|
ensureDir(dir);
|
|
126904
127631
|
}
|
|
126905
|
-
(0,
|
|
127632
|
+
(0, import_node_fs4.writeFileSync)(inputs.ciWorkflowPath, ciWorkflow);
|
|
127633
|
+
if (inputs.provider === "github" || inputs.provider === "unknown") {
|
|
127634
|
+
const gcPath = ".github/workflows/postman-preview-gc.yml";
|
|
127635
|
+
if (inputs.ciWorkflowPath.endsWith(".github/workflows/ci.yml") || inputs.ciWorkflowPath === ".github/workflows/ci.yml") {
|
|
127636
|
+
ensureDir(".github/workflows");
|
|
127637
|
+
(0, import_node_fs4.writeFileSync)(gcPath, renderGcWorkflowTemplate());
|
|
127638
|
+
}
|
|
127639
|
+
}
|
|
126906
127640
|
}
|
|
126907
127641
|
if (!dependencies.repoMutation || inputs.repoWriteMode === "none") {
|
|
126908
127642
|
return { commitSha: "", resolvedCurrentRef: "", pushed: false };
|
|
126909
127643
|
}
|
|
126910
127644
|
const provisionPath = ".github/workflows/provision.yml";
|
|
126911
|
-
const provisionExists = inputs.provider === "github" && (0,
|
|
127645
|
+
const provisionExists = inputs.provider === "github" && (0, import_node_fs4.existsSync)(provisionPath);
|
|
127646
|
+
const gcWorkflowPath = ".github/workflows/postman-preview-gc.yml";
|
|
127647
|
+
const gcExists = inputs.generateCiWorkflow && (0, import_node_fs4.existsSync)(gcWorkflowPath);
|
|
126912
127648
|
const stagePaths = [
|
|
126913
127649
|
inputs.artifactDir,
|
|
126914
127650
|
".postman",
|
|
126915
127651
|
inputs.generateCiWorkflow ? inputs.ciWorkflowPath : null,
|
|
127652
|
+
gcExists ? gcWorkflowPath : null,
|
|
126916
127653
|
provisionExists ? provisionPath : null
|
|
126917
|
-
].filter((entry) => typeof entry === "string" && ((0,
|
|
127654
|
+
].filter((entry) => typeof entry === "string" && ((0, import_node_fs4.existsSync)(entry) || entry === provisionPath));
|
|
126918
127655
|
if (stagePaths.length === 0) {
|
|
126919
127656
|
dependencies.core.info("No generated repository paths were found; skipping repo mutation.");
|
|
126920
127657
|
return {
|
|
@@ -126962,6 +127699,31 @@ async function runRepoSync(inputs, dependencies) {
|
|
|
126962
127699
|
}
|
|
126963
127700
|
}
|
|
126964
127701
|
async function runRepoSyncInner(inputs, dependencies) {
|
|
127702
|
+
const branchDecision = decideBranchTier(inputs);
|
|
127703
|
+
assertBranchAssetIds(inputs, branchDecision);
|
|
127704
|
+
const isCanonicalWriter = branchDecision.tier === "legacy" || branchDecision.tier === "canonical";
|
|
127705
|
+
if (!isCanonicalWriter) {
|
|
127706
|
+
if (branchDecision.tier === "preview" && branchDecision.identity.headBranch) {
|
|
127707
|
+
inputs = {
|
|
127708
|
+
...inputs,
|
|
127709
|
+
projectName: previewAssetName(inputs.projectName, branchDecision.identity.headBranch),
|
|
127710
|
+
workspaceLinkEnabled: false,
|
|
127711
|
+
environmentSyncEnabled: false,
|
|
127712
|
+
repoWriteMode: "none"
|
|
127713
|
+
};
|
|
127714
|
+
} else if (branchDecision.tier === "channel" && branchDecision.channel) {
|
|
127715
|
+
inputs = {
|
|
127716
|
+
...inputs,
|
|
127717
|
+
projectName: channelAssetName(inputs.projectName, branchDecision.channel.code),
|
|
127718
|
+
workspaceLinkEnabled: false,
|
|
127719
|
+
environmentSyncEnabled: false,
|
|
127720
|
+
repoWriteMode: "none"
|
|
127721
|
+
};
|
|
127722
|
+
}
|
|
127723
|
+
dependencies.core.info(
|
|
127724
|
+
`branch-aware sync: ${branchDecision.tier} run \u2014 branch-scoped asset set "${inputs.projectName}", no workspace repo-link mutation, no state write-back`
|
|
127725
|
+
);
|
|
127726
|
+
}
|
|
126965
127727
|
const outputs = createOutputs(inputs);
|
|
126966
127728
|
const versionRequested = inputs.collectionSyncMode === "version" || inputs.specSyncMode === "version";
|
|
126967
127729
|
const releaseLabel = deriveReleaseLabel(inputs);
|
|
@@ -126969,7 +127731,8 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
126969
127731
|
throw new Error("release-label is required when collection-sync-mode or spec-sync-mode is version");
|
|
126970
127732
|
}
|
|
126971
127733
|
const assetProjectName = createAssetProjectName(inputs, releaseLabel);
|
|
126972
|
-
const
|
|
127734
|
+
const trackedState = readResourcesState();
|
|
127735
|
+
const resourcesState = isCanonicalWriter ? trackedState : trackedState?.workspace ? { workspace: trackedState.workspace } : null;
|
|
126973
127736
|
if (resourcesState) {
|
|
126974
127737
|
if (!inputs.workspaceId && resourcesState.workspace?.id) {
|
|
126975
127738
|
inputs.workspaceId = resourcesState.workspace.id;
|
|
@@ -126995,7 +127758,8 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
126995
127758
|
}
|
|
126996
127759
|
}
|
|
126997
127760
|
}
|
|
126998
|
-
const
|
|
127761
|
+
const branchAssetMarker = buildBranchAssetMarker(branchDecision, inputs);
|
|
127762
|
+
const envUids = await upsertEnvironments(inputs, dependencies, resourcesState, branchAssetMarker);
|
|
126999
127763
|
outputs["environment-uids-json"] = JSON.stringify(envUids);
|
|
127000
127764
|
if (inputs.environmentSyncEnabled && inputs.workspaceId && dependencies.internalIntegration) {
|
|
127001
127765
|
const associations = Object.entries(envUids).map(([envName, envUid]) => ({
|
|
@@ -127051,11 +127815,23 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
127051
127815
|
}
|
|
127052
127816
|
}
|
|
127053
127817
|
if (!resolvedMockUrl) {
|
|
127054
|
-
const mock = await
|
|
127055
|
-
|
|
127056
|
-
|
|
127057
|
-
|
|
127058
|
-
|
|
127818
|
+
const mock = await retry(
|
|
127819
|
+
() => dependencies.postman.createMock(
|
|
127820
|
+
inputs.workspaceId,
|
|
127821
|
+
mockName,
|
|
127822
|
+
inputs.baselineCollectionId,
|
|
127823
|
+
mockEnvUid
|
|
127824
|
+
),
|
|
127825
|
+
{
|
|
127826
|
+
maxAttempts: 3,
|
|
127827
|
+
delayMs: 2e3,
|
|
127828
|
+
backoffMultiplier: 2,
|
|
127829
|
+
onRetry: ({ attempt, error }) => {
|
|
127830
|
+
dependencies.core.warning(
|
|
127831
|
+
`Mock create attempt ${attempt} failed (${error instanceof Error ? error.message : String(error)}); retrying`
|
|
127832
|
+
);
|
|
127833
|
+
}
|
|
127834
|
+
}
|
|
127059
127835
|
);
|
|
127060
127836
|
resolvedMockUrl = mock.url;
|
|
127061
127837
|
dependencies.core.info(`Created new mock: ${resolvedMockUrl}`);
|
|
@@ -127124,16 +127900,19 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
127124
127900
|
);
|
|
127125
127901
|
} catch (error) {
|
|
127126
127902
|
outputs["workspace-link-status"] = "failed";
|
|
127127
|
-
|
|
127128
|
-
|
|
127129
|
-
|
|
127903
|
+
const message = `Workspace link failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
127904
|
+
if (branchDecision.tier === "canonical") {
|
|
127905
|
+
throw new Error(message, { cause: error });
|
|
127906
|
+
}
|
|
127907
|
+
dependencies.core.warning(message);
|
|
127130
127908
|
}
|
|
127131
127909
|
}
|
|
127132
127910
|
await exportArtifacts(inputs, dependencies, envUids, assetProjectName, {
|
|
127133
127911
|
workspaceLinkStatus: outputs["workspace-link-status"],
|
|
127134
127912
|
priorWorkspaceId: resourcesState?.workspace?.id,
|
|
127135
127913
|
existingSpecs: resourcesState?.cloudResources?.specs,
|
|
127136
|
-
releaseLabel
|
|
127914
|
+
releaseLabel,
|
|
127915
|
+
priorState: resourcesState
|
|
127137
127916
|
});
|
|
127138
127917
|
const commit = await commitAndPushGeneratedFiles(inputs, dependencies);
|
|
127139
127918
|
outputs["commit-sha"] = commit.commitSha;
|
|
@@ -127141,6 +127920,38 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
127141
127920
|
outputs["resolved-current-ref"] = commit.resolvedCurrentRef;
|
|
127142
127921
|
}
|
|
127143
127922
|
outputs["repo-sync-summary-json"] = createRepoSummary(outputs, envUids, commit.pushed);
|
|
127923
|
+
if (branchDecision.tier === "canonical" && inputs.specId && inputs.specContentChanged !== false && dependencies.postman.tagSpecVersion) {
|
|
127924
|
+
const shortSha = (branchDecision.identity.headSha ?? "").slice(0, 7);
|
|
127925
|
+
const tagName = inputs.releaseLabel ? `${inputs.releaseLabel}${shortSha ? ` (${shortSha})` : ""}` : shortSha || `sync-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
127926
|
+
try {
|
|
127927
|
+
const tag = await dependencies.postman.tagSpecVersion(inputs.specId, tagName);
|
|
127928
|
+
outputs["spec-version-tag"] = tag.name || tagName;
|
|
127929
|
+
if (tag.id) {
|
|
127930
|
+
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)}`;
|
|
127931
|
+
}
|
|
127932
|
+
dependencies.core.info(`Tagged spec version at finalize: ${tag.name || tagName}`);
|
|
127933
|
+
} catch (error) {
|
|
127934
|
+
const status = error.status;
|
|
127935
|
+
if (status === 409) {
|
|
127936
|
+
const tags = await dependencies.postman.listSpecVersionTags?.(inputs.specId).catch(() => []) ?? [];
|
|
127937
|
+
const latest = tags[0];
|
|
127938
|
+
if (latest && (latest.name === tagName || /\([0-9a-f]{7}\)$/.test(latest.name) || /^[0-9a-f]{7}$/.test(latest.name))) {
|
|
127939
|
+
outputs["spec-version-tag"] = latest.name;
|
|
127940
|
+
outputs["spec-version-url"] = `https://web.postman.co/workspace/${encodeURIComponent(inputs.workspaceId)}/specification/${encodeURIComponent(inputs.specId)}?tagId=${encodeURIComponent(latest.id)}&versionLabel=${encodeURIComponent(latest.name)}`;
|
|
127941
|
+
dependencies.core.info(`Latest changelog group already tagged as "${latest.name}"; adopting.`);
|
|
127942
|
+
} else {
|
|
127943
|
+
dependencies.core.warning("Latest changelog group already carries a hand-applied tag; leaving it in place.");
|
|
127944
|
+
}
|
|
127945
|
+
} else {
|
|
127946
|
+
dependencies.core.warning(`Spec version tagging failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);
|
|
127947
|
+
}
|
|
127948
|
+
}
|
|
127949
|
+
}
|
|
127950
|
+
if (inputs.branchStrategy !== "legacy" || process.env[BRANCH_DECISION_ENV]) {
|
|
127951
|
+
const decision = decideBranchTier(inputs);
|
|
127952
|
+
outputs["sync-status"] = "synced";
|
|
127953
|
+
outputs["branch-decision"] = serializeBranchDecision(decision);
|
|
127954
|
+
}
|
|
127144
127955
|
for (const [name, value] of Object.entries(outputs)) {
|
|
127145
127956
|
dependencies.core.setOutput(name, value);
|
|
127146
127957
|
}
|
|
@@ -127244,6 +128055,8 @@ async function resolvePostmanApiKeyAndTeamId(inputs, actionCore, actionExec, mas
|
|
|
127244
128055
|
const gateway = new AccessTokenGatewayClient({
|
|
127245
128056
|
tokenProvider,
|
|
127246
128057
|
bifrostBaseUrl: inputs.postmanBifrostBase,
|
|
128058
|
+
// No fallback on the org-mode probe: the expected non-org 400 must
|
|
128059
|
+
// surface verbatim, not be re-fired against the /_api alias.
|
|
127247
128060
|
secretMasker: masker
|
|
127248
128061
|
});
|
|
127249
128062
|
const squads = await gateway.getSquads(teamId);
|
|
@@ -127299,6 +128112,7 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
|
|
|
127299
128112
|
const gateway = new AccessTokenGatewayClient({
|
|
127300
128113
|
tokenProvider,
|
|
127301
128114
|
bifrostBaseUrl: inputs.postmanBifrostBase,
|
|
128115
|
+
fallbackBaseUrl: inputs.postmanFallbackBase,
|
|
127302
128116
|
teamId: resolved.teamId,
|
|
127303
128117
|
orgMode: inputs.orgMode,
|
|
127304
128118
|
secretMasker: masker
|
|
@@ -127330,7 +128144,19 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
|
|
|
127330
128144
|
listMonitors: gatewayAssets.listMonitors.bind(gatewayAssets),
|
|
127331
128145
|
monitorExists: gatewayAssets.monitorExists.bind(gatewayAssets),
|
|
127332
128146
|
findMonitorByCollection: gatewayAssets.findMonitorByCollection.bind(gatewayAssets),
|
|
127333
|
-
runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets)
|
|
128147
|
+
runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets),
|
|
128148
|
+
listEnvironments: gatewayAssets.listEnvironments.bind(gatewayAssets),
|
|
128149
|
+
// GC path (preview/channel retention machine — lib/repo/preview-gc.ts).
|
|
128150
|
+
deleteEnvironment: gatewayAssets.deleteEnvironment.bind(gatewayAssets),
|
|
128151
|
+
deleteMock: gatewayAssets.deleteMock.bind(gatewayAssets),
|
|
128152
|
+
deleteMonitor: gatewayAssets.deleteMonitor.bind(gatewayAssets),
|
|
128153
|
+
deleteCollection: gatewayAssets.deleteCollection.bind(gatewayAssets),
|
|
128154
|
+
listSpecifications: gatewayAssets.listSpecifications.bind(gatewayAssets),
|
|
128155
|
+
getSpecContent: gatewayAssets.getSpecContent.bind(gatewayAssets),
|
|
128156
|
+
listSpecCollections: gatewayAssets.listSpecCollections.bind(gatewayAssets),
|
|
128157
|
+
deleteSpec: gatewayAssets.deleteSpec.bind(gatewayAssets),
|
|
128158
|
+
tagSpecVersion: gatewayAssets.tagSpecVersion.bind(gatewayAssets),
|
|
128159
|
+
listSpecVersionTags: gatewayAssets.listSpecVersionTags.bind(gatewayAssets)
|
|
127334
128160
|
};
|
|
127335
128161
|
const repoMutation = repository && (inputs.repoWriteMode === "commit-only" || inputs.repoWriteMode === "commit-and-push") ? new RepoMutationService({
|
|
127336
128162
|
provider: inputs.provider,
|
|
@@ -127367,6 +128193,400 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
|
|
|
127367
128193
|
repoMutation
|
|
127368
128194
|
};
|
|
127369
128195
|
}
|
|
128196
|
+
function decideBranchTier(inputs, env = process.env) {
|
|
128197
|
+
return resolveEffectiveBranchDecision(
|
|
128198
|
+
{
|
|
128199
|
+
strategy: inputs.branchStrategy,
|
|
128200
|
+
identity: resolveBranchIdentity(env, { defaultBranch: inputs.canonicalBranch }),
|
|
128201
|
+
canonicalBranch: inputs.canonicalBranch,
|
|
128202
|
+
channels: parseChannelRules(inputs.channels)
|
|
128203
|
+
},
|
|
128204
|
+
env
|
|
128205
|
+
);
|
|
128206
|
+
}
|
|
128207
|
+
function runGatedSkip(inputs, decision, actionCore) {
|
|
128208
|
+
actionCore.info(
|
|
128209
|
+
`branch-aware sync: gated run (${decision.reason}) \u2014 repo-sync skipped, zero workspace writes`
|
|
128210
|
+
);
|
|
128211
|
+
const outputs = createOutputs(inputs);
|
|
128212
|
+
outputs["sync-status"] = "skipped-branch-gate";
|
|
128213
|
+
outputs["branch-decision"] = serializeBranchDecision(decision);
|
|
128214
|
+
outputs["repo-sync-summary-json"] = JSON.stringify({
|
|
128215
|
+
status: "skipped-branch-gate",
|
|
128216
|
+
reason: decision.reason
|
|
128217
|
+
});
|
|
128218
|
+
for (const [name, value] of Object.entries(outputs)) {
|
|
128219
|
+
actionCore.setOutput(name, value);
|
|
128220
|
+
}
|
|
128221
|
+
return outputs;
|
|
128222
|
+
}
|
|
128223
|
+
|
|
128224
|
+
// src/lib/repo/preview-gc.ts
|
|
128225
|
+
function looksGenerated(name) {
|
|
128226
|
+
return / @[A-Za-z0-9._-]+/.test(name) || /^\[[A-Z][A-Z0-9]*\] /.test(name);
|
|
128227
|
+
}
|
|
128228
|
+
function resolveMarker(candidate) {
|
|
128229
|
+
return candidate.marker ?? parseAssetMarker(candidate.description);
|
|
128230
|
+
}
|
|
128231
|
+
function stampChannelRetirement(marker, reason, now, previewTtlDays) {
|
|
128232
|
+
const deleteAfter = new Date(now.getTime() + previewTtlDays * 24 * 60 * 60 * 1e3);
|
|
128233
|
+
return {
|
|
128234
|
+
...marker,
|
|
128235
|
+
retirementDetectedAt: now.toISOString(),
|
|
128236
|
+
retirementReason: reason,
|
|
128237
|
+
deleteAfter: deleteAfter.toISOString()
|
|
128238
|
+
};
|
|
128239
|
+
}
|
|
128240
|
+
function clearChannelRetirement(marker) {
|
|
128241
|
+
const next = { ...marker };
|
|
128242
|
+
delete next.retirementDetectedAt;
|
|
128243
|
+
delete next.retirementReason;
|
|
128244
|
+
delete next.deleteAfter;
|
|
128245
|
+
return next;
|
|
128246
|
+
}
|
|
128247
|
+
function decideRetention(candidate, context) {
|
|
128248
|
+
const marker = resolveMarker(candidate);
|
|
128249
|
+
if (!marker) {
|
|
128250
|
+
if (looksGenerated(candidate.name)) {
|
|
128251
|
+
return { action: "orphan-audit", reason: "generated-looking name with missing or invalid marker" };
|
|
128252
|
+
}
|
|
128253
|
+
return { action: "stranger", reason: "no marker; not our asset" };
|
|
128254
|
+
}
|
|
128255
|
+
if (marker.repo !== context.repo) {
|
|
128256
|
+
return { action: "stranger", reason: `marker repo ${marker.repo} does not match ${context.repo}` };
|
|
128257
|
+
}
|
|
128258
|
+
if (context.triggerGeneration && marker.createdAt) {
|
|
128259
|
+
const created = new Date(marker.createdAt);
|
|
128260
|
+
if (!Number.isNaN(created.getTime()) && created.getTime() > context.triggerGeneration.getTime()) {
|
|
128261
|
+
return { action: "retain", reason: "marker generation is newer than the GC trigger (name reuse)" };
|
|
128262
|
+
}
|
|
128263
|
+
}
|
|
128264
|
+
if (marker.role === "channel") {
|
|
128265
|
+
if (marker.retirementReason && marker.deleteAfter) {
|
|
128266
|
+
const existence3 = context.branchExists(marker.rawBranch);
|
|
128267
|
+
const mapped = context.channelMapped?.(marker.rawBranch);
|
|
128268
|
+
const restored = marker.retirementReason === "branch-deleted" ? existence3 === "exists" && mapped !== false : existence3 === "exists" && mapped === true;
|
|
128269
|
+
if (restored) {
|
|
128270
|
+
return { action: "restore", reason: "retired channel branch and mapping are active again", branchProbe: "exists" };
|
|
128271
|
+
}
|
|
128272
|
+
const deleteAfter = new Date(marker.deleteAfter);
|
|
128273
|
+
if (!Number.isNaN(deleteAfter.getTime()) && context.now.getTime() >= deleteAfter.getTime()) {
|
|
128274
|
+
return { action: "delete", reason: `retired channel set past deleteAfter (${marker.retirementReason})` };
|
|
128275
|
+
}
|
|
128276
|
+
return { action: "retain", reason: "retired channel set inside deleteAfter window" };
|
|
128277
|
+
}
|
|
128278
|
+
const existence2 = context.branchExists(marker.rawBranch);
|
|
128279
|
+
if (existence2 === "deleted") {
|
|
128280
|
+
return { action: "retire", reason: "channel branch deleted; start retirement window", branchProbe: "deleted", retirementReason: "branch-deleted" };
|
|
128281
|
+
}
|
|
128282
|
+
if (context.channelMapped?.(marker.rawBranch) === false) {
|
|
128283
|
+
return { action: "retire", reason: "channel mapping removed; start retirement window", branchProbe: existence2, retirementReason: "mapping-removed" };
|
|
128284
|
+
}
|
|
128285
|
+
return { action: "retain", reason: "active channel set (channels are never TTL-swept)", branchProbe: existence2 };
|
|
128286
|
+
}
|
|
128287
|
+
if (context.onlyBranch !== void 0) {
|
|
128288
|
+
if (marker.rawBranch !== context.onlyBranch && marker.sanitizedBranch !== context.onlyBranch) {
|
|
128289
|
+
return { action: "retain", reason: "outside manual --branch scope" };
|
|
128290
|
+
}
|
|
128291
|
+
return { action: "delete", reason: `manual gc --branch ${context.onlyBranch}` };
|
|
128292
|
+
}
|
|
128293
|
+
if (context.allPreviews) {
|
|
128294
|
+
return { action: "delete", reason: "manual gc --all-previews" };
|
|
128295
|
+
}
|
|
128296
|
+
const existence = context.branchExists(marker.rawBranch);
|
|
128297
|
+
if (existence === "exists") {
|
|
128298
|
+
return { action: "retain", reason: "branch still exists", branchProbe: "exists" };
|
|
128299
|
+
}
|
|
128300
|
+
if (existence === "deleted") {
|
|
128301
|
+
return { action: "delete", reason: "branch deleted", branchProbe: "deleted" };
|
|
128302
|
+
}
|
|
128303
|
+
if (marker.expiresAt) {
|
|
128304
|
+
const expires = new Date(marker.expiresAt);
|
|
128305
|
+
if (!Number.isNaN(expires.getTime()) && context.now.getTime() >= expires.getTime()) {
|
|
128306
|
+
return { action: "delete", reason: "sliding TTL expired", branchProbe: "skipped" };
|
|
128307
|
+
}
|
|
128308
|
+
}
|
|
128309
|
+
return { action: "retain", reason: "branch existence unknown and TTL not expired", branchProbe: "skipped" };
|
|
128310
|
+
}
|
|
128311
|
+
async function runPreviewGc(options) {
|
|
128312
|
+
const { context, candidates, deleters, dryRun } = options;
|
|
128313
|
+
const log = options.log ?? (() => void 0);
|
|
128314
|
+
const summary2 = {
|
|
128315
|
+
repo: context.repo,
|
|
128316
|
+
scannedAt: context.now.toISOString(),
|
|
128317
|
+
degraded: Boolean(options.degraded),
|
|
128318
|
+
counts: { delete: 0, retire: 0, restore: 0, retain: 0, stranger: 0, "orphan-audit": 0, errors: 0 },
|
|
128319
|
+
entries: []
|
|
128320
|
+
};
|
|
128321
|
+
for (const candidate of candidates) {
|
|
128322
|
+
const decision = decideRetention(candidate, context);
|
|
128323
|
+
const entry = {
|
|
128324
|
+
kind: candidate.kind,
|
|
128325
|
+
uid: candidate.uid,
|
|
128326
|
+
name: candidate.name,
|
|
128327
|
+
action: decision.action,
|
|
128328
|
+
reason: decision.reason
|
|
128329
|
+
};
|
|
128330
|
+
summary2.counts[decision.action] += 1;
|
|
128331
|
+
if (decision.action === "retire" && !dryRun && candidate.kind === "environment") {
|
|
128332
|
+
const retire = options.retirers?.environment;
|
|
128333
|
+
if (!retire) {
|
|
128334
|
+
entry.error = "no channel retirement writer wired for environment";
|
|
128335
|
+
summary2.counts.errors += 1;
|
|
128336
|
+
} else {
|
|
128337
|
+
try {
|
|
128338
|
+
await retire(candidate, decision.retirementReason ?? "branch-deleted");
|
|
128339
|
+
log(`gc: retired channel ${candidate.name} (${candidate.uid}) \u2014 ${decision.reason}`);
|
|
128340
|
+
} catch (error) {
|
|
128341
|
+
entry.error = error instanceof Error ? error.message : String(error);
|
|
128342
|
+
summary2.counts.errors += 1;
|
|
128343
|
+
}
|
|
128344
|
+
}
|
|
128345
|
+
} else if (decision.action === "restore" && !dryRun && candidate.kind === "environment") {
|
|
128346
|
+
const restore = options.retirers?.restoreEnvironment;
|
|
128347
|
+
if (!restore) {
|
|
128348
|
+
entry.error = "no channel retirement restore writer wired for environment";
|
|
128349
|
+
summary2.counts.errors += 1;
|
|
128350
|
+
} else {
|
|
128351
|
+
try {
|
|
128352
|
+
await restore(candidate);
|
|
128353
|
+
log(`gc: restored channel ${candidate.name} (${candidate.uid})`);
|
|
128354
|
+
} catch (error) {
|
|
128355
|
+
entry.error = error instanceof Error ? error.message : String(error);
|
|
128356
|
+
summary2.counts.errors += 1;
|
|
128357
|
+
}
|
|
128358
|
+
}
|
|
128359
|
+
} else if (decision.action === "delete" && !dryRun) {
|
|
128360
|
+
const remove = deleters[candidate.kind];
|
|
128361
|
+
if (!remove) {
|
|
128362
|
+
entry.error = `no deleter wired for kind ${candidate.kind}`;
|
|
128363
|
+
summary2.counts.errors += 1;
|
|
128364
|
+
} else {
|
|
128365
|
+
try {
|
|
128366
|
+
await remove(candidate.uid);
|
|
128367
|
+
entry.deleted = true;
|
|
128368
|
+
log(`gc: deleted ${candidate.kind} ${candidate.name} (${candidate.uid}) \u2014 ${decision.reason}`);
|
|
128369
|
+
} catch (error) {
|
|
128370
|
+
entry.error = error instanceof Error ? error.message : String(error);
|
|
128371
|
+
summary2.counts.errors += 1;
|
|
128372
|
+
log(`gc: FAILED deleting ${candidate.kind} ${candidate.name} (${candidate.uid}): ${entry.error}`);
|
|
128373
|
+
}
|
|
128374
|
+
}
|
|
128375
|
+
} else if (decision.action === "delete" && dryRun) {
|
|
128376
|
+
log(`gc (dry-run): would delete ${candidate.kind} ${candidate.name} (${candidate.uid}) \u2014 ${decision.reason}`);
|
|
128377
|
+
}
|
|
128378
|
+
summary2.entries.push(entry);
|
|
128379
|
+
}
|
|
128380
|
+
return summary2;
|
|
128381
|
+
}
|
|
128382
|
+
function renderGcSummary(summary2) {
|
|
128383
|
+
const lines = [];
|
|
128384
|
+
lines.push(`Preview GC summary for ${summary2.repo} at ${summary2.scannedAt}${summary2.degraded ? " (degraded: branch probes skipped)" : ""}`);
|
|
128385
|
+
lines.push(
|
|
128386
|
+
` 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}`
|
|
128387
|
+
);
|
|
128388
|
+
for (const entry of summary2.entries) {
|
|
128389
|
+
if (entry.action === "retain" || entry.action === "stranger") continue;
|
|
128390
|
+
const status = entry.error ? `ERROR: ${entry.error}` : entry.deleted ? "deleted" : entry.action;
|
|
128391
|
+
lines.push(` [${entry.kind}] ${entry.name} (${entry.uid}): ${status} \u2014 ${entry.reason}`);
|
|
128392
|
+
}
|
|
128393
|
+
return lines.join("\n");
|
|
128394
|
+
}
|
|
128395
|
+
|
|
128396
|
+
// src/lib/repo/gc-runner.ts
|
|
128397
|
+
var GC_MARKER_ENV_KEY = "x-pm-onboarding";
|
|
128398
|
+
async function inventoryRemoteBranches(exec) {
|
|
128399
|
+
try {
|
|
128400
|
+
const result = await exec.getExecOutput("git", ["ls-remote", "--heads", "origin"], {
|
|
128401
|
+
ignoreReturnCode: true
|
|
128402
|
+
});
|
|
128403
|
+
if (result.exitCode !== 0) {
|
|
128404
|
+
return void 0;
|
|
128405
|
+
}
|
|
128406
|
+
const branches = /* @__PURE__ */ new Set();
|
|
128407
|
+
for (const line of result.stdout.split("\n")) {
|
|
128408
|
+
const match = line.match(/\trefs\/heads\/(.+)$/);
|
|
128409
|
+
if (match) branches.add(match[1].trim());
|
|
128410
|
+
}
|
|
128411
|
+
return branches;
|
|
128412
|
+
} catch {
|
|
128413
|
+
return void 0;
|
|
128414
|
+
}
|
|
128415
|
+
}
|
|
128416
|
+
function markerFromEnvironment(envelope) {
|
|
128417
|
+
const record = envelope && typeof envelope === "object" ? envelope : null;
|
|
128418
|
+
const data = record && typeof record.data === "object" && record.data !== null ? record.data : record;
|
|
128419
|
+
const values = data && Array.isArray(data.values) ? data.values : [];
|
|
128420
|
+
for (const value of values) {
|
|
128421
|
+
if (String(value?.key ?? "") === GC_MARKER_ENV_KEY) {
|
|
128422
|
+
return parseAssetMarker(`${GC_MARKER_ENV_KEY}: ${String(value?.value ?? "")}`);
|
|
128423
|
+
}
|
|
128424
|
+
}
|
|
128425
|
+
return void 0;
|
|
128426
|
+
}
|
|
128427
|
+
function environmentValues(envelope) {
|
|
128428
|
+
const record = envelope && typeof envelope === "object" ? envelope : null;
|
|
128429
|
+
const data = record && typeof record.data === "object" && record.data !== null ? record.data : record;
|
|
128430
|
+
const values = data && Array.isArray(data.values) ? data.values : [];
|
|
128431
|
+
return values.map((raw) => {
|
|
128432
|
+
const value = raw && typeof raw === "object" ? raw : {};
|
|
128433
|
+
return {
|
|
128434
|
+
key: String(value.key ?? ""),
|
|
128435
|
+
value: String(value.value ?? ""),
|
|
128436
|
+
enabled: value.enabled !== false,
|
|
128437
|
+
type: String(value.type ?? "default")
|
|
128438
|
+
};
|
|
128439
|
+
});
|
|
128440
|
+
}
|
|
128441
|
+
function markerFromSpecContent(content) {
|
|
128442
|
+
if (!content) return void 0;
|
|
128443
|
+
try {
|
|
128444
|
+
const parsed = load(content);
|
|
128445
|
+
const marker = parsed?.["x-postman-onboarding"];
|
|
128446
|
+
return marker && typeof marker === "object" ? parseAssetMarker(`${GC_MARKER_ENV_KEY}: ${JSON.stringify(marker)}`) : void 0;
|
|
128447
|
+
} catch {
|
|
128448
|
+
return void 0;
|
|
128449
|
+
}
|
|
128450
|
+
}
|
|
128451
|
+
function isGcCandidateName(name) {
|
|
128452
|
+
return / @[A-Za-z0-9._-]+/.test(name) || /^\[[A-Z][A-Z0-9]*\] /.test(name);
|
|
128453
|
+
}
|
|
128454
|
+
async function collectGcCandidates(postman, workspaceId) {
|
|
128455
|
+
const candidates = [];
|
|
128456
|
+
const environments = await postman.listEnvironments(workspaceId);
|
|
128457
|
+
for (const env of environments) {
|
|
128458
|
+
if (!isGcCandidateName(env.name)) continue;
|
|
128459
|
+
let marker;
|
|
128460
|
+
try {
|
|
128461
|
+
marker = markerFromEnvironment(await postman.getEnvironment(env.uid));
|
|
128462
|
+
} catch {
|
|
128463
|
+
marker = void 0;
|
|
128464
|
+
}
|
|
128465
|
+
candidates.push({ kind: "environment", uid: env.uid, name: env.name, marker });
|
|
128466
|
+
}
|
|
128467
|
+
const mocks = await postman.listMocks();
|
|
128468
|
+
for (const mock of mocks) {
|
|
128469
|
+
if (!isGcCandidateName(mock.name)) continue;
|
|
128470
|
+
const envMatch = candidates.find(
|
|
128471
|
+
(entry) => entry.kind === "environment" && entry.uid === mock.environment
|
|
128472
|
+
);
|
|
128473
|
+
candidates.push({ kind: "mock", uid: mock.uid, name: mock.name, marker: envMatch?.marker });
|
|
128474
|
+
}
|
|
128475
|
+
const monitors = await postman.listMonitors();
|
|
128476
|
+
for (const monitor of monitors) {
|
|
128477
|
+
if (!isGcCandidateName(monitor.name)) continue;
|
|
128478
|
+
const envMatch = candidates.find(
|
|
128479
|
+
(entry) => entry.kind === "environment" && entry.uid === monitor.environmentUid
|
|
128480
|
+
);
|
|
128481
|
+
candidates.push({ kind: "monitor", uid: monitor.uid, name: monitor.name, marker: envMatch?.marker });
|
|
128482
|
+
}
|
|
128483
|
+
const ownedCollections = /* @__PURE__ */ new Map();
|
|
128484
|
+
for (const mock of mocks) {
|
|
128485
|
+
const marker = candidates.find((entry) => entry.kind === "environment" && entry.uid === mock.environment)?.marker;
|
|
128486
|
+
if (marker && mock.collection) ownedCollections.set(mock.collection, { name: `${mock.name} collection`, marker });
|
|
128487
|
+
}
|
|
128488
|
+
for (const monitor of monitors) {
|
|
128489
|
+
const marker = candidates.find((entry) => entry.kind === "environment" && entry.uid === monitor.environmentUid)?.marker;
|
|
128490
|
+
if (marker && monitor.collectionUid) ownedCollections.set(monitor.collectionUid, { name: `${monitor.name} collection`, marker });
|
|
128491
|
+
}
|
|
128492
|
+
for (const [uid, collection] of ownedCollections) {
|
|
128493
|
+
candidates.push({ kind: "collection", uid, name: collection.name, marker: collection.marker });
|
|
128494
|
+
}
|
|
128495
|
+
const specifications = await postman.listSpecifications(workspaceId);
|
|
128496
|
+
for (const spec of specifications) {
|
|
128497
|
+
if (!isGcCandidateName(spec.name)) continue;
|
|
128498
|
+
let marker;
|
|
128499
|
+
try {
|
|
128500
|
+
marker = markerFromSpecContent(await postman.getSpecContent(spec.uid));
|
|
128501
|
+
} catch {
|
|
128502
|
+
marker = void 0;
|
|
128503
|
+
}
|
|
128504
|
+
candidates.push({ kind: "spec", uid: spec.uid, name: spec.name, marker });
|
|
128505
|
+
if (marker) {
|
|
128506
|
+
try {
|
|
128507
|
+
for (const collection of await postman.listSpecCollections(spec.uid)) {
|
|
128508
|
+
if (!ownedCollections.has(collection.uid)) {
|
|
128509
|
+
candidates.push({ kind: "collection", uid: collection.uid, name: collection.name || `${spec.name} collection`, marker });
|
|
128510
|
+
}
|
|
128511
|
+
}
|
|
128512
|
+
} catch {
|
|
128513
|
+
}
|
|
128514
|
+
}
|
|
128515
|
+
}
|
|
128516
|
+
return candidates;
|
|
128517
|
+
}
|
|
128518
|
+
async function runGc(options) {
|
|
128519
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
128520
|
+
const log = options.log ?? (() => void 0);
|
|
128521
|
+
const remoteBranches = options.onlyBranch || options.allPreviews ? void 0 : await inventoryRemoteBranches(options.exec);
|
|
128522
|
+
const degraded = remoteBranches === void 0 && !options.onlyBranch && !options.allPreviews;
|
|
128523
|
+
if (degraded) {
|
|
128524
|
+
log("gc: branch inventory unavailable (credential absent/denied?) \u2014 degraded sweep, TTL only");
|
|
128525
|
+
}
|
|
128526
|
+
const branchExists = (rawBranch) => {
|
|
128527
|
+
if (!remoteBranches) return "unknown";
|
|
128528
|
+
return remoteBranches.has(rawBranch) ? "exists" : "deleted";
|
|
128529
|
+
};
|
|
128530
|
+
const channelRules = options.channels === void 0 ? void 0 : parseChannelRules(options.channels);
|
|
128531
|
+
const channelMapped = channelRules ? (rawBranch) => channelRules.some(
|
|
128532
|
+
(rule) => rule.pattern.endsWith("*") ? rawBranch.startsWith(rule.pattern.slice(0, -1)) : rawBranch === rule.pattern
|
|
128533
|
+
) : void 0;
|
|
128534
|
+
const candidates = await collectGcCandidates(options.postman, options.workspaceId);
|
|
128535
|
+
const retiredChannels = candidates.filter((candidate) => candidate.kind === "environment" && candidate.marker?.role === "channel" && candidate.marker.retirementReason).map((candidate) => candidate.marker);
|
|
128536
|
+
for (const candidate of candidates) {
|
|
128537
|
+
const marker = candidate.marker;
|
|
128538
|
+
if (!marker || marker.role !== "channel" || marker.retirementReason) continue;
|
|
128539
|
+
const retired = retiredChannels.find((entry) => entry.repo === marker.repo && entry.rawBranch === marker.rawBranch);
|
|
128540
|
+
if (retired) candidate.marker = retired;
|
|
128541
|
+
}
|
|
128542
|
+
return runPreviewGc({
|
|
128543
|
+
context: {
|
|
128544
|
+
repo: options.repo,
|
|
128545
|
+
now,
|
|
128546
|
+
branchExists,
|
|
128547
|
+
channelMapped,
|
|
128548
|
+
onlyBranch: options.onlyBranch,
|
|
128549
|
+
allPreviews: options.allPreviews
|
|
128550
|
+
},
|
|
128551
|
+
candidates,
|
|
128552
|
+
deleters: {
|
|
128553
|
+
environment: (uid) => options.postman.deleteEnvironment(uid),
|
|
128554
|
+
mock: (uid) => options.postman.deleteMock(uid),
|
|
128555
|
+
monitor: (uid) => options.postman.deleteMonitor(uid),
|
|
128556
|
+
collection: (uid) => options.postman.deleteCollection(uid),
|
|
128557
|
+
spec: (uid) => options.postman.deleteSpec(uid)
|
|
128558
|
+
},
|
|
128559
|
+
retirers: {
|
|
128560
|
+
environment: async (candidate, reason) => {
|
|
128561
|
+
if (!candidate.marker) return;
|
|
128562
|
+
const envelope = await options.postman.getEnvironment(candidate.uid);
|
|
128563
|
+
const values = environmentValues(envelope);
|
|
128564
|
+
const retired = stampChannelRetirement(
|
|
128565
|
+
candidate.marker,
|
|
128566
|
+
reason,
|
|
128567
|
+
now,
|
|
128568
|
+
options.previewTtlDays ?? 30
|
|
128569
|
+
);
|
|
128570
|
+
const markerValue = values.find((value) => value.key === GC_MARKER_ENV_KEY);
|
|
128571
|
+
if (markerValue) markerValue.value = JSON.stringify(retired);
|
|
128572
|
+
else values.push({ key: GC_MARKER_ENV_KEY, value: JSON.stringify(retired), enabled: true, type: "default" });
|
|
128573
|
+
await options.postman.updateEnvironment(candidate.uid, candidate.name, values);
|
|
128574
|
+
},
|
|
128575
|
+
restoreEnvironment: async (candidate) => {
|
|
128576
|
+
if (!candidate.marker) return;
|
|
128577
|
+
const envelope = await options.postman.getEnvironment(candidate.uid);
|
|
128578
|
+
const values = environmentValues(envelope);
|
|
128579
|
+
const restored = clearChannelRetirement(candidate.marker);
|
|
128580
|
+
const markerValue = values.find((value) => value.key === GC_MARKER_ENV_KEY);
|
|
128581
|
+
if (markerValue) markerValue.value = JSON.stringify(restored);
|
|
128582
|
+
await options.postman.updateEnvironment(candidate.uid, candidate.name, values);
|
|
128583
|
+
}
|
|
128584
|
+
},
|
|
128585
|
+
degraded,
|
|
128586
|
+
dryRun: options.dryRun,
|
|
128587
|
+
log
|
|
128588
|
+
});
|
|
128589
|
+
}
|
|
127370
128590
|
|
|
127371
128591
|
// src/cli.ts
|
|
127372
128592
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
@@ -127415,15 +128635,25 @@ var CLI_INPUT_NAMES = [
|
|
|
127415
128635
|
"ssl-client-passphrase",
|
|
127416
128636
|
"ssl-extra-ca-certs",
|
|
127417
128637
|
"spec-id",
|
|
128638
|
+
"spec-content-changed",
|
|
127418
128639
|
"spec-path",
|
|
127419
128640
|
"team-id",
|
|
127420
128641
|
"postman-region",
|
|
127421
|
-
"postman-stack"
|
|
128642
|
+
"postman-stack",
|
|
128643
|
+
"branch-strategy",
|
|
128644
|
+
"canonical-branch",
|
|
128645
|
+
"channels",
|
|
128646
|
+
"preview-ttl"
|
|
127422
128647
|
];
|
|
127423
128648
|
var HELP_TEXT = `Usage: postman-repo-sync [options]
|
|
127424
128649
|
|
|
127425
128650
|
Sync Postman artifacts into a git repository.
|
|
127426
128651
|
|
|
128652
|
+
Subcommands:
|
|
128653
|
+
gc [--branch <name> | --all-previews] [--dry-run]
|
|
128654
|
+
Garbage-collect preview/channel asset sets
|
|
128655
|
+
(marker-guarded; strangers are never deleted)
|
|
128656
|
+
|
|
127427
128657
|
Options:
|
|
127428
128658
|
--help Show this help and exit
|
|
127429
128659
|
--version Show version and exit
|
|
@@ -127517,7 +128747,7 @@ function resolvePackageVersion() {
|
|
|
127517
128747
|
candidates.push(import_node_path3.default.join(process.cwd(), "package.json"));
|
|
127518
128748
|
for (const packageJsonPath of candidates) {
|
|
127519
128749
|
try {
|
|
127520
|
-
const packageJson = JSON.parse((0,
|
|
128750
|
+
const packageJson = JSON.parse((0, import_node_fs5.readFileSync)(packageJsonPath, "utf8"));
|
|
127521
128751
|
if (packageJson.name === "@postman-cse/onboarding-repo-sync" && packageJson.version) {
|
|
127522
128752
|
return String(packageJson.version).trim();
|
|
127523
128753
|
}
|
|
@@ -127682,9 +128912,22 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
127682
128912
|
return;
|
|
127683
128913
|
}
|
|
127684
128914
|
const env = runtime.env ?? process.env;
|
|
128915
|
+
if (argv[0] === "gc") {
|
|
128916
|
+
await runGcCommand(argv.slice(1), env, writeStdout);
|
|
128917
|
+
return;
|
|
128918
|
+
}
|
|
127685
128919
|
const config = parseCliArgs(argv, env);
|
|
127686
128920
|
const inputs = resolveInputs(config.inputEnv);
|
|
127687
128921
|
const reporter = new ConsoleReporter();
|
|
128922
|
+
const branchDecision = decideBranchTier(inputs, config.inputEnv);
|
|
128923
|
+
if (branchDecision.tier === "gated") {
|
|
128924
|
+
const result2 = runGatedSkip(inputs, branchDecision, reporter);
|
|
128925
|
+
await writeOptionalFile(config.resultJsonPath, JSON.stringify(result2, null, 2));
|
|
128926
|
+
await writeOptionalFile(config.dotenvPath, toDotenv(result2));
|
|
128927
|
+
writeStdout(`${JSON.stringify(result2, null, 2)}
|
|
128928
|
+
`);
|
|
128929
|
+
return;
|
|
128930
|
+
}
|
|
127688
128931
|
await mintAccessTokenIfNeeded(inputs, reporter);
|
|
127689
128932
|
const initialMasker = createSecretMasker([
|
|
127690
128933
|
inputs.postmanApiKey,
|
|
@@ -127735,6 +128978,80 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
127735
128978
|
writeStdout(`${JSON.stringify(result, null, 2)}
|
|
127736
128979
|
`);
|
|
127737
128980
|
}
|
|
128981
|
+
async function runGcCommand(argv, env, writeStdout) {
|
|
128982
|
+
let onlyBranch;
|
|
128983
|
+
let allPreviews = false;
|
|
128984
|
+
let dryRun = false;
|
|
128985
|
+
const passthrough = [];
|
|
128986
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
128987
|
+
const arg = argv[index];
|
|
128988
|
+
if (arg === "--branch") {
|
|
128989
|
+
onlyBranch = argv[index + 1];
|
|
128990
|
+
if (!onlyBranch || onlyBranch.startsWith("--")) {
|
|
128991
|
+
throw new Error("Missing value for --branch");
|
|
128992
|
+
}
|
|
128993
|
+
index += 1;
|
|
128994
|
+
} else if (arg === "--all-previews") {
|
|
128995
|
+
allPreviews = true;
|
|
128996
|
+
} else if (arg === "--dry-run") {
|
|
128997
|
+
dryRun = true;
|
|
128998
|
+
} else {
|
|
128999
|
+
passthrough.push(arg);
|
|
129000
|
+
}
|
|
129001
|
+
}
|
|
129002
|
+
if (onlyBranch && allPreviews) {
|
|
129003
|
+
throw new Error("Use either --branch <name> or --all-previews, not both");
|
|
129004
|
+
}
|
|
129005
|
+
const config = parseCliArgs(passthrough, env);
|
|
129006
|
+
const inputs = resolveInputs(config.inputEnv);
|
|
129007
|
+
if (!inputs.workspaceId) {
|
|
129008
|
+
throw new Error("gc requires --workspace-id (the workspace holding the preview/channel sets)");
|
|
129009
|
+
}
|
|
129010
|
+
const repo = inputs.repoUrl || inputs.repository;
|
|
129011
|
+
if (!repo) {
|
|
129012
|
+
throw new Error("gc requires --repo-url or --repository to scope marker ownership");
|
|
129013
|
+
}
|
|
129014
|
+
const reporter = new ConsoleReporter();
|
|
129015
|
+
await mintAccessTokenIfNeeded(inputs, reporter);
|
|
129016
|
+
const initialMasker = createSecretMasker([
|
|
129017
|
+
inputs.postmanApiKey,
|
|
129018
|
+
inputs.postmanAccessToken,
|
|
129019
|
+
inputs.githubToken,
|
|
129020
|
+
inputs.ghFallbackToken
|
|
129021
|
+
]);
|
|
129022
|
+
const resolved = await resolvePostmanApiKeyAndTeamId(
|
|
129023
|
+
inputs,
|
|
129024
|
+
reporter,
|
|
129025
|
+
createCliExec(initialMasker),
|
|
129026
|
+
initialMasker,
|
|
129027
|
+
{ persistGeneratedApiKeySecret: false, env }
|
|
129028
|
+
);
|
|
129029
|
+
const dependencies = createCliDependencies(inputs, resolved);
|
|
129030
|
+
if (!dependencies.postman.deleteCollection || !dependencies.postman.listSpecifications || !dependencies.postman.getSpecContent || !dependencies.postman.listSpecCollections || !dependencies.postman.deleteSpec) {
|
|
129031
|
+
throw new Error("gc requires the full branch-aware inventory client; this runtime is missing a spec or collection GC capability.");
|
|
129032
|
+
}
|
|
129033
|
+
const summary2 = await runGc({
|
|
129034
|
+
workspaceId: inputs.workspaceId,
|
|
129035
|
+
repo,
|
|
129036
|
+
postman: {
|
|
129037
|
+
...dependencies.postman,
|
|
129038
|
+
deleteCollection: dependencies.postman.deleteCollection,
|
|
129039
|
+
listSpecifications: dependencies.postman.listSpecifications,
|
|
129040
|
+
getSpecContent: dependencies.postman.getSpecContent,
|
|
129041
|
+
listSpecCollections: dependencies.postman.listSpecCollections,
|
|
129042
|
+
deleteSpec: dependencies.postman.deleteSpec
|
|
129043
|
+
},
|
|
129044
|
+
exec: createCliExec(initialMasker),
|
|
129045
|
+
onlyBranch,
|
|
129046
|
+
allPreviews,
|
|
129047
|
+
dryRun,
|
|
129048
|
+
previewTtlDays: inputs.previewTtlDays,
|
|
129049
|
+
channels: inputs.channels,
|
|
129050
|
+
log: (message) => reporter.info(message)
|
|
129051
|
+
});
|
|
129052
|
+
reporter.info(renderGcSummary(summary2));
|
|
129053
|
+
writeStdout(JSON.stringify(summary2, null, 2) + "\n");
|
|
129054
|
+
}
|
|
127738
129055
|
var currentModulePath = typeof __filename === "string" ? __filename : "";
|
|
127739
129056
|
var entrypoint = process.argv[1];
|
|
127740
129057
|
function isEntrypoint(currentPath, entrypointPath) {
|
|
@@ -127742,7 +129059,7 @@ function isEntrypoint(currentPath, entrypointPath) {
|
|
|
127742
129059
|
return false;
|
|
127743
129060
|
}
|
|
127744
129061
|
try {
|
|
127745
|
-
return (0,
|
|
129062
|
+
return (0, import_node_fs5.realpathSync)(currentPath) === (0, import_node_fs5.realpathSync)(entrypointPath);
|
|
127746
129063
|
} catch {
|
|
127747
129064
|
return import_node_path3.default.resolve(currentPath) === import_node_path3.default.resolve(entrypointPath);
|
|
127748
129065
|
}
|