@postman-cse/onboarding-repo-sync 2.1.18 → 2.2.0
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 +15 -1
- package/action.yml +8 -0
- package/dist/action.cjs +240 -23
- package/dist/cli.cjs +240 -23
- package/dist/index.cjs +240 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,6 +113,17 @@ with:
|
|
|
113
113
|
monitor-id: 1e2f3a4b-monitor-id
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
Repo-sync supports public mocks for anonymous validation. An explicit `mock-url` must match one mock in the resolved workspace and reference the expected baseline collection and environment. Discovered and newly created mocks pass the same live visibility check. Private mocks, responses without a recognized visibility field, stale URLs, and identity mismatches fail before `mock-url` is emitted; repo-sync never places a Postman API key in a collection or environment to make a private mock callable.
|
|
117
|
+
|
|
118
|
+
For manual collection validation against that public mock, opt in to a dedicated Postman environment:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
with:
|
|
122
|
+
mock-environment-enabled: true
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
On canonical and legacy runs, the action creates or reuses `<project> - Mock`, sets its `baseUrl` to the validated mock URL, and emits `mock-environment-uid`. Preview and channel runs skip it so branch retention cleanup cannot leak an untracked environment. Its exported representation lives at `postman/mocks/manual-validation.postman_environment.json`; it is deliberately excluded from `environment-uids-json`, system-environment associations, monitors, and generated CI environment selection. Select it explicitly when running baseline, Smoke, or Contract collections manually. Repo-sync never replaces the runtime `prod` or `dev` `baseUrl` with a mock URL.
|
|
126
|
+
|
|
116
127
|
### mTLS certificates for Postman CLI runs
|
|
117
128
|
|
|
118
129
|
The generated CI workflow can run [Postman CLI collection runs](https://learning.postman.com/docs/postman-cli/postman-cli-collections/) with client certificates. Pass the cert material as inputs; when a GitHub token and repository context are available, the action persists them as repository secrets (`POSTMAN_SSL_CLIENT_CERT_B64`, `POSTMAN_SSL_CLIENT_KEY_B64`, `POSTMAN_SSL_CLIENT_PASSPHRASE`, `POSTMAN_SSL_EXTRA_CA_CERTS_B64`) for the generated workflow:
|
|
@@ -144,6 +155,7 @@ with:
|
|
|
144
155
|
| `release-label` | Optional release label used for versioned naming. | no | |
|
|
145
156
|
| `monitor-id` | Existing smoke monitor ID. When set, the action validates and reuses this monitor instead of creating a new one. | no | |
|
|
146
157
|
| `mock-url` | Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one. | no | |
|
|
158
|
+
| `mock-environment-enabled` | Create or update a dedicated manual-validation environment whose baseUrl is the validated public mock URL. This environment is excluded from runtime CI selection. | no | `false` |
|
|
147
159
|
| `monitor-cron` | Cron expression for monitor scheduling (e.g. '0 */6 * * *'). When empty, the monitor is created disabled and triggered to run once per workflow invocation (and once on every subsequent run). | no | `""` |
|
|
148
160
|
| `environments-json` | JSON array of environment slugs to create or update. | no | `["prod"]` |
|
|
149
161
|
| `git-provider` | Git provider override ('github', 'gitlab', 'bitbucket', 'azure-devops'). Auto-detected from environment when omitted. | no | |
|
|
@@ -191,6 +203,8 @@ with:
|
|
|
191
203
|
| `environment-sync-status` | Whether environment sync succeeded, was skipped, or failed. |
|
|
192
204
|
| `environment-uids-json` | JSON map of environment slug to Postman environment uid. |
|
|
193
205
|
| `mock-url` | Created or reused mock server URL. |
|
|
206
|
+
| `mock-environment-uid` | Dedicated manual-validation environment UID when mock-environment-enabled succeeds. |
|
|
207
|
+
| `mock-environment-status` | Whether the optional manual-validation mock environment succeeded, was skipped, or failed. |
|
|
194
208
|
| `monitor-id` | Created or reused smoke monitor ID. |
|
|
195
209
|
| `repo-sync-summary-json` | JSON summary of repo materialization and workspace sync outputs. |
|
|
196
210
|
| `commit-sha` | Commit SHA produced by repo-write-mode, if any. |
|
|
@@ -283,7 +297,7 @@ For `commit-and-push`, the push target is resolved from `current-ref`, then `GIT
|
|
|
283
297
|
|
|
284
298
|
Mocks and monitors: when `baseline-collection-id`, `workspace-id`, and at least one environment are available, the action creates or reuses a mock server. When `smoke-collection-id` is also available, it creates or reuses a cloud smoke monitor unless `monitor-type: cli` is set. With an empty `monitor-cron`, a new cloud monitor is created disabled and triggered once per workflow invocation.
|
|
285
299
|
|
|
286
|
-
Asset reuse priority is explicit inputs (`environment-uids-json`, `mock-url`, `monitor-id`), then live discovery by exact workspace-scoped name, collection UID, and environment UID, then create. Creates submit once and reconcile on ambiguous gateway errors; overlapping compatible creates inside one process share a single in-flight promise. Adopted environments are updated to the requested values. Mock environment assignment has no verified patch route, so a mock is reused only when its live environment already matches; mismatches are never claimed as converged. Concurrent jobs against the same workspace can still race because Postman does not expose create idempotency keys—serialize those workflows with GitHub Actions `concurrency` (or an equivalent CI lock) when duplicate mocks/monitors/environments would be harmful.
|
|
300
|
+
Asset reuse priority is explicit inputs (`environment-uids-json`, `mock-url`, `monitor-id`), then live discovery by exact workspace-scoped name, collection UID, and environment UID, then create. Mock reuse is public-only: the gateway's live `published` field must confirm public visibility, and explicit URLs must match the expected workspace, collection, and environment. Private or unknown visibility fails closed without injecting credentials. Creates submit once and reconcile on ambiguous gateway errors; overlapping compatible creates inside one process share a single in-flight promise. Adopted environments are updated to the requested values. Mock environment assignment has no verified patch route, so a mock is reused only when its live environment already matches; mismatches are never claimed as converged. Concurrent jobs against the same workspace can still race because Postman does not expose create idempotency keys—serialize those workflows with GitHub Actions `concurrency` (or an equivalent CI lock) when duplicate mocks/monitors/environments would be harmful.
|
|
287
301
|
|
|
288
302
|
Deeper reference:
|
|
289
303
|
|
package/action.yml
CHANGED
|
@@ -57,6 +57,10 @@ inputs:
|
|
|
57
57
|
mock-url:
|
|
58
58
|
description: Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one.
|
|
59
59
|
required: false
|
|
60
|
+
mock-environment-enabled:
|
|
61
|
+
description: Create or update a dedicated manual-validation environment whose baseUrl is the validated public mock URL. This environment is excluded from runtime CI selection.
|
|
62
|
+
required: false
|
|
63
|
+
default: "false"
|
|
60
64
|
monitor-cron:
|
|
61
65
|
description: Cron expression for monitor scheduling (e.g. '0 */6 * * *'). When empty, the monitor is created disabled and triggered to run once per workflow invocation (and once on every subsequent run).
|
|
62
66
|
required: false
|
|
@@ -200,6 +204,10 @@ outputs:
|
|
|
200
204
|
description: JSON map of environment slug to Postman environment uid.
|
|
201
205
|
mock-url:
|
|
202
206
|
description: Created or reused mock server URL.
|
|
207
|
+
mock-environment-uid:
|
|
208
|
+
description: Dedicated manual-validation environment UID when mock-environment-enabled succeeds.
|
|
209
|
+
mock-environment-status:
|
|
210
|
+
description: Whether the optional manual-validation mock environment succeeded, was skipped, or failed.
|
|
203
211
|
monitor-id:
|
|
204
212
|
description: Created or reused smoke monitor ID.
|
|
205
213
|
repo-sync-summary-json:
|
package/dist/action.cjs
CHANGED
|
@@ -136660,6 +136660,11 @@ var postmanRepoSyncActionContract = {
|
|
|
136660
136660
|
description: "Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one.",
|
|
136661
136661
|
required: false
|
|
136662
136662
|
},
|
|
136663
|
+
"mock-environment-enabled": {
|
|
136664
|
+
description: "Create or update a dedicated manual-validation environment whose baseUrl is the validated public mock URL. This environment is excluded from runtime CI selection.",
|
|
136665
|
+
required: false,
|
|
136666
|
+
default: "false"
|
|
136667
|
+
},
|
|
136663
136668
|
"monitor-cron": {
|
|
136664
136669
|
description: "Cron expression for monitor scheduling (e.g. '0 */6 * * *'). When empty, the monitor is created disabled and triggered to run once per workflow invocation (and once on every subsequent run).",
|
|
136665
136670
|
required: false,
|
|
@@ -136853,6 +136858,12 @@ var postmanRepoSyncActionContract = {
|
|
|
136853
136858
|
"mock-url": {
|
|
136854
136859
|
description: "Created or reused mock server URL."
|
|
136855
136860
|
},
|
|
136861
|
+
"mock-environment-uid": {
|
|
136862
|
+
description: "Dedicated manual-validation environment UID when mock-environment-enabled succeeds."
|
|
136863
|
+
},
|
|
136864
|
+
"mock-environment-status": {
|
|
136865
|
+
description: "Whether the optional manual-validation mock environment succeeded, was skipped, or failed."
|
|
136866
|
+
},
|
|
136856
136867
|
"monitor-id": {
|
|
136857
136868
|
description: "Created or reused smoke monitor ID."
|
|
136858
136869
|
},
|
|
@@ -136941,6 +136952,25 @@ var PostmanAssetsClient = class {
|
|
|
136941
136952
|
};
|
|
136942
136953
|
|
|
136943
136954
|
// src/lib/postman/postman-gateway-assets-client.ts
|
|
136955
|
+
var MockContractError = class extends Error {
|
|
136956
|
+
constructor(message) {
|
|
136957
|
+
super(message);
|
|
136958
|
+
this.name = "MockContractError";
|
|
136959
|
+
}
|
|
136960
|
+
};
|
|
136961
|
+
function requirePublicMock(mock) {
|
|
136962
|
+
if (mock.visibility === "private") {
|
|
136963
|
+
throw new MockContractError(
|
|
136964
|
+
`MOCK_NOT_PUBLIC: Mock ${mock.uid} is private. Make it public in Postman or remove/rename it so repo-sync can create the canonical public mock.`
|
|
136965
|
+
);
|
|
136966
|
+
}
|
|
136967
|
+
if (mock.visibility !== "public") {
|
|
136968
|
+
throw new MockContractError(
|
|
136969
|
+
`MOCK_VISIBILITY_UNKNOWN: Mock ${mock.uid} did not expose a supported visibility field. Refusing to assume it is publicly callable.`
|
|
136970
|
+
);
|
|
136971
|
+
}
|
|
136972
|
+
return mock;
|
|
136973
|
+
}
|
|
136944
136974
|
var MAX_CREATE_FLIGHTS = 256;
|
|
136945
136975
|
var createFlights = /* @__PURE__ */ new Map();
|
|
136946
136976
|
var PostmanGatewayAssetsClient = class {
|
|
@@ -137122,6 +137152,41 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137122
137152
|
const id = record.uid ?? record.id;
|
|
137123
137153
|
return typeof id === "string" ? id.trim() : String(id ?? "").trim();
|
|
137124
137154
|
}
|
|
137155
|
+
decodeMockRecord(value, operation) {
|
|
137156
|
+
const record = this.asRecord(value);
|
|
137157
|
+
if (!record) {
|
|
137158
|
+
throw new MockContractError(
|
|
137159
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a non-object mock record`
|
|
137160
|
+
);
|
|
137161
|
+
}
|
|
137162
|
+
const uid = this.idOf(record);
|
|
137163
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
137164
|
+
const collection = typeof record.collection === "string" ? record.collection.trim() : "";
|
|
137165
|
+
const mockUrl = typeof (record.url ?? record.mockUrl) === "string" ? String(record.url ?? record.mockUrl).trim() : "";
|
|
137166
|
+
const environment = typeof record.environment === "string" ? record.environment.trim() : "";
|
|
137167
|
+
if (!uid) {
|
|
137168
|
+
throw new MockContractError(
|
|
137169
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a mock without a UID`
|
|
137170
|
+
);
|
|
137171
|
+
}
|
|
137172
|
+
if (!name) {
|
|
137173
|
+
throw new MockContractError(
|
|
137174
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a name`
|
|
137175
|
+
);
|
|
137176
|
+
}
|
|
137177
|
+
if (!collection) {
|
|
137178
|
+
throw new MockContractError(
|
|
137179
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a collection UID`
|
|
137180
|
+
);
|
|
137181
|
+
}
|
|
137182
|
+
if (!mockUrl) {
|
|
137183
|
+
throw new MockContractError(
|
|
137184
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a URL`
|
|
137185
|
+
);
|
|
137186
|
+
}
|
|
137187
|
+
const visibility = typeof record.published === "boolean" ? record.published ? "public" : "private" : "unknown";
|
|
137188
|
+
return { uid, name, collection, mockUrl, environment, visibility };
|
|
137189
|
+
}
|
|
137125
137190
|
/**
|
|
137126
137191
|
* Reduce a Postman public uid (`<owner>-<uuid>`, 6 hyphen groups) to the bare
|
|
137127
137192
|
* model id (`<uuid>`, 5 groups), mirroring `decomposeUID`. Used ONLY for the
|
|
@@ -137422,7 +137487,8 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137422
137487
|
return this.singleFlight(flightKey, flightKey, "mock", async () => {
|
|
137423
137488
|
const existing = await this.findMockByCollection(collection, environment, mockName);
|
|
137424
137489
|
if (existing) {
|
|
137425
|
-
|
|
137490
|
+
const reusable = requirePublicMock(existing);
|
|
137491
|
+
return { uid: reusable.uid, url: reusable.mockUrl };
|
|
137426
137492
|
}
|
|
137427
137493
|
const body = {
|
|
137428
137494
|
name: mockName,
|
|
@@ -137445,13 +137511,12 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137445
137511
|
);
|
|
137446
137512
|
const parseMock = (response) => {
|
|
137447
137513
|
const record = this.dataOf(response);
|
|
137448
|
-
const
|
|
137449
|
-
|
|
137450
|
-
|
|
137451
|
-
}
|
|
137514
|
+
const created = requirePublicMock(
|
|
137515
|
+
this.decodeMockRecord(record, "Mock create")
|
|
137516
|
+
);
|
|
137452
137517
|
return {
|
|
137453
|
-
uid,
|
|
137454
|
-
url:
|
|
137518
|
+
uid: created.uid,
|
|
137519
|
+
url: created.mockUrl
|
|
137455
137520
|
};
|
|
137456
137521
|
};
|
|
137457
137522
|
try {
|
|
@@ -137462,7 +137527,8 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137462
137527
|
error2
|
|
137463
137528
|
);
|
|
137464
137529
|
if (adopted) {
|
|
137465
|
-
|
|
137530
|
+
const reusable = requirePublicMock(adopted);
|
|
137531
|
+
return { uid: reusable.uid, url: reusable.mockUrl };
|
|
137466
137532
|
}
|
|
137467
137533
|
if (adopted === void 0) {
|
|
137468
137534
|
const retried = await this.resendAbsentCreate(async () => parseMock(await send2("auto")));
|
|
@@ -137478,14 +137544,14 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137478
137544
|
method: "get",
|
|
137479
137545
|
path: `/mocks?workspace=${this.workspaceId}`
|
|
137480
137546
|
});
|
|
137481
|
-
const
|
|
137482
|
-
|
|
137483
|
-
|
|
137484
|
-
|
|
137485
|
-
|
|
137486
|
-
|
|
137487
|
-
|
|
137488
|
-
|
|
137547
|
+
const envelope = this.asRecord(response);
|
|
137548
|
+
const items = Array.isArray(response) ? response : Array.isArray(envelope?.data) ? envelope.data : null;
|
|
137549
|
+
if (!items) {
|
|
137550
|
+
throw new MockContractError(
|
|
137551
|
+
"CONTRACT_MOCK_RESPONSE_INVALID: Mock list envelope must be an array or contain a data array"
|
|
137552
|
+
);
|
|
137553
|
+
}
|
|
137554
|
+
return items.map((raw) => this.decodeMockRecord(raw, "Mock list"));
|
|
137489
137555
|
}
|
|
137490
137556
|
/**
|
|
137491
137557
|
* Delete an environment through the sync service (GC path). The path id is
|
|
@@ -137539,7 +137605,7 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137539
137605
|
`workspace ${this.workspaceId}, name "${mockName}", collection ${want}, and environment ${environment || "(none)"}`,
|
|
137540
137606
|
matches
|
|
137541
137607
|
);
|
|
137542
|
-
return match
|
|
137608
|
+
return match;
|
|
137543
137609
|
}
|
|
137544
137610
|
// --- monitors (service: monitors; collection-based = jobTemplates) ---
|
|
137545
137611
|
/** Map raw jobTemplate records to the facade shape; `collection` is a flat public uid. */
|
|
@@ -138469,6 +138535,61 @@ function resolveRepoSyncMasker(dependencies) {
|
|
|
138469
138535
|
function causeText(cause) {
|
|
138470
138536
|
return cause instanceof Error ? cause.message : String(cause);
|
|
138471
138537
|
}
|
|
138538
|
+
function normalizeMockUrl(value, source) {
|
|
138539
|
+
let url;
|
|
138540
|
+
try {
|
|
138541
|
+
url = new URL(String(value || "").trim());
|
|
138542
|
+
} catch {
|
|
138543
|
+
throw new MockContractError(
|
|
138544
|
+
`EXPLICIT_MOCK_URL_INVALID: ${source} is not a valid absolute URL`
|
|
138545
|
+
);
|
|
138546
|
+
}
|
|
138547
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) {
|
|
138548
|
+
throw new MockContractError(
|
|
138549
|
+
`EXPLICIT_MOCK_URL_INVALID: ${source} must be an HTTPS URL without credentials, query parameters, or a fragment`
|
|
138550
|
+
);
|
|
138551
|
+
}
|
|
138552
|
+
url.pathname = url.pathname.replace(/\/+$/g, "") || "/";
|
|
138553
|
+
return url.toString();
|
|
138554
|
+
}
|
|
138555
|
+
function resolveExplicitPublicMock(mocks, explicitUrl, collectionUid, environmentUid) {
|
|
138556
|
+
const normalized = normalizeMockUrl(explicitUrl, "mock-url");
|
|
138557
|
+
const matches = mocks.filter(
|
|
138558
|
+
(mock) => normalizeMockUrl(mock.mockUrl, `mock ${mock.uid} URL`) === normalized
|
|
138559
|
+
);
|
|
138560
|
+
if (matches.length === 0) {
|
|
138561
|
+
throw new MockContractError(
|
|
138562
|
+
`EXPLICIT_MOCK_URL_NOT_FOUND: mock-url was not found in the resolved workspace mock inventory`
|
|
138563
|
+
);
|
|
138564
|
+
}
|
|
138565
|
+
if (matches.length > 1) {
|
|
138566
|
+
throw new MockContractError(
|
|
138567
|
+
`EXPLICIT_MOCK_URL_AMBIGUOUS: mock-url matched multiple workspace mocks (${matches.map((mock) => mock.uid).join(", ")})`
|
|
138568
|
+
);
|
|
138569
|
+
}
|
|
138570
|
+
const match = matches[0];
|
|
138571
|
+
if (match.collection !== collectionUid) {
|
|
138572
|
+
throw new MockContractError(
|
|
138573
|
+
`EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references collection ${match.collection || "(none)"}, expected ${collectionUid}`
|
|
138574
|
+
);
|
|
138575
|
+
}
|
|
138576
|
+
if (match.environment !== environmentUid) {
|
|
138577
|
+
throw new MockContractError(
|
|
138578
|
+
`EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references environment ${match.environment || "(none)"}, expected ${environmentUid}`
|
|
138579
|
+
);
|
|
138580
|
+
}
|
|
138581
|
+
return requirePublicMock(match);
|
|
138582
|
+
}
|
|
138583
|
+
function shouldRetryMockCreate(error2) {
|
|
138584
|
+
if (error2 instanceof MockContractError) return false;
|
|
138585
|
+
if (error2 instanceof HttpError) {
|
|
138586
|
+
return error2.status === 408 || error2.status === 429 || error2.status >= 500;
|
|
138587
|
+
}
|
|
138588
|
+
if (error2 instanceof Error && error2.message.startsWith("Multiple mocks match ")) {
|
|
138589
|
+
return false;
|
|
138590
|
+
}
|
|
138591
|
+
return true;
|
|
138592
|
+
}
|
|
138472
138593
|
function toOneLineDisplay(value) {
|
|
138473
138594
|
return String(value ?? "").replace(/[\r\n\u0085\u2028\u2029\v\f]+/g, " ").replace(/[ \t]{2,}/g, " ").trim();
|
|
138474
138595
|
}
|
|
@@ -138675,6 +138796,7 @@ function resolveInputs(env = process.env) {
|
|
|
138675
138796
|
orgMode: parseBooleanInput(getInput2("org-mode", env), false),
|
|
138676
138797
|
monitorId: getInput2("monitor-id", env),
|
|
138677
138798
|
mockUrl: getInput2("mock-url", env),
|
|
138799
|
+
mockEnvironmentEnabled: parseBooleanInput(getInput2("mock-environment-enabled", env), false),
|
|
138678
138800
|
monitorCron: getInput2("monitor-cron", env),
|
|
138679
138801
|
sslClientCert: getInput2("ssl-client-cert", env),
|
|
138680
138802
|
sslClientKey: getInput2("ssl-client-key", env),
|
|
@@ -138901,6 +139023,8 @@ function createOutputs(inputs) {
|
|
|
138901
139023
|
"environment-sync-status": "skipped",
|
|
138902
139024
|
"environment-uids-json": JSON.stringify(inputs.environmentUids),
|
|
138903
139025
|
"mock-url": "",
|
|
139026
|
+
"mock-environment-uid": "",
|
|
139027
|
+
"mock-environment-status": "skipped",
|
|
138904
139028
|
"monitor-id": "",
|
|
138905
139029
|
"repo-sync-summary-json": "{}",
|
|
138906
139030
|
"commit-sha": "",
|
|
@@ -138968,6 +139092,7 @@ function readActionInputs(actionCore) {
|
|
|
138968
139092
|
INPUT_ORG_MODE: readInput(actionCore, "org-mode"),
|
|
138969
139093
|
INPUT_MONITOR_ID: readInput(actionCore, "monitor-id"),
|
|
138970
139094
|
INPUT_MOCK_URL: readInput(actionCore, "mock-url"),
|
|
139095
|
+
INPUT_MOCK_ENVIRONMENT_ENABLED: readInput(actionCore, "mock-environment-enabled"),
|
|
138971
139096
|
INPUT_MONITOR_CRON: readInput(actionCore, "monitor-cron"),
|
|
138972
139097
|
INPUT_SSL_CLIENT_CERT: sslClientCert,
|
|
138973
139098
|
INPUT_SSL_CLIENT_KEY: sslClientKey,
|
|
@@ -139169,6 +139294,52 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
|
|
|
139169
139294
|
}
|
|
139170
139295
|
return envUids;
|
|
139171
139296
|
}
|
|
139297
|
+
async function upsertMockEnvironment(inputs, dependencies, assetProjectName, mockUrl) {
|
|
139298
|
+
if (!inputs.mockEnvironmentEnabled || !inputs.workspaceId || !mockUrl) {
|
|
139299
|
+
return "";
|
|
139300
|
+
}
|
|
139301
|
+
const displayName = `${assetProjectName} - Mock`;
|
|
139302
|
+
const values = buildEnvironmentValues("mock", mockUrl);
|
|
139303
|
+
const mask = resolveRepoSyncMasker(dependencies);
|
|
139304
|
+
try {
|
|
139305
|
+
const discovered = await dependencies.postman.findEnvironmentByName(
|
|
139306
|
+
inputs.workspaceId,
|
|
139307
|
+
displayName
|
|
139308
|
+
);
|
|
139309
|
+
if (discovered?.uid) {
|
|
139310
|
+
try {
|
|
139311
|
+
const existing = await dependencies.postman.getEnvironment(discovered.uid);
|
|
139312
|
+
const currentValues = existing.data?.values ?? existing.values ?? [];
|
|
139313
|
+
if (currentValues.some((value) => value.key === "baseUrl" && value.value === mockUrl)) {
|
|
139314
|
+
dependencies.core.info(`Mock environment already points to ${mockUrl}: ${discovered.uid}`);
|
|
139315
|
+
return discovered.uid;
|
|
139316
|
+
}
|
|
139317
|
+
} catch {
|
|
139318
|
+
}
|
|
139319
|
+
await dependencies.postman.updateEnvironment(discovered.uid, displayName, values);
|
|
139320
|
+
dependencies.core.info(`Updated mock environment ${displayName}: ${discovered.uid}`);
|
|
139321
|
+
return discovered.uid;
|
|
139322
|
+
}
|
|
139323
|
+
const uid = await dependencies.postman.createEnvironment(
|
|
139324
|
+
inputs.workspaceId,
|
|
139325
|
+
displayName,
|
|
139326
|
+
values
|
|
139327
|
+
);
|
|
139328
|
+
dependencies.core.info(`Created mock environment ${displayName}: ${uid}`);
|
|
139329
|
+
return uid;
|
|
139330
|
+
} catch (error2) {
|
|
139331
|
+
dependencies.core.warning(
|
|
139332
|
+
formatOrchestrationIssue({
|
|
139333
|
+
operation: "Mock environment upsert",
|
|
139334
|
+
entity: `workspace ${inputs.workspaceId} environment "${displayName}"`,
|
|
139335
|
+
cause: error2,
|
|
139336
|
+
remediation: "verify environment access or disable mock-environment-enabled then rerun",
|
|
139337
|
+
mask
|
|
139338
|
+
})
|
|
139339
|
+
);
|
|
139340
|
+
return "";
|
|
139341
|
+
}
|
|
139342
|
+
}
|
|
139172
139343
|
function ensureDir(path9) {
|
|
139173
139344
|
(0, import_node_fs5.mkdirSync)(path9, { recursive: true });
|
|
139174
139345
|
}
|
|
@@ -139807,6 +139978,13 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
|
|
|
139807
139978
|
true
|
|
139808
139979
|
);
|
|
139809
139980
|
}
|
|
139981
|
+
if (options.mockEnvironmentUid) {
|
|
139982
|
+
writeJsonFile(
|
|
139983
|
+
`${mocksDir}/manual-validation.postman_environment.json`,
|
|
139984
|
+
await dependencies.postman.getEnvironment(options.mockEnvironmentUid),
|
|
139985
|
+
true
|
|
139986
|
+
);
|
|
139987
|
+
}
|
|
139810
139988
|
const durableWorkspaceId = resolveDurableWorkspaceId({
|
|
139811
139989
|
candidateId: inputs.workspaceId,
|
|
139812
139990
|
priorId: options.priorWorkspaceId,
|
|
@@ -139865,6 +140043,8 @@ function createRepoSummary(outputs, envUids, pushed) {
|
|
|
139865
140043
|
commitSha: outputs["commit-sha"],
|
|
139866
140044
|
environmentCount: Object.keys(envUids).length,
|
|
139867
140045
|
environmentSyncStatus: outputs["environment-sync-status"],
|
|
140046
|
+
mockEnvironmentStatus: outputs["mock-environment-status"],
|
|
140047
|
+
mockEnvironmentUid: outputs["mock-environment-uid"],
|
|
139868
140048
|
mockUrl: outputs["mock-url"],
|
|
139869
140049
|
monitorId: outputs["monitor-id"],
|
|
139870
140050
|
pushed,
|
|
@@ -140095,20 +140275,37 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
140095
140275
|
let resolvedMockUrl = "";
|
|
140096
140276
|
const mockName = `${assetProjectName} Mock`;
|
|
140097
140277
|
if (inputs.mockUrl) {
|
|
140098
|
-
|
|
140278
|
+
let mocks;
|
|
140279
|
+
try {
|
|
140280
|
+
mocks = await dependencies.postman.listMocks();
|
|
140281
|
+
} catch (error2) {
|
|
140282
|
+
throw new Error(
|
|
140283
|
+
formatOrchestrationIssue({
|
|
140284
|
+
operation: "Explicit mock-url lookup",
|
|
140285
|
+
entity: `mock-url ${inputs.mockUrl} workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`,
|
|
140286
|
+
cause: error2,
|
|
140287
|
+
remediation: "verify the URL and mock access then rerun",
|
|
140288
|
+
mask
|
|
140289
|
+
}),
|
|
140290
|
+
{ cause: error2 }
|
|
140291
|
+
);
|
|
140292
|
+
}
|
|
140293
|
+
resolvedMockUrl = resolveExplicitPublicMock(
|
|
140294
|
+
mocks,
|
|
140295
|
+
inputs.mockUrl,
|
|
140296
|
+
inputs.baselineCollectionId,
|
|
140297
|
+
mockEnvUid
|
|
140298
|
+
).mockUrl;
|
|
140099
140299
|
dependencies.core.info(`Reusing mock from explicit input: ${resolvedMockUrl}`);
|
|
140100
140300
|
}
|
|
140101
140301
|
if (!resolvedMockUrl && inputs.baselineCollectionId) {
|
|
140302
|
+
let discovered;
|
|
140102
140303
|
try {
|
|
140103
|
-
|
|
140304
|
+
discovered = await dependencies.postman.findMockByCollection(
|
|
140104
140305
|
inputs.baselineCollectionId,
|
|
140105
140306
|
mockEnvUid,
|
|
140106
140307
|
mockName
|
|
140107
140308
|
);
|
|
140108
|
-
if (discovered) {
|
|
140109
|
-
resolvedMockUrl = discovered.mockUrl;
|
|
140110
|
-
dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
|
|
140111
|
-
}
|
|
140112
140309
|
} catch (error2) {
|
|
140113
140310
|
throw new Error(
|
|
140114
140311
|
formatOrchestrationIssue({
|
|
@@ -140121,6 +140318,10 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
140121
140318
|
{ cause: error2 }
|
|
140122
140319
|
);
|
|
140123
140320
|
}
|
|
140321
|
+
if (discovered) {
|
|
140322
|
+
resolvedMockUrl = requirePublicMock(discovered).mockUrl;
|
|
140323
|
+
dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
|
|
140324
|
+
}
|
|
140124
140325
|
}
|
|
140125
140326
|
if (!resolvedMockUrl) {
|
|
140126
140327
|
const mockEntity = `mock "${mockName}" workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`;
|
|
@@ -140137,6 +140338,7 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
140137
140338
|
maxAttempts: 3,
|
|
140138
140339
|
delayMs: 2e3,
|
|
140139
140340
|
backoffMultiplier: 2,
|
|
140341
|
+
shouldRetry: (error2) => shouldRetryMockCreate(error2),
|
|
140140
140342
|
onRetry: ({ attempt, maxAttempts, error: error2 }) => {
|
|
140141
140343
|
dependencies.core.warning(
|
|
140142
140344
|
formatOrchestrationIssue({
|
|
@@ -140168,6 +140370,20 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
140168
140370
|
}
|
|
140169
140371
|
outputs["mock-url"] = resolvedMockUrl;
|
|
140170
140372
|
dependencies.core.setOutput("mock-url", resolvedMockUrl);
|
|
140373
|
+
if (inputs.mockEnvironmentEnabled && isCanonicalWriter) {
|
|
140374
|
+
const mockEnvironmentUid = await upsertMockEnvironment(
|
|
140375
|
+
inputs,
|
|
140376
|
+
dependencies,
|
|
140377
|
+
assetProjectName,
|
|
140378
|
+
resolvedMockUrl
|
|
140379
|
+
);
|
|
140380
|
+
outputs["mock-environment-uid"] = mockEnvironmentUid;
|
|
140381
|
+
outputs["mock-environment-status"] = mockEnvironmentUid ? "success" : "failed";
|
|
140382
|
+
} else if (inputs.mockEnvironmentEnabled) {
|
|
140383
|
+
dependencies.core.warning(
|
|
140384
|
+
"mock-environment-enabled is skipped for preview and channel runs so manual-validation environments cannot escape branch retention cleanup."
|
|
140385
|
+
);
|
|
140386
|
+
}
|
|
140171
140387
|
}
|
|
140172
140388
|
}
|
|
140173
140389
|
if (inputs.workspaceId && inputs.smokeCollectionId && Object.keys(envUids).length > 0) {
|
|
@@ -140300,6 +140516,7 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
140300
140516
|
workspaceLinkStatus: outputs["workspace-link-status"],
|
|
140301
140517
|
priorWorkspaceId: resourcesState?.workspace?.id,
|
|
140302
140518
|
existingSpecs: resourcesState?.cloudResources?.specs,
|
|
140519
|
+
mockEnvironmentUid: outputs["mock-environment-uid"] || void 0,
|
|
140303
140520
|
releaseLabel,
|
|
140304
140521
|
priorState: resourcesState,
|
|
140305
140522
|
preparedPrebuiltCollections
|
package/dist/cli.cjs
CHANGED
|
@@ -134765,6 +134765,11 @@ var postmanRepoSyncActionContract = {
|
|
|
134765
134765
|
description: "Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one.",
|
|
134766
134766
|
required: false
|
|
134767
134767
|
},
|
|
134768
|
+
"mock-environment-enabled": {
|
|
134769
|
+
description: "Create or update a dedicated manual-validation environment whose baseUrl is the validated public mock URL. This environment is excluded from runtime CI selection.",
|
|
134770
|
+
required: false,
|
|
134771
|
+
default: "false"
|
|
134772
|
+
},
|
|
134768
134773
|
"monitor-cron": {
|
|
134769
134774
|
description: "Cron expression for monitor scheduling (e.g. '0 */6 * * *'). When empty, the monitor is created disabled and triggered to run once per workflow invocation (and once on every subsequent run).",
|
|
134770
134775
|
required: false,
|
|
@@ -134958,6 +134963,12 @@ var postmanRepoSyncActionContract = {
|
|
|
134958
134963
|
"mock-url": {
|
|
134959
134964
|
description: "Created or reused mock server URL."
|
|
134960
134965
|
},
|
|
134966
|
+
"mock-environment-uid": {
|
|
134967
|
+
description: "Dedicated manual-validation environment UID when mock-environment-enabled succeeds."
|
|
134968
|
+
},
|
|
134969
|
+
"mock-environment-status": {
|
|
134970
|
+
description: "Whether the optional manual-validation mock environment succeeded, was skipped, or failed."
|
|
134971
|
+
},
|
|
134961
134972
|
"monitor-id": {
|
|
134962
134973
|
description: "Created or reused smoke monitor ID."
|
|
134963
134974
|
},
|
|
@@ -135046,6 +135057,25 @@ var PostmanAssetsClient = class {
|
|
|
135046
135057
|
};
|
|
135047
135058
|
|
|
135048
135059
|
// src/lib/postman/postman-gateway-assets-client.ts
|
|
135060
|
+
var MockContractError = class extends Error {
|
|
135061
|
+
constructor(message) {
|
|
135062
|
+
super(message);
|
|
135063
|
+
this.name = "MockContractError";
|
|
135064
|
+
}
|
|
135065
|
+
};
|
|
135066
|
+
function requirePublicMock(mock) {
|
|
135067
|
+
if (mock.visibility === "private") {
|
|
135068
|
+
throw new MockContractError(
|
|
135069
|
+
`MOCK_NOT_PUBLIC: Mock ${mock.uid} is private. Make it public in Postman or remove/rename it so repo-sync can create the canonical public mock.`
|
|
135070
|
+
);
|
|
135071
|
+
}
|
|
135072
|
+
if (mock.visibility !== "public") {
|
|
135073
|
+
throw new MockContractError(
|
|
135074
|
+
`MOCK_VISIBILITY_UNKNOWN: Mock ${mock.uid} did not expose a supported visibility field. Refusing to assume it is publicly callable.`
|
|
135075
|
+
);
|
|
135076
|
+
}
|
|
135077
|
+
return mock;
|
|
135078
|
+
}
|
|
135049
135079
|
var MAX_CREATE_FLIGHTS = 256;
|
|
135050
135080
|
var createFlights = /* @__PURE__ */ new Map();
|
|
135051
135081
|
var PostmanGatewayAssetsClient = class {
|
|
@@ -135227,6 +135257,41 @@ var PostmanGatewayAssetsClient = class {
|
|
|
135227
135257
|
const id = record.uid ?? record.id;
|
|
135228
135258
|
return typeof id === "string" ? id.trim() : String(id ?? "").trim();
|
|
135229
135259
|
}
|
|
135260
|
+
decodeMockRecord(value, operation) {
|
|
135261
|
+
const record = this.asRecord(value);
|
|
135262
|
+
if (!record) {
|
|
135263
|
+
throw new MockContractError(
|
|
135264
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a non-object mock record`
|
|
135265
|
+
);
|
|
135266
|
+
}
|
|
135267
|
+
const uid = this.idOf(record);
|
|
135268
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
135269
|
+
const collection = typeof record.collection === "string" ? record.collection.trim() : "";
|
|
135270
|
+
const mockUrl = typeof (record.url ?? record.mockUrl) === "string" ? String(record.url ?? record.mockUrl).trim() : "";
|
|
135271
|
+
const environment = typeof record.environment === "string" ? record.environment.trim() : "";
|
|
135272
|
+
if (!uid) {
|
|
135273
|
+
throw new MockContractError(
|
|
135274
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a mock without a UID`
|
|
135275
|
+
);
|
|
135276
|
+
}
|
|
135277
|
+
if (!name) {
|
|
135278
|
+
throw new MockContractError(
|
|
135279
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a name`
|
|
135280
|
+
);
|
|
135281
|
+
}
|
|
135282
|
+
if (!collection) {
|
|
135283
|
+
throw new MockContractError(
|
|
135284
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a collection UID`
|
|
135285
|
+
);
|
|
135286
|
+
}
|
|
135287
|
+
if (!mockUrl) {
|
|
135288
|
+
throw new MockContractError(
|
|
135289
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a URL`
|
|
135290
|
+
);
|
|
135291
|
+
}
|
|
135292
|
+
const visibility = typeof record.published === "boolean" ? record.published ? "public" : "private" : "unknown";
|
|
135293
|
+
return { uid, name, collection, mockUrl, environment, visibility };
|
|
135294
|
+
}
|
|
135230
135295
|
/**
|
|
135231
135296
|
* Reduce a Postman public uid (`<owner>-<uuid>`, 6 hyphen groups) to the bare
|
|
135232
135297
|
* model id (`<uuid>`, 5 groups), mirroring `decomposeUID`. Used ONLY for the
|
|
@@ -135527,7 +135592,8 @@ var PostmanGatewayAssetsClient = class {
|
|
|
135527
135592
|
return this.singleFlight(flightKey, flightKey, "mock", async () => {
|
|
135528
135593
|
const existing = await this.findMockByCollection(collection, environment, mockName);
|
|
135529
135594
|
if (existing) {
|
|
135530
|
-
|
|
135595
|
+
const reusable = requirePublicMock(existing);
|
|
135596
|
+
return { uid: reusable.uid, url: reusable.mockUrl };
|
|
135531
135597
|
}
|
|
135532
135598
|
const body = {
|
|
135533
135599
|
name: mockName,
|
|
@@ -135550,13 +135616,12 @@ var PostmanGatewayAssetsClient = class {
|
|
|
135550
135616
|
);
|
|
135551
135617
|
const parseMock = (response) => {
|
|
135552
135618
|
const record = this.dataOf(response);
|
|
135553
|
-
const
|
|
135554
|
-
|
|
135555
|
-
|
|
135556
|
-
}
|
|
135619
|
+
const created = requirePublicMock(
|
|
135620
|
+
this.decodeMockRecord(record, "Mock create")
|
|
135621
|
+
);
|
|
135557
135622
|
return {
|
|
135558
|
-
uid,
|
|
135559
|
-
url:
|
|
135623
|
+
uid: created.uid,
|
|
135624
|
+
url: created.mockUrl
|
|
135560
135625
|
};
|
|
135561
135626
|
};
|
|
135562
135627
|
try {
|
|
@@ -135567,7 +135632,8 @@ var PostmanGatewayAssetsClient = class {
|
|
|
135567
135632
|
error
|
|
135568
135633
|
);
|
|
135569
135634
|
if (adopted) {
|
|
135570
|
-
|
|
135635
|
+
const reusable = requirePublicMock(adopted);
|
|
135636
|
+
return { uid: reusable.uid, url: reusable.mockUrl };
|
|
135571
135637
|
}
|
|
135572
135638
|
if (adopted === void 0) {
|
|
135573
135639
|
const retried = await this.resendAbsentCreate(async () => parseMock(await send2("auto")));
|
|
@@ -135583,14 +135649,14 @@ var PostmanGatewayAssetsClient = class {
|
|
|
135583
135649
|
method: "get",
|
|
135584
135650
|
path: `/mocks?workspace=${this.workspaceId}`
|
|
135585
135651
|
});
|
|
135586
|
-
const
|
|
135587
|
-
|
|
135588
|
-
|
|
135589
|
-
|
|
135590
|
-
|
|
135591
|
-
|
|
135592
|
-
|
|
135593
|
-
|
|
135652
|
+
const envelope = this.asRecord(response);
|
|
135653
|
+
const items = Array.isArray(response) ? response : Array.isArray(envelope?.data) ? envelope.data : null;
|
|
135654
|
+
if (!items) {
|
|
135655
|
+
throw new MockContractError(
|
|
135656
|
+
"CONTRACT_MOCK_RESPONSE_INVALID: Mock list envelope must be an array or contain a data array"
|
|
135657
|
+
);
|
|
135658
|
+
}
|
|
135659
|
+
return items.map((raw) => this.decodeMockRecord(raw, "Mock list"));
|
|
135594
135660
|
}
|
|
135595
135661
|
/**
|
|
135596
135662
|
* Delete an environment through the sync service (GC path). The path id is
|
|
@@ -135644,7 +135710,7 @@ var PostmanGatewayAssetsClient = class {
|
|
|
135644
135710
|
`workspace ${this.workspaceId}, name "${mockName}", collection ${want}, and environment ${environment || "(none)"}`,
|
|
135645
135711
|
matches
|
|
135646
135712
|
);
|
|
135647
|
-
return match
|
|
135713
|
+
return match;
|
|
135648
135714
|
}
|
|
135649
135715
|
// --- monitors (service: monitors; collection-based = jobTemplates) ---
|
|
135650
135716
|
/** Map raw jobTemplate records to the facade shape; `collection` is a flat public uid. */
|
|
@@ -136522,6 +136588,61 @@ function resolveRepoSyncMasker(dependencies) {
|
|
|
136522
136588
|
function causeText(cause) {
|
|
136523
136589
|
return cause instanceof Error ? cause.message : String(cause);
|
|
136524
136590
|
}
|
|
136591
|
+
function normalizeMockUrl(value, source) {
|
|
136592
|
+
let url;
|
|
136593
|
+
try {
|
|
136594
|
+
url = new URL(String(value || "").trim());
|
|
136595
|
+
} catch {
|
|
136596
|
+
throw new MockContractError(
|
|
136597
|
+
`EXPLICIT_MOCK_URL_INVALID: ${source} is not a valid absolute URL`
|
|
136598
|
+
);
|
|
136599
|
+
}
|
|
136600
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) {
|
|
136601
|
+
throw new MockContractError(
|
|
136602
|
+
`EXPLICIT_MOCK_URL_INVALID: ${source} must be an HTTPS URL without credentials, query parameters, or a fragment`
|
|
136603
|
+
);
|
|
136604
|
+
}
|
|
136605
|
+
url.pathname = url.pathname.replace(/\/+$/g, "") || "/";
|
|
136606
|
+
return url.toString();
|
|
136607
|
+
}
|
|
136608
|
+
function resolveExplicitPublicMock(mocks, explicitUrl, collectionUid, environmentUid) {
|
|
136609
|
+
const normalized = normalizeMockUrl(explicitUrl, "mock-url");
|
|
136610
|
+
const matches = mocks.filter(
|
|
136611
|
+
(mock) => normalizeMockUrl(mock.mockUrl, `mock ${mock.uid} URL`) === normalized
|
|
136612
|
+
);
|
|
136613
|
+
if (matches.length === 0) {
|
|
136614
|
+
throw new MockContractError(
|
|
136615
|
+
`EXPLICIT_MOCK_URL_NOT_FOUND: mock-url was not found in the resolved workspace mock inventory`
|
|
136616
|
+
);
|
|
136617
|
+
}
|
|
136618
|
+
if (matches.length > 1) {
|
|
136619
|
+
throw new MockContractError(
|
|
136620
|
+
`EXPLICIT_MOCK_URL_AMBIGUOUS: mock-url matched multiple workspace mocks (${matches.map((mock) => mock.uid).join(", ")})`
|
|
136621
|
+
);
|
|
136622
|
+
}
|
|
136623
|
+
const match = matches[0];
|
|
136624
|
+
if (match.collection !== collectionUid) {
|
|
136625
|
+
throw new MockContractError(
|
|
136626
|
+
`EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references collection ${match.collection || "(none)"}, expected ${collectionUid}`
|
|
136627
|
+
);
|
|
136628
|
+
}
|
|
136629
|
+
if (match.environment !== environmentUid) {
|
|
136630
|
+
throw new MockContractError(
|
|
136631
|
+
`EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references environment ${match.environment || "(none)"}, expected ${environmentUid}`
|
|
136632
|
+
);
|
|
136633
|
+
}
|
|
136634
|
+
return requirePublicMock(match);
|
|
136635
|
+
}
|
|
136636
|
+
function shouldRetryMockCreate(error) {
|
|
136637
|
+
if (error instanceof MockContractError) return false;
|
|
136638
|
+
if (error instanceof HttpError) {
|
|
136639
|
+
return error.status === 408 || error.status === 429 || error.status >= 500;
|
|
136640
|
+
}
|
|
136641
|
+
if (error instanceof Error && error.message.startsWith("Multiple mocks match ")) {
|
|
136642
|
+
return false;
|
|
136643
|
+
}
|
|
136644
|
+
return true;
|
|
136645
|
+
}
|
|
136525
136646
|
function toOneLineDisplay(value) {
|
|
136526
136647
|
return String(value ?? "").replace(/[\r\n\u0085\u2028\u2029\v\f]+/g, " ").replace(/[ \t]{2,}/g, " ").trim();
|
|
136527
136648
|
}
|
|
@@ -136725,6 +136846,7 @@ function resolveInputs(env = process.env) {
|
|
|
136725
136846
|
orgMode: parseBooleanInput(getInput("org-mode", env), false),
|
|
136726
136847
|
monitorId: getInput("monitor-id", env),
|
|
136727
136848
|
mockUrl: getInput("mock-url", env),
|
|
136849
|
+
mockEnvironmentEnabled: parseBooleanInput(getInput("mock-environment-enabled", env), false),
|
|
136728
136850
|
monitorCron: getInput("monitor-cron", env),
|
|
136729
136851
|
sslClientCert: getInput("ssl-client-cert", env),
|
|
136730
136852
|
sslClientKey: getInput("ssl-client-key", env),
|
|
@@ -136951,6 +137073,8 @@ function createOutputs(inputs) {
|
|
|
136951
137073
|
"environment-sync-status": "skipped",
|
|
136952
137074
|
"environment-uids-json": JSON.stringify(inputs.environmentUids),
|
|
136953
137075
|
"mock-url": "",
|
|
137076
|
+
"mock-environment-uid": "",
|
|
137077
|
+
"mock-environment-status": "skipped",
|
|
136954
137078
|
"monitor-id": "",
|
|
136955
137079
|
"repo-sync-summary-json": "{}",
|
|
136956
137080
|
"commit-sha": "",
|
|
@@ -137079,6 +137203,52 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
|
|
|
137079
137203
|
}
|
|
137080
137204
|
return envUids;
|
|
137081
137205
|
}
|
|
137206
|
+
async function upsertMockEnvironment(inputs, dependencies, assetProjectName, mockUrl) {
|
|
137207
|
+
if (!inputs.mockEnvironmentEnabled || !inputs.workspaceId || !mockUrl) {
|
|
137208
|
+
return "";
|
|
137209
|
+
}
|
|
137210
|
+
const displayName = `${assetProjectName} - Mock`;
|
|
137211
|
+
const values = buildEnvironmentValues("mock", mockUrl);
|
|
137212
|
+
const mask = resolveRepoSyncMasker(dependencies);
|
|
137213
|
+
try {
|
|
137214
|
+
const discovered = await dependencies.postman.findEnvironmentByName(
|
|
137215
|
+
inputs.workspaceId,
|
|
137216
|
+
displayName
|
|
137217
|
+
);
|
|
137218
|
+
if (discovered?.uid) {
|
|
137219
|
+
try {
|
|
137220
|
+
const existing = await dependencies.postman.getEnvironment(discovered.uid);
|
|
137221
|
+
const currentValues = existing.data?.values ?? existing.values ?? [];
|
|
137222
|
+
if (currentValues.some((value) => value.key === "baseUrl" && value.value === mockUrl)) {
|
|
137223
|
+
dependencies.core.info(`Mock environment already points to ${mockUrl}: ${discovered.uid}`);
|
|
137224
|
+
return discovered.uid;
|
|
137225
|
+
}
|
|
137226
|
+
} catch {
|
|
137227
|
+
}
|
|
137228
|
+
await dependencies.postman.updateEnvironment(discovered.uid, displayName, values);
|
|
137229
|
+
dependencies.core.info(`Updated mock environment ${displayName}: ${discovered.uid}`);
|
|
137230
|
+
return discovered.uid;
|
|
137231
|
+
}
|
|
137232
|
+
const uid = await dependencies.postman.createEnvironment(
|
|
137233
|
+
inputs.workspaceId,
|
|
137234
|
+
displayName,
|
|
137235
|
+
values
|
|
137236
|
+
);
|
|
137237
|
+
dependencies.core.info(`Created mock environment ${displayName}: ${uid}`);
|
|
137238
|
+
return uid;
|
|
137239
|
+
} catch (error) {
|
|
137240
|
+
dependencies.core.warning(
|
|
137241
|
+
formatOrchestrationIssue({
|
|
137242
|
+
operation: "Mock environment upsert",
|
|
137243
|
+
entity: `workspace ${inputs.workspaceId} environment "${displayName}"`,
|
|
137244
|
+
cause: error,
|
|
137245
|
+
remediation: "verify environment access or disable mock-environment-enabled then rerun",
|
|
137246
|
+
mask
|
|
137247
|
+
})
|
|
137248
|
+
);
|
|
137249
|
+
return "";
|
|
137250
|
+
}
|
|
137251
|
+
}
|
|
137082
137252
|
function ensureDir(path5) {
|
|
137083
137253
|
(0, import_node_fs5.mkdirSync)(path5, { recursive: true });
|
|
137084
137254
|
}
|
|
@@ -137717,6 +137887,13 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
|
|
|
137717
137887
|
true
|
|
137718
137888
|
);
|
|
137719
137889
|
}
|
|
137890
|
+
if (options.mockEnvironmentUid) {
|
|
137891
|
+
writeJsonFile(
|
|
137892
|
+
`${mocksDir}/manual-validation.postman_environment.json`,
|
|
137893
|
+
await dependencies.postman.getEnvironment(options.mockEnvironmentUid),
|
|
137894
|
+
true
|
|
137895
|
+
);
|
|
137896
|
+
}
|
|
137720
137897
|
const durableWorkspaceId = resolveDurableWorkspaceId({
|
|
137721
137898
|
candidateId: inputs.workspaceId,
|
|
137722
137899
|
priorId: options.priorWorkspaceId,
|
|
@@ -137775,6 +137952,8 @@ function createRepoSummary(outputs, envUids, pushed) {
|
|
|
137775
137952
|
commitSha: outputs["commit-sha"],
|
|
137776
137953
|
environmentCount: Object.keys(envUids).length,
|
|
137777
137954
|
environmentSyncStatus: outputs["environment-sync-status"],
|
|
137955
|
+
mockEnvironmentStatus: outputs["mock-environment-status"],
|
|
137956
|
+
mockEnvironmentUid: outputs["mock-environment-uid"],
|
|
137778
137957
|
mockUrl: outputs["mock-url"],
|
|
137779
137958
|
monitorId: outputs["monitor-id"],
|
|
137780
137959
|
pushed,
|
|
@@ -138005,20 +138184,37 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
138005
138184
|
let resolvedMockUrl = "";
|
|
138006
138185
|
const mockName = `${assetProjectName} Mock`;
|
|
138007
138186
|
if (inputs.mockUrl) {
|
|
138008
|
-
|
|
138187
|
+
let mocks;
|
|
138188
|
+
try {
|
|
138189
|
+
mocks = await dependencies.postman.listMocks();
|
|
138190
|
+
} catch (error) {
|
|
138191
|
+
throw new Error(
|
|
138192
|
+
formatOrchestrationIssue({
|
|
138193
|
+
operation: "Explicit mock-url lookup",
|
|
138194
|
+
entity: `mock-url ${inputs.mockUrl} workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`,
|
|
138195
|
+
cause: error,
|
|
138196
|
+
remediation: "verify the URL and mock access then rerun",
|
|
138197
|
+
mask
|
|
138198
|
+
}),
|
|
138199
|
+
{ cause: error }
|
|
138200
|
+
);
|
|
138201
|
+
}
|
|
138202
|
+
resolvedMockUrl = resolveExplicitPublicMock(
|
|
138203
|
+
mocks,
|
|
138204
|
+
inputs.mockUrl,
|
|
138205
|
+
inputs.baselineCollectionId,
|
|
138206
|
+
mockEnvUid
|
|
138207
|
+
).mockUrl;
|
|
138009
138208
|
dependencies.core.info(`Reusing mock from explicit input: ${resolvedMockUrl}`);
|
|
138010
138209
|
}
|
|
138011
138210
|
if (!resolvedMockUrl && inputs.baselineCollectionId) {
|
|
138211
|
+
let discovered;
|
|
138012
138212
|
try {
|
|
138013
|
-
|
|
138213
|
+
discovered = await dependencies.postman.findMockByCollection(
|
|
138014
138214
|
inputs.baselineCollectionId,
|
|
138015
138215
|
mockEnvUid,
|
|
138016
138216
|
mockName
|
|
138017
138217
|
);
|
|
138018
|
-
if (discovered) {
|
|
138019
|
-
resolvedMockUrl = discovered.mockUrl;
|
|
138020
|
-
dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
|
|
138021
|
-
}
|
|
138022
138218
|
} catch (error) {
|
|
138023
138219
|
throw new Error(
|
|
138024
138220
|
formatOrchestrationIssue({
|
|
@@ -138031,6 +138227,10 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
138031
138227
|
{ cause: error }
|
|
138032
138228
|
);
|
|
138033
138229
|
}
|
|
138230
|
+
if (discovered) {
|
|
138231
|
+
resolvedMockUrl = requirePublicMock(discovered).mockUrl;
|
|
138232
|
+
dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
|
|
138233
|
+
}
|
|
138034
138234
|
}
|
|
138035
138235
|
if (!resolvedMockUrl) {
|
|
138036
138236
|
const mockEntity = `mock "${mockName}" workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`;
|
|
@@ -138047,6 +138247,7 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
138047
138247
|
maxAttempts: 3,
|
|
138048
138248
|
delayMs: 2e3,
|
|
138049
138249
|
backoffMultiplier: 2,
|
|
138250
|
+
shouldRetry: (error) => shouldRetryMockCreate(error),
|
|
138050
138251
|
onRetry: ({ attempt, maxAttempts, error }) => {
|
|
138051
138252
|
dependencies.core.warning(
|
|
138052
138253
|
formatOrchestrationIssue({
|
|
@@ -138078,6 +138279,20 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
138078
138279
|
}
|
|
138079
138280
|
outputs["mock-url"] = resolvedMockUrl;
|
|
138080
138281
|
dependencies.core.setOutput("mock-url", resolvedMockUrl);
|
|
138282
|
+
if (inputs.mockEnvironmentEnabled && isCanonicalWriter) {
|
|
138283
|
+
const mockEnvironmentUid = await upsertMockEnvironment(
|
|
138284
|
+
inputs,
|
|
138285
|
+
dependencies,
|
|
138286
|
+
assetProjectName,
|
|
138287
|
+
resolvedMockUrl
|
|
138288
|
+
);
|
|
138289
|
+
outputs["mock-environment-uid"] = mockEnvironmentUid;
|
|
138290
|
+
outputs["mock-environment-status"] = mockEnvironmentUid ? "success" : "failed";
|
|
138291
|
+
} else if (inputs.mockEnvironmentEnabled) {
|
|
138292
|
+
dependencies.core.warning(
|
|
138293
|
+
"mock-environment-enabled is skipped for preview and channel runs so manual-validation environments cannot escape branch retention cleanup."
|
|
138294
|
+
);
|
|
138295
|
+
}
|
|
138081
138296
|
}
|
|
138082
138297
|
}
|
|
138083
138298
|
if (inputs.workspaceId && inputs.smokeCollectionId && Object.keys(envUids).length > 0) {
|
|
@@ -138210,6 +138425,7 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
138210
138425
|
workspaceLinkStatus: outputs["workspace-link-status"],
|
|
138211
138426
|
priorWorkspaceId: resourcesState?.workspace?.id,
|
|
138212
138427
|
existingSpecs: resourcesState?.cloudResources?.specs,
|
|
138428
|
+
mockEnvironmentUid: outputs["mock-environment-uid"] || void 0,
|
|
138213
138429
|
releaseLabel,
|
|
138214
138430
|
priorState: resourcesState,
|
|
138215
138431
|
preparedPrebuiltCollections
|
|
@@ -139030,6 +139246,7 @@ var CLI_INPUT_NAMES = [
|
|
|
139030
139246
|
"org-mode",
|
|
139031
139247
|
"monitor-id",
|
|
139032
139248
|
"mock-url",
|
|
139249
|
+
"mock-environment-enabled",
|
|
139033
139250
|
"monitor-cron",
|
|
139034
139251
|
"ssl-client-cert",
|
|
139035
139252
|
"ssl-client-key",
|
package/dist/index.cjs
CHANGED
|
@@ -136682,6 +136682,11 @@ var postmanRepoSyncActionContract = {
|
|
|
136682
136682
|
description: "Existing mock server URL. When set, the action validates and reuses this mock instead of creating a new one.",
|
|
136683
136683
|
required: false
|
|
136684
136684
|
},
|
|
136685
|
+
"mock-environment-enabled": {
|
|
136686
|
+
description: "Create or update a dedicated manual-validation environment whose baseUrl is the validated public mock URL. This environment is excluded from runtime CI selection.",
|
|
136687
|
+
required: false,
|
|
136688
|
+
default: "false"
|
|
136689
|
+
},
|
|
136685
136690
|
"monitor-cron": {
|
|
136686
136691
|
description: "Cron expression for monitor scheduling (e.g. '0 */6 * * *'). When empty, the monitor is created disabled and triggered to run once per workflow invocation (and once on every subsequent run).",
|
|
136687
136692
|
required: false,
|
|
@@ -136875,6 +136880,12 @@ var postmanRepoSyncActionContract = {
|
|
|
136875
136880
|
"mock-url": {
|
|
136876
136881
|
description: "Created or reused mock server URL."
|
|
136877
136882
|
},
|
|
136883
|
+
"mock-environment-uid": {
|
|
136884
|
+
description: "Dedicated manual-validation environment UID when mock-environment-enabled succeeds."
|
|
136885
|
+
},
|
|
136886
|
+
"mock-environment-status": {
|
|
136887
|
+
description: "Whether the optional manual-validation mock environment succeeded, was skipped, or failed."
|
|
136888
|
+
},
|
|
136878
136889
|
"monitor-id": {
|
|
136879
136890
|
description: "Created or reused smoke monitor ID."
|
|
136880
136891
|
},
|
|
@@ -136963,6 +136974,25 @@ var PostmanAssetsClient = class {
|
|
|
136963
136974
|
};
|
|
136964
136975
|
|
|
136965
136976
|
// src/lib/postman/postman-gateway-assets-client.ts
|
|
136977
|
+
var MockContractError = class extends Error {
|
|
136978
|
+
constructor(message) {
|
|
136979
|
+
super(message);
|
|
136980
|
+
this.name = "MockContractError";
|
|
136981
|
+
}
|
|
136982
|
+
};
|
|
136983
|
+
function requirePublicMock(mock) {
|
|
136984
|
+
if (mock.visibility === "private") {
|
|
136985
|
+
throw new MockContractError(
|
|
136986
|
+
`MOCK_NOT_PUBLIC: Mock ${mock.uid} is private. Make it public in Postman or remove/rename it so repo-sync can create the canonical public mock.`
|
|
136987
|
+
);
|
|
136988
|
+
}
|
|
136989
|
+
if (mock.visibility !== "public") {
|
|
136990
|
+
throw new MockContractError(
|
|
136991
|
+
`MOCK_VISIBILITY_UNKNOWN: Mock ${mock.uid} did not expose a supported visibility field. Refusing to assume it is publicly callable.`
|
|
136992
|
+
);
|
|
136993
|
+
}
|
|
136994
|
+
return mock;
|
|
136995
|
+
}
|
|
136966
136996
|
var MAX_CREATE_FLIGHTS = 256;
|
|
136967
136997
|
var createFlights = /* @__PURE__ */ new Map();
|
|
136968
136998
|
var PostmanGatewayAssetsClient = class {
|
|
@@ -137144,6 +137174,41 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137144
137174
|
const id = record.uid ?? record.id;
|
|
137145
137175
|
return typeof id === "string" ? id.trim() : String(id ?? "").trim();
|
|
137146
137176
|
}
|
|
137177
|
+
decodeMockRecord(value, operation) {
|
|
137178
|
+
const record = this.asRecord(value);
|
|
137179
|
+
if (!record) {
|
|
137180
|
+
throw new MockContractError(
|
|
137181
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a non-object mock record`
|
|
137182
|
+
);
|
|
137183
|
+
}
|
|
137184
|
+
const uid = this.idOf(record);
|
|
137185
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
137186
|
+
const collection = typeof record.collection === "string" ? record.collection.trim() : "";
|
|
137187
|
+
const mockUrl = typeof (record.url ?? record.mockUrl) === "string" ? String(record.url ?? record.mockUrl).trim() : "";
|
|
137188
|
+
const environment = typeof record.environment === "string" ? record.environment.trim() : "";
|
|
137189
|
+
if (!uid) {
|
|
137190
|
+
throw new MockContractError(
|
|
137191
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned a mock without a UID`
|
|
137192
|
+
);
|
|
137193
|
+
}
|
|
137194
|
+
if (!name) {
|
|
137195
|
+
throw new MockContractError(
|
|
137196
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a name`
|
|
137197
|
+
);
|
|
137198
|
+
}
|
|
137199
|
+
if (!collection) {
|
|
137200
|
+
throw new MockContractError(
|
|
137201
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a collection UID`
|
|
137202
|
+
);
|
|
137203
|
+
}
|
|
137204
|
+
if (!mockUrl) {
|
|
137205
|
+
throw new MockContractError(
|
|
137206
|
+
`CONTRACT_MOCK_RESPONSE_INVALID: ${operation} returned mock ${uid} without a URL`
|
|
137207
|
+
);
|
|
137208
|
+
}
|
|
137209
|
+
const visibility = typeof record.published === "boolean" ? record.published ? "public" : "private" : "unknown";
|
|
137210
|
+
return { uid, name, collection, mockUrl, environment, visibility };
|
|
137211
|
+
}
|
|
137147
137212
|
/**
|
|
137148
137213
|
* Reduce a Postman public uid (`<owner>-<uuid>`, 6 hyphen groups) to the bare
|
|
137149
137214
|
* model id (`<uuid>`, 5 groups), mirroring `decomposeUID`. Used ONLY for the
|
|
@@ -137444,7 +137509,8 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137444
137509
|
return this.singleFlight(flightKey, flightKey, "mock", async () => {
|
|
137445
137510
|
const existing = await this.findMockByCollection(collection, environment, mockName);
|
|
137446
137511
|
if (existing) {
|
|
137447
|
-
|
|
137512
|
+
const reusable = requirePublicMock(existing);
|
|
137513
|
+
return { uid: reusable.uid, url: reusable.mockUrl };
|
|
137448
137514
|
}
|
|
137449
137515
|
const body = {
|
|
137450
137516
|
name: mockName,
|
|
@@ -137467,13 +137533,12 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137467
137533
|
);
|
|
137468
137534
|
const parseMock = (response) => {
|
|
137469
137535
|
const record = this.dataOf(response);
|
|
137470
|
-
const
|
|
137471
|
-
|
|
137472
|
-
|
|
137473
|
-
}
|
|
137536
|
+
const created = requirePublicMock(
|
|
137537
|
+
this.decodeMockRecord(record, "Mock create")
|
|
137538
|
+
);
|
|
137474
137539
|
return {
|
|
137475
|
-
uid,
|
|
137476
|
-
url:
|
|
137540
|
+
uid: created.uid,
|
|
137541
|
+
url: created.mockUrl
|
|
137477
137542
|
};
|
|
137478
137543
|
};
|
|
137479
137544
|
try {
|
|
@@ -137484,7 +137549,8 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137484
137549
|
error2
|
|
137485
137550
|
);
|
|
137486
137551
|
if (adopted) {
|
|
137487
|
-
|
|
137552
|
+
const reusable = requirePublicMock(adopted);
|
|
137553
|
+
return { uid: reusable.uid, url: reusable.mockUrl };
|
|
137488
137554
|
}
|
|
137489
137555
|
if (adopted === void 0) {
|
|
137490
137556
|
const retried = await this.resendAbsentCreate(async () => parseMock(await send2("auto")));
|
|
@@ -137500,14 +137566,14 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137500
137566
|
method: "get",
|
|
137501
137567
|
path: `/mocks?workspace=${this.workspaceId}`
|
|
137502
137568
|
});
|
|
137503
|
-
const
|
|
137504
|
-
|
|
137505
|
-
|
|
137506
|
-
|
|
137507
|
-
|
|
137508
|
-
|
|
137509
|
-
|
|
137510
|
-
|
|
137569
|
+
const envelope = this.asRecord(response);
|
|
137570
|
+
const items = Array.isArray(response) ? response : Array.isArray(envelope?.data) ? envelope.data : null;
|
|
137571
|
+
if (!items) {
|
|
137572
|
+
throw new MockContractError(
|
|
137573
|
+
"CONTRACT_MOCK_RESPONSE_INVALID: Mock list envelope must be an array or contain a data array"
|
|
137574
|
+
);
|
|
137575
|
+
}
|
|
137576
|
+
return items.map((raw) => this.decodeMockRecord(raw, "Mock list"));
|
|
137511
137577
|
}
|
|
137512
137578
|
/**
|
|
137513
137579
|
* Delete an environment through the sync service (GC path). The path id is
|
|
@@ -137561,7 +137627,7 @@ var PostmanGatewayAssetsClient = class {
|
|
|
137561
137627
|
`workspace ${this.workspaceId}, name "${mockName}", collection ${want}, and environment ${environment || "(none)"}`,
|
|
137562
137628
|
matches
|
|
137563
137629
|
);
|
|
137564
|
-
return match
|
|
137630
|
+
return match;
|
|
137565
137631
|
}
|
|
137566
137632
|
// --- monitors (service: monitors; collection-based = jobTemplates) ---
|
|
137567
137633
|
/** Map raw jobTemplate records to the facade shape; `collection` is a flat public uid. */
|
|
@@ -138491,6 +138557,61 @@ function resolveRepoSyncMasker(dependencies) {
|
|
|
138491
138557
|
function causeText(cause) {
|
|
138492
138558
|
return cause instanceof Error ? cause.message : String(cause);
|
|
138493
138559
|
}
|
|
138560
|
+
function normalizeMockUrl(value, source) {
|
|
138561
|
+
let url;
|
|
138562
|
+
try {
|
|
138563
|
+
url = new URL(String(value || "").trim());
|
|
138564
|
+
} catch {
|
|
138565
|
+
throw new MockContractError(
|
|
138566
|
+
`EXPLICIT_MOCK_URL_INVALID: ${source} is not a valid absolute URL`
|
|
138567
|
+
);
|
|
138568
|
+
}
|
|
138569
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) {
|
|
138570
|
+
throw new MockContractError(
|
|
138571
|
+
`EXPLICIT_MOCK_URL_INVALID: ${source} must be an HTTPS URL without credentials, query parameters, or a fragment`
|
|
138572
|
+
);
|
|
138573
|
+
}
|
|
138574
|
+
url.pathname = url.pathname.replace(/\/+$/g, "") || "/";
|
|
138575
|
+
return url.toString();
|
|
138576
|
+
}
|
|
138577
|
+
function resolveExplicitPublicMock(mocks, explicitUrl, collectionUid, environmentUid) {
|
|
138578
|
+
const normalized = normalizeMockUrl(explicitUrl, "mock-url");
|
|
138579
|
+
const matches = mocks.filter(
|
|
138580
|
+
(mock) => normalizeMockUrl(mock.mockUrl, `mock ${mock.uid} URL`) === normalized
|
|
138581
|
+
);
|
|
138582
|
+
if (matches.length === 0) {
|
|
138583
|
+
throw new MockContractError(
|
|
138584
|
+
`EXPLICIT_MOCK_URL_NOT_FOUND: mock-url was not found in the resolved workspace mock inventory`
|
|
138585
|
+
);
|
|
138586
|
+
}
|
|
138587
|
+
if (matches.length > 1) {
|
|
138588
|
+
throw new MockContractError(
|
|
138589
|
+
`EXPLICIT_MOCK_URL_AMBIGUOUS: mock-url matched multiple workspace mocks (${matches.map((mock) => mock.uid).join(", ")})`
|
|
138590
|
+
);
|
|
138591
|
+
}
|
|
138592
|
+
const match = matches[0];
|
|
138593
|
+
if (match.collection !== collectionUid) {
|
|
138594
|
+
throw new MockContractError(
|
|
138595
|
+
`EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references collection ${match.collection || "(none)"}, expected ${collectionUid}`
|
|
138596
|
+
);
|
|
138597
|
+
}
|
|
138598
|
+
if (match.environment !== environmentUid) {
|
|
138599
|
+
throw new MockContractError(
|
|
138600
|
+
`EXPLICIT_MOCK_IDENTITY_MISMATCH: Mock ${match.uid} references environment ${match.environment || "(none)"}, expected ${environmentUid}`
|
|
138601
|
+
);
|
|
138602
|
+
}
|
|
138603
|
+
return requirePublicMock(match);
|
|
138604
|
+
}
|
|
138605
|
+
function shouldRetryMockCreate(error2) {
|
|
138606
|
+
if (error2 instanceof MockContractError) return false;
|
|
138607
|
+
if (error2 instanceof HttpError) {
|
|
138608
|
+
return error2.status === 408 || error2.status === 429 || error2.status >= 500;
|
|
138609
|
+
}
|
|
138610
|
+
if (error2 instanceof Error && error2.message.startsWith("Multiple mocks match ")) {
|
|
138611
|
+
return false;
|
|
138612
|
+
}
|
|
138613
|
+
return true;
|
|
138614
|
+
}
|
|
138494
138615
|
function toOneLineDisplay(value) {
|
|
138495
138616
|
return String(value ?? "").replace(/[\r\n\u0085\u2028\u2029\v\f]+/g, " ").replace(/[ \t]{2,}/g, " ").trim();
|
|
138496
138617
|
}
|
|
@@ -138697,6 +138818,7 @@ function resolveInputs(env = process.env) {
|
|
|
138697
138818
|
orgMode: parseBooleanInput(getInput2("org-mode", env), false),
|
|
138698
138819
|
monitorId: getInput2("monitor-id", env),
|
|
138699
138820
|
mockUrl: getInput2("mock-url", env),
|
|
138821
|
+
mockEnvironmentEnabled: parseBooleanInput(getInput2("mock-environment-enabled", env), false),
|
|
138700
138822
|
monitorCron: getInput2("monitor-cron", env),
|
|
138701
138823
|
sslClientCert: getInput2("ssl-client-cert", env),
|
|
138702
138824
|
sslClientKey: getInput2("ssl-client-key", env),
|
|
@@ -138923,6 +139045,8 @@ function createOutputs(inputs) {
|
|
|
138923
139045
|
"environment-sync-status": "skipped",
|
|
138924
139046
|
"environment-uids-json": JSON.stringify(inputs.environmentUids),
|
|
138925
139047
|
"mock-url": "",
|
|
139048
|
+
"mock-environment-uid": "",
|
|
139049
|
+
"mock-environment-status": "skipped",
|
|
138926
139050
|
"monitor-id": "",
|
|
138927
139051
|
"repo-sync-summary-json": "{}",
|
|
138928
139052
|
"commit-sha": "",
|
|
@@ -138990,6 +139114,7 @@ function readActionInputs(actionCore) {
|
|
|
138990
139114
|
INPUT_ORG_MODE: readInput(actionCore, "org-mode"),
|
|
138991
139115
|
INPUT_MONITOR_ID: readInput(actionCore, "monitor-id"),
|
|
138992
139116
|
INPUT_MOCK_URL: readInput(actionCore, "mock-url"),
|
|
139117
|
+
INPUT_MOCK_ENVIRONMENT_ENABLED: readInput(actionCore, "mock-environment-enabled"),
|
|
138993
139118
|
INPUT_MONITOR_CRON: readInput(actionCore, "monitor-cron"),
|
|
138994
139119
|
INPUT_SSL_CLIENT_CERT: sslClientCert,
|
|
138995
139120
|
INPUT_SSL_CLIENT_KEY: sslClientKey,
|
|
@@ -139191,6 +139316,52 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
|
|
|
139191
139316
|
}
|
|
139192
139317
|
return envUids;
|
|
139193
139318
|
}
|
|
139319
|
+
async function upsertMockEnvironment(inputs, dependencies, assetProjectName, mockUrl) {
|
|
139320
|
+
if (!inputs.mockEnvironmentEnabled || !inputs.workspaceId || !mockUrl) {
|
|
139321
|
+
return "";
|
|
139322
|
+
}
|
|
139323
|
+
const displayName = `${assetProjectName} - Mock`;
|
|
139324
|
+
const values = buildEnvironmentValues("mock", mockUrl);
|
|
139325
|
+
const mask = resolveRepoSyncMasker(dependencies);
|
|
139326
|
+
try {
|
|
139327
|
+
const discovered = await dependencies.postman.findEnvironmentByName(
|
|
139328
|
+
inputs.workspaceId,
|
|
139329
|
+
displayName
|
|
139330
|
+
);
|
|
139331
|
+
if (discovered?.uid) {
|
|
139332
|
+
try {
|
|
139333
|
+
const existing = await dependencies.postman.getEnvironment(discovered.uid);
|
|
139334
|
+
const currentValues = existing.data?.values ?? existing.values ?? [];
|
|
139335
|
+
if (currentValues.some((value) => value.key === "baseUrl" && value.value === mockUrl)) {
|
|
139336
|
+
dependencies.core.info(`Mock environment already points to ${mockUrl}: ${discovered.uid}`);
|
|
139337
|
+
return discovered.uid;
|
|
139338
|
+
}
|
|
139339
|
+
} catch {
|
|
139340
|
+
}
|
|
139341
|
+
await dependencies.postman.updateEnvironment(discovered.uid, displayName, values);
|
|
139342
|
+
dependencies.core.info(`Updated mock environment ${displayName}: ${discovered.uid}`);
|
|
139343
|
+
return discovered.uid;
|
|
139344
|
+
}
|
|
139345
|
+
const uid = await dependencies.postman.createEnvironment(
|
|
139346
|
+
inputs.workspaceId,
|
|
139347
|
+
displayName,
|
|
139348
|
+
values
|
|
139349
|
+
);
|
|
139350
|
+
dependencies.core.info(`Created mock environment ${displayName}: ${uid}`);
|
|
139351
|
+
return uid;
|
|
139352
|
+
} catch (error2) {
|
|
139353
|
+
dependencies.core.warning(
|
|
139354
|
+
formatOrchestrationIssue({
|
|
139355
|
+
operation: "Mock environment upsert",
|
|
139356
|
+
entity: `workspace ${inputs.workspaceId} environment "${displayName}"`,
|
|
139357
|
+
cause: error2,
|
|
139358
|
+
remediation: "verify environment access or disable mock-environment-enabled then rerun",
|
|
139359
|
+
mask
|
|
139360
|
+
})
|
|
139361
|
+
);
|
|
139362
|
+
return "";
|
|
139363
|
+
}
|
|
139364
|
+
}
|
|
139194
139365
|
function ensureDir(path9) {
|
|
139195
139366
|
(0, import_node_fs5.mkdirSync)(path9, { recursive: true });
|
|
139196
139367
|
}
|
|
@@ -139829,6 +140000,13 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
|
|
|
139829
140000
|
true
|
|
139830
140001
|
);
|
|
139831
140002
|
}
|
|
140003
|
+
if (options.mockEnvironmentUid) {
|
|
140004
|
+
writeJsonFile(
|
|
140005
|
+
`${mocksDir}/manual-validation.postman_environment.json`,
|
|
140006
|
+
await dependencies.postman.getEnvironment(options.mockEnvironmentUid),
|
|
140007
|
+
true
|
|
140008
|
+
);
|
|
140009
|
+
}
|
|
139832
140010
|
const durableWorkspaceId = resolveDurableWorkspaceId({
|
|
139833
140011
|
candidateId: inputs.workspaceId,
|
|
139834
140012
|
priorId: options.priorWorkspaceId,
|
|
@@ -139887,6 +140065,8 @@ function createRepoSummary(outputs, envUids, pushed) {
|
|
|
139887
140065
|
commitSha: outputs["commit-sha"],
|
|
139888
140066
|
environmentCount: Object.keys(envUids).length,
|
|
139889
140067
|
environmentSyncStatus: outputs["environment-sync-status"],
|
|
140068
|
+
mockEnvironmentStatus: outputs["mock-environment-status"],
|
|
140069
|
+
mockEnvironmentUid: outputs["mock-environment-uid"],
|
|
139890
140070
|
mockUrl: outputs["mock-url"],
|
|
139891
140071
|
monitorId: outputs["monitor-id"],
|
|
139892
140072
|
pushed,
|
|
@@ -140117,20 +140297,37 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
140117
140297
|
let resolvedMockUrl = "";
|
|
140118
140298
|
const mockName = `${assetProjectName} Mock`;
|
|
140119
140299
|
if (inputs.mockUrl) {
|
|
140120
|
-
|
|
140300
|
+
let mocks;
|
|
140301
|
+
try {
|
|
140302
|
+
mocks = await dependencies.postman.listMocks();
|
|
140303
|
+
} catch (error2) {
|
|
140304
|
+
throw new Error(
|
|
140305
|
+
formatOrchestrationIssue({
|
|
140306
|
+
operation: "Explicit mock-url lookup",
|
|
140307
|
+
entity: `mock-url ${inputs.mockUrl} workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`,
|
|
140308
|
+
cause: error2,
|
|
140309
|
+
remediation: "verify the URL and mock access then rerun",
|
|
140310
|
+
mask
|
|
140311
|
+
}),
|
|
140312
|
+
{ cause: error2 }
|
|
140313
|
+
);
|
|
140314
|
+
}
|
|
140315
|
+
resolvedMockUrl = resolveExplicitPublicMock(
|
|
140316
|
+
mocks,
|
|
140317
|
+
inputs.mockUrl,
|
|
140318
|
+
inputs.baselineCollectionId,
|
|
140319
|
+
mockEnvUid
|
|
140320
|
+
).mockUrl;
|
|
140121
140321
|
dependencies.core.info(`Reusing mock from explicit input: ${resolvedMockUrl}`);
|
|
140122
140322
|
}
|
|
140123
140323
|
if (!resolvedMockUrl && inputs.baselineCollectionId) {
|
|
140324
|
+
let discovered;
|
|
140124
140325
|
try {
|
|
140125
|
-
|
|
140326
|
+
discovered = await dependencies.postman.findMockByCollection(
|
|
140126
140327
|
inputs.baselineCollectionId,
|
|
140127
140328
|
mockEnvUid,
|
|
140128
140329
|
mockName
|
|
140129
140330
|
);
|
|
140130
|
-
if (discovered) {
|
|
140131
|
-
resolvedMockUrl = discovered.mockUrl;
|
|
140132
|
-
dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
|
|
140133
|
-
}
|
|
140134
140331
|
} catch (error2) {
|
|
140135
140332
|
throw new Error(
|
|
140136
140333
|
formatOrchestrationIssue({
|
|
@@ -140143,6 +140340,10 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
140143
140340
|
{ cause: error2 }
|
|
140144
140341
|
);
|
|
140145
140342
|
}
|
|
140343
|
+
if (discovered) {
|
|
140344
|
+
resolvedMockUrl = requirePublicMock(discovered).mockUrl;
|
|
140345
|
+
dependencies.core.info(`Discovered existing mock for collection ${inputs.baselineCollectionId}: ${resolvedMockUrl}`);
|
|
140346
|
+
}
|
|
140146
140347
|
}
|
|
140147
140348
|
if (!resolvedMockUrl) {
|
|
140148
140349
|
const mockEntity = `mock "${mockName}" workspace ${inputs.workspaceId} collection ${inputs.baselineCollectionId} environment ${mockEnvUid}`;
|
|
@@ -140159,6 +140360,7 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
140159
140360
|
maxAttempts: 3,
|
|
140160
140361
|
delayMs: 2e3,
|
|
140161
140362
|
backoffMultiplier: 2,
|
|
140363
|
+
shouldRetry: (error2) => shouldRetryMockCreate(error2),
|
|
140162
140364
|
onRetry: ({ attempt, maxAttempts, error: error2 }) => {
|
|
140163
140365
|
dependencies.core.warning(
|
|
140164
140366
|
formatOrchestrationIssue({
|
|
@@ -140190,6 +140392,20 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
140190
140392
|
}
|
|
140191
140393
|
outputs["mock-url"] = resolvedMockUrl;
|
|
140192
140394
|
dependencies.core.setOutput("mock-url", resolvedMockUrl);
|
|
140395
|
+
if (inputs.mockEnvironmentEnabled && isCanonicalWriter) {
|
|
140396
|
+
const mockEnvironmentUid = await upsertMockEnvironment(
|
|
140397
|
+
inputs,
|
|
140398
|
+
dependencies,
|
|
140399
|
+
assetProjectName,
|
|
140400
|
+
resolvedMockUrl
|
|
140401
|
+
);
|
|
140402
|
+
outputs["mock-environment-uid"] = mockEnvironmentUid;
|
|
140403
|
+
outputs["mock-environment-status"] = mockEnvironmentUid ? "success" : "failed";
|
|
140404
|
+
} else if (inputs.mockEnvironmentEnabled) {
|
|
140405
|
+
dependencies.core.warning(
|
|
140406
|
+
"mock-environment-enabled is skipped for preview and channel runs so manual-validation environments cannot escape branch retention cleanup."
|
|
140407
|
+
);
|
|
140408
|
+
}
|
|
140193
140409
|
}
|
|
140194
140410
|
}
|
|
140195
140411
|
if (inputs.workspaceId && inputs.smokeCollectionId && Object.keys(envUids).length > 0) {
|
|
@@ -140322,6 +140538,7 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
140322
140538
|
workspaceLinkStatus: outputs["workspace-link-status"],
|
|
140323
140539
|
priorWorkspaceId: resourcesState?.workspace?.id,
|
|
140324
140540
|
existingSpecs: resourcesState?.cloudResources?.specs,
|
|
140541
|
+
mockEnvironmentUid: outputs["mock-environment-uid"] || void 0,
|
|
140325
140542
|
releaseLabel,
|
|
140326
140543
|
priorState: resourcesState,
|
|
140327
140544
|
preparedPrebuiltCollections
|