@postman-cse/onboarding-bootstrap 0.9.1 → 0.10.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/.github/workflows/contract-smoke.yml +155 -0
- package/.omc/sessions/b36ac16d-ce26-4e94-ae7e-dd817f2430a3.json +8 -0
- package/CLAUDE.md +57 -0
- package/README.md +124 -12
- package/action.yml +17 -18
- package/dist/cli.cjs +231 -489
- package/dist/index.cjs +226 -480
- package/package.json +3 -2
- package/src/cli.ts +5 -11
- package/src/contracts.ts +24 -24
- package/src/index.ts +253 -323
- package/src/lib/postman/postman-assets-client.ts +27 -4
- package/tests/bootstrap-action.test.ts +565 -79
- package/tests/cli.test.ts +12 -6
- package/tests/contract.test.ts +20 -3
- package/tests/postman-assets-client.test.ts +153 -0
package/src/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as core from '@actions/core';
|
|
2
2
|
import * as exec from '@actions/exec';
|
|
3
3
|
import * as io from '@actions/io';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
4
5
|
import { parse, stringify } from 'yaml';
|
|
5
6
|
|
|
6
7
|
import { openAlphaActionContract } from './contracts.js';
|
|
7
|
-
import { GitHubApiClient, type GitHubApiClientAuthMode } from './lib/github/github-api-client.js';
|
|
8
8
|
import { createInternalIntegrationAdapter, type InternalIntegrationAdapter } from './lib/postman/internal-integration-adapter.js';
|
|
9
9
|
import { PostmanAssetsClient } from './lib/postman/postman-assets-client.js';
|
|
10
10
|
import { resolveCanonicalWorkspaceSelection } from './lib/postman/workspace-selection.js';
|
|
@@ -19,22 +19,25 @@ export interface ResolvedInputs {
|
|
|
19
19
|
baselineCollectionId?: string;
|
|
20
20
|
smokeCollectionId?: string;
|
|
21
21
|
contractCollectionId?: string;
|
|
22
|
+
collectionSyncMode: 'reuse' | 'refresh' | 'version';
|
|
23
|
+
specSyncMode: 'update' | 'version';
|
|
24
|
+
releaseLabel?: string;
|
|
22
25
|
domain?: string;
|
|
23
26
|
domainCode?: string;
|
|
24
27
|
requesterEmail?: string;
|
|
25
28
|
workspaceAdminUserIds?: string;
|
|
29
|
+
workspaceTeamId?: string;
|
|
26
30
|
teamId?: string;
|
|
27
31
|
repoUrl?: string;
|
|
28
32
|
specUrl: string;
|
|
29
|
-
environmentsJson: string;
|
|
30
|
-
systemEnvMapJson: string;
|
|
31
33
|
governanceMappingJson: string;
|
|
32
34
|
postmanApiKey: string;
|
|
33
35
|
postmanAccessToken?: string;
|
|
34
|
-
githubToken?: string;
|
|
35
|
-
ghFallbackToken?: string;
|
|
36
|
-
githubAuthMode: string;
|
|
37
36
|
integrationBackend: string;
|
|
37
|
+
githubRefName?: string;
|
|
38
|
+
githubHeadRef?: string;
|
|
39
|
+
githubRef?: string;
|
|
40
|
+
githubSha?: string;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
export interface PlannedOutputs {
|
|
@@ -61,13 +64,6 @@ export interface LintSummary {
|
|
|
61
64
|
warnings: number;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
interface BootstrapRepositoryVariables {
|
|
65
|
-
lintErrors: number;
|
|
66
|
-
lintWarnings: number;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
type RepoVariableClient = Pick<GitHubApiClient, 'setRepositoryVariable' | 'getRepositoryVariable'>;
|
|
70
|
-
|
|
71
67
|
export interface CoreLike {
|
|
72
68
|
error(message: string): void;
|
|
73
69
|
getInput(name: string, options?: { required?: boolean }): string;
|
|
@@ -102,7 +98,6 @@ export interface BootstrapExecutionDependencies {
|
|
|
102
98
|
'error' | 'group' | 'info' | 'setOutput' | 'warning'
|
|
103
99
|
>;
|
|
104
100
|
exec: ExecLike;
|
|
105
|
-
github?: RepoVariableClient;
|
|
106
101
|
io: IOLike;
|
|
107
102
|
internalIntegration?: Pick<
|
|
108
103
|
InternalIntegrationAdapter,
|
|
@@ -116,6 +111,7 @@ export interface BootstrapExecutionDependencies {
|
|
|
116
111
|
| 'generateCollection'
|
|
117
112
|
| 'getAutoDerivedTeamId'
|
|
118
113
|
| 'getSpecContent'
|
|
114
|
+
| 'getTeams'
|
|
119
115
|
| 'getWorkspaceGitRepoUrl'
|
|
120
116
|
| 'injectTests'
|
|
121
117
|
| 'inviteRequesterToWorkspace'
|
|
@@ -185,16 +181,28 @@ function asStringArray(value: unknown, inputName: string): string[] {
|
|
|
185
181
|
return value.map((entry) => String(entry));
|
|
186
182
|
}
|
|
187
183
|
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
|
|
184
|
+
function parseBooleanInput(value: string | undefined, defaultValue: boolean): boolean {
|
|
185
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
186
|
+
if (!normalized) return defaultValue;
|
|
187
|
+
if (['true', '1', 'yes', 'on'].includes(normalized)) return true;
|
|
188
|
+
if (['false', '0', 'no', 'off'].includes(normalized)) return false;
|
|
189
|
+
return defaultValue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function parseCollectionSyncMode(
|
|
193
|
+
value: string | undefined
|
|
194
|
+
): 'reuse' | 'refresh' | 'version' {
|
|
195
|
+
if (value === 'reuse' || value === 'version') {
|
|
196
|
+
return value;
|
|
191
197
|
}
|
|
192
|
-
return
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
return 'refresh';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function parseSpecSyncMode(value: string | undefined): 'update' | 'version' {
|
|
202
|
+
if (value === 'version') {
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
return 'update';
|
|
198
206
|
}
|
|
199
207
|
|
|
200
208
|
export function resolveInputs(
|
|
@@ -233,41 +241,38 @@ export function resolveInputs(
|
|
|
233
241
|
}
|
|
234
242
|
|
|
235
243
|
return {
|
|
236
|
-
projectName: getInput('project-name', env)
|
|
244
|
+
projectName: getInput('project-name', env)
|
|
245
|
+
?? env.GITHUB_REPOSITORY?.split('/').pop()
|
|
246
|
+
?? env.CI_PROJECT_NAME
|
|
247
|
+
?? '',
|
|
237
248
|
workspaceId: getInput('workspace-id', env),
|
|
238
249
|
specId: getInput('spec-id', env),
|
|
239
250
|
baselineCollectionId: getInput('baseline-collection-id', env),
|
|
240
251
|
smokeCollectionId: getInput('smoke-collection-id', env),
|
|
241
252
|
contractCollectionId: getInput('contract-collection-id', env),
|
|
253
|
+
collectionSyncMode: parseCollectionSyncMode(getInput('collection-sync-mode', env)),
|
|
254
|
+
specSyncMode: parseSpecSyncMode(getInput('spec-sync-mode', env)),
|
|
255
|
+
releaseLabel: getInput('release-label', env),
|
|
242
256
|
domain: getInput('domain', env),
|
|
243
257
|
domainCode: getInput('domain-code', env),
|
|
244
258
|
requesterEmail: getInput('requester-email', env),
|
|
245
259
|
workspaceAdminUserIds:
|
|
246
260
|
getInput('workspace-admin-user-ids', env) || env.WORKSPACE_ADMIN_USER_IDS || '',
|
|
261
|
+
workspaceTeamId: getInput('workspace-team-id', env) || env.POSTMAN_WORKSPACE_TEAM_ID,
|
|
247
262
|
teamId: getInput('team-id', env) || env.POSTMAN_TEAM_ID || '',
|
|
248
263
|
repoUrl: repoContext.repoUrl || '',
|
|
249
264
|
specUrl,
|
|
250
|
-
environmentsJson:
|
|
251
|
-
getInput('environments-json', env) ??
|
|
252
|
-
openAlphaActionContract.inputs['environments-json'].default ??
|
|
253
|
-
'["prod"]',
|
|
254
|
-
systemEnvMapJson:
|
|
255
|
-
getInput('system-env-map-json', env) ??
|
|
256
|
-
openAlphaActionContract.inputs['system-env-map-json'].default ??
|
|
257
|
-
'{}',
|
|
258
265
|
governanceMappingJson:
|
|
259
266
|
getInput('governance-mapping-json', env) ??
|
|
260
267
|
openAlphaActionContract.inputs['governance-mapping-json'].default ??
|
|
261
268
|
'{}',
|
|
262
269
|
postmanApiKey: getInput('postman-api-key', env) ?? '',
|
|
263
270
|
postmanAccessToken: getInput('postman-access-token', env),
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
'github_token_first',
|
|
270
|
-
integrationBackend
|
|
271
|
+
integrationBackend,
|
|
272
|
+
githubRefName: env.GITHUB_REF_NAME,
|
|
273
|
+
githubHeadRef: env.GITHUB_HEAD_REF,
|
|
274
|
+
githubRef: env.GITHUB_REF,
|
|
275
|
+
githubSha: env.GITHUB_SHA
|
|
271
276
|
};
|
|
272
277
|
}
|
|
273
278
|
|
|
@@ -305,13 +310,9 @@ export function readActionInputs(
|
|
|
305
310
|
const specUrl = requireInput(actionCore, 'spec-url');
|
|
306
311
|
const postmanApiKey = requireInput(actionCore, 'postman-api-key');
|
|
307
312
|
const postmanAccessToken = optionalInput(actionCore, 'postman-access-token');
|
|
308
|
-
const githubToken = optionalInput(actionCore, 'github-token');
|
|
309
|
-
const ghFallbackToken = optionalInput(actionCore, 'gh-fallback-token');
|
|
310
313
|
|
|
311
314
|
actionCore.setSecret(postmanApiKey);
|
|
312
315
|
if (postmanAccessToken) actionCore.setSecret(postmanAccessToken);
|
|
313
|
-
if (githubToken) actionCore.setSecret(githubToken);
|
|
314
|
-
if (ghFallbackToken) actionCore.setSecret(ghFallbackToken);
|
|
315
316
|
|
|
316
317
|
const inputs = resolveInputs({
|
|
317
318
|
...process.env,
|
|
@@ -321,6 +322,13 @@ export function readActionInputs(
|
|
|
321
322
|
INPUT_BASELINE_COLLECTION_ID: optionalInput(actionCore, 'baseline-collection-id'),
|
|
322
323
|
INPUT_SMOKE_COLLECTION_ID: optionalInput(actionCore, 'smoke-collection-id'),
|
|
323
324
|
INPUT_CONTRACT_COLLECTION_ID: optionalInput(actionCore, 'contract-collection-id'),
|
|
325
|
+
INPUT_COLLECTION_SYNC_MODE:
|
|
326
|
+
optionalInput(actionCore, 'collection-sync-mode') ??
|
|
327
|
+
openAlphaActionContract.inputs['collection-sync-mode'].default,
|
|
328
|
+
INPUT_SPEC_SYNC_MODE:
|
|
329
|
+
optionalInput(actionCore, 'spec-sync-mode') ??
|
|
330
|
+
openAlphaActionContract.inputs['spec-sync-mode'].default,
|
|
331
|
+
INPUT_RELEASE_LABEL: optionalInput(actionCore, 'release-label'),
|
|
324
332
|
INPUT_DOMAIN: optionalInput(actionCore, 'domain'),
|
|
325
333
|
INPUT_DOMAIN_CODE: optionalInput(actionCore, 'domain-code'),
|
|
326
334
|
INPUT_REQUESTER_EMAIL: optionalInput(actionCore, 'requester-email'),
|
|
@@ -328,26 +336,17 @@ export function readActionInputs(
|
|
|
328
336
|
actionCore,
|
|
329
337
|
'workspace-admin-user-ids'
|
|
330
338
|
),
|
|
339
|
+
INPUT_WORKSPACE_TEAM_ID:
|
|
340
|
+
optionalInput(actionCore, 'workspace-team-id') || process.env.POSTMAN_WORKSPACE_TEAM_ID,
|
|
331
341
|
INPUT_TEAM_ID:
|
|
332
342
|
optionalInput(actionCore, 'postman-team-id') || process.env.POSTMAN_TEAM_ID,
|
|
333
343
|
INPUT_REPO_URL: optionalInput(actionCore, 'repo-url'),
|
|
334
344
|
INPUT_SPEC_URL: specUrl,
|
|
335
|
-
INPUT_ENVIRONMENTS_JSON:
|
|
336
|
-
optionalInput(actionCore, 'environments-json') ??
|
|
337
|
-
openAlphaActionContract.inputs['environments-json'].default,
|
|
338
|
-
INPUT_SYSTEM_ENV_MAP_JSON:
|
|
339
|
-
optionalInput(actionCore, 'system-env-map-json') ??
|
|
340
|
-
openAlphaActionContract.inputs['system-env-map-json'].default,
|
|
341
345
|
INPUT_GOVERNANCE_MAPPING_JSON:
|
|
342
346
|
optionalInput(actionCore, 'governance-mapping-json') ??
|
|
343
347
|
openAlphaActionContract.inputs['governance-mapping-json'].default,
|
|
344
348
|
INPUT_POSTMAN_API_KEY: postmanApiKey,
|
|
345
349
|
INPUT_POSTMAN_ACCESS_TOKEN: postmanAccessToken,
|
|
346
|
-
INPUT_GITHUB_TOKEN: githubToken,
|
|
347
|
-
INPUT_GH_FALLBACK_TOKEN: ghFallbackToken,
|
|
348
|
-
INPUT_GITHUB_AUTH_MODE:
|
|
349
|
-
optionalInput(actionCore, 'github-auth-mode') ??
|
|
350
|
-
openAlphaActionContract.inputs['github-auth-mode'].default,
|
|
351
350
|
INPUT_INTEGRATION_BACKEND:
|
|
352
351
|
optionalInput(actionCore, 'integration-backend') ??
|
|
353
352
|
openAlphaActionContract.inputs['integration-backend'].default
|
|
@@ -456,6 +455,83 @@ async function fetchSpecDocument(
|
|
|
456
455
|
);
|
|
457
456
|
}
|
|
458
457
|
|
|
458
|
+
function normalizeReleaseLabel(value: string | undefined): string | undefined {
|
|
459
|
+
const trimmed = String(value || '').trim();
|
|
460
|
+
if (!trimmed) {
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return trimmed
|
|
465
|
+
.replace(/^refs\/heads\//, '')
|
|
466
|
+
.replace(/^refs\/tags\//, '')
|
|
467
|
+
.replace(/^refs\/pull\//, 'pull-')
|
|
468
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '-')
|
|
469
|
+
.replace(/^-+|-+$/g, '') || undefined;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function deriveReleaseLabel(inputs: ResolvedInputs): string | undefined {
|
|
473
|
+
if (inputs.releaseLabel) {
|
|
474
|
+
return normalizeReleaseLabel(inputs.releaseLabel);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return (
|
|
478
|
+
normalizeReleaseLabel(inputs.githubRefName) ??
|
|
479
|
+
normalizeReleaseLabel(inputs.githubHeadRef) ??
|
|
480
|
+
normalizeReleaseLabel(inputs.githubRef)
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function createAssetProjectName(
|
|
485
|
+
inputs: ResolvedInputs,
|
|
486
|
+
releaseLabel?: string
|
|
487
|
+
): string {
|
|
488
|
+
if (!releaseLabel) {
|
|
489
|
+
return inputs.projectName;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return `${inputs.projectName} ${releaseLabel}`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
type CloudResourceMap = Record<string, string>;
|
|
496
|
+
|
|
497
|
+
type PostmanResourcesState = {
|
|
498
|
+
workspace?: {
|
|
499
|
+
id?: string;
|
|
500
|
+
};
|
|
501
|
+
cloudResources?: {
|
|
502
|
+
collections?: CloudResourceMap;
|
|
503
|
+
environments?: CloudResourceMap;
|
|
504
|
+
specs?: CloudResourceMap;
|
|
505
|
+
};
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
function readResourcesState(): PostmanResourcesState | null {
|
|
509
|
+
try {
|
|
510
|
+
return parse(readFileSync('.postman/resources.yaml', 'utf8')) as PostmanResourcesState;
|
|
511
|
+
} catch {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function getFirstCloudResourceId(map: CloudResourceMap | undefined): string | undefined {
|
|
517
|
+
if (!map) {
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
520
|
+
return Object.values(map)[0];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function findCloudResourceId(
|
|
524
|
+
map: CloudResourceMap | undefined,
|
|
525
|
+
matcher: (path: string) => boolean
|
|
526
|
+
): string | undefined {
|
|
527
|
+
if (!map) {
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const match = Object.entries(map).find(([filePath]) => matcher(filePath));
|
|
532
|
+
return match?.[1];
|
|
533
|
+
}
|
|
534
|
+
|
|
459
535
|
const SPEC_SUMMARY_MAX_LEN = 200;
|
|
460
536
|
const SPEC_HTTP_METHODS = new Set([
|
|
461
537
|
'get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'
|
|
@@ -542,146 +618,19 @@ function validateSpecStructure(content: string): void {
|
|
|
542
618
|
}
|
|
543
619
|
}
|
|
544
620
|
|
|
545
|
-
function varName(projectName: string, baseName: string): string {
|
|
546
|
-
const slug = projectName.toUpperCase().replace(/[^A-Z0-9]/g, '_');
|
|
547
|
-
return `POSTMAN_${slug}_${baseName}`;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
async function readVariable(
|
|
551
|
-
github: RepoVariableClient | undefined,
|
|
552
|
-
projectName: string,
|
|
553
|
-
baseName: string
|
|
554
|
-
): Promise<string | undefined> {
|
|
555
|
-
if (!github) {
|
|
556
|
-
return undefined;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
const namespaced = await github
|
|
560
|
-
.getRepositoryVariable(varName(projectName, baseName))
|
|
561
|
-
.catch(() => undefined);
|
|
562
|
-
if (namespaced) {
|
|
563
|
-
return namespaced;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const legacy = await github
|
|
567
|
-
.getRepositoryVariable(`POSTMAN_${baseName}`)
|
|
568
|
-
.catch(() => undefined);
|
|
569
|
-
return legacy || undefined;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
async function persistVariable(
|
|
573
|
-
github: RepoVariableClient | undefined,
|
|
574
|
-
name: string,
|
|
575
|
-
value: string,
|
|
576
|
-
actionCore: Pick<CoreLike, 'warning'>
|
|
577
|
-
): Promise<void> {
|
|
578
|
-
if (!github || !value) {
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
try {
|
|
583
|
-
await github.setRepositoryVariable(name, value);
|
|
584
|
-
} catch (err) {
|
|
585
|
-
actionCore.warning(
|
|
586
|
-
`Failed to persist ${name}: ${err instanceof Error ? err.message : String(err)}`
|
|
587
|
-
);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
async function writeVariable(
|
|
592
|
-
github: RepoVariableClient | undefined,
|
|
593
|
-
projectName: string,
|
|
594
|
-
baseName: string,
|
|
595
|
-
value: string,
|
|
596
|
-
actionCore: Pick<CoreLike, 'warning'>
|
|
597
|
-
): Promise<void> {
|
|
598
|
-
if (!github || !value) {
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
await persistVariable(github, varName(projectName, baseName), value, actionCore);
|
|
603
|
-
await persistVariable(github, `POSTMAN_${baseName}`, value, actionCore);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
async function persistBootstrapRepositoryVariables(
|
|
607
|
-
github: RepoVariableClient,
|
|
608
|
-
projectName: string,
|
|
609
|
-
outputs: PlannedOutputs,
|
|
610
|
-
systemEnvMap: Record<string, string>,
|
|
611
|
-
environments: string[],
|
|
612
|
-
lintSummary: BootstrapRepositoryVariables,
|
|
613
|
-
actionCore: Pick<CoreLike, 'warning'>
|
|
614
|
-
): Promise<void> {
|
|
615
|
-
await persistVariable(
|
|
616
|
-
github,
|
|
617
|
-
'LINT_WARNINGS',
|
|
618
|
-
String(lintSummary.lintWarnings),
|
|
619
|
-
actionCore
|
|
620
|
-
);
|
|
621
|
-
await persistVariable(
|
|
622
|
-
github,
|
|
623
|
-
'LINT_ERRORS',
|
|
624
|
-
String(lintSummary.lintErrors),
|
|
625
|
-
actionCore
|
|
626
|
-
);
|
|
627
|
-
await writeVariable(
|
|
628
|
-
github,
|
|
629
|
-
projectName,
|
|
630
|
-
'WORKSPACE_ID',
|
|
631
|
-
outputs['workspace-id'],
|
|
632
|
-
actionCore
|
|
633
|
-
);
|
|
634
|
-
await writeVariable(github, projectName, 'SPEC_UID', outputs['spec-id'], actionCore);
|
|
635
|
-
await writeVariable(
|
|
636
|
-
github,
|
|
637
|
-
projectName,
|
|
638
|
-
'BASELINE_COLLECTION_UID',
|
|
639
|
-
outputs['baseline-collection-id'],
|
|
640
|
-
actionCore
|
|
641
|
-
);
|
|
642
|
-
await writeVariable(
|
|
643
|
-
github,
|
|
644
|
-
projectName,
|
|
645
|
-
'SMOKE_COLLECTION_UID',
|
|
646
|
-
outputs['smoke-collection-id'],
|
|
647
|
-
actionCore
|
|
648
|
-
);
|
|
649
|
-
await writeVariable(
|
|
650
|
-
github,
|
|
651
|
-
projectName,
|
|
652
|
-
'CONTRACT_COLLECTION_UID',
|
|
653
|
-
outputs['contract-collection-id'],
|
|
654
|
-
actionCore
|
|
655
|
-
);
|
|
656
|
-
|
|
657
|
-
for (const envName of environments) {
|
|
658
|
-
const systemEnvId = systemEnvMap[envName];
|
|
659
|
-
if (!systemEnvId) {
|
|
660
|
-
continue;
|
|
661
|
-
}
|
|
662
|
-
await writeVariable(
|
|
663
|
-
github,
|
|
664
|
-
projectName,
|
|
665
|
-
`SYSTEM_ENV_${envName.toUpperCase()}`,
|
|
666
|
-
systemEnvId,
|
|
667
|
-
actionCore
|
|
668
|
-
);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
621
|
export async function runBootstrap(
|
|
673
622
|
inputs: ResolvedInputs,
|
|
674
623
|
dependencies: BootstrapExecutionDependencies
|
|
675
624
|
): Promise<PlannedOutputs> {
|
|
676
625
|
const outputs = createPlannedOutputs(inputs);
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
)
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
626
|
+
const requiresReleaseLabel =
|
|
627
|
+
inputs.collectionSyncMode === 'version' || inputs.specSyncMode === 'version';
|
|
628
|
+
const releaseLabel = requiresReleaseLabel ? deriveReleaseLabel(inputs) : undefined;
|
|
629
|
+
if (requiresReleaseLabel && !releaseLabel) {
|
|
630
|
+
throw new Error(
|
|
631
|
+
'Versioned spec or collection sync requires a release-label or derivable GitHub ref metadata'
|
|
632
|
+
);
|
|
633
|
+
}
|
|
685
634
|
const workspaceName = createWorkspaceName(inputs);
|
|
686
635
|
const aboutText = `Auto-provisioned by Postman CS open-alpha for ${inputs.projectName}`;
|
|
687
636
|
|
|
@@ -689,19 +638,17 @@ export async function runBootstrap(
|
|
|
689
638
|
await ensurePostmanCli(dependencies, inputs.postmanApiKey);
|
|
690
639
|
});
|
|
691
640
|
|
|
641
|
+
const resourcesState = readResourcesState();
|
|
692
642
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
repoWorkspaceId = await readVariable(
|
|
698
|
-
dependencies.github,
|
|
699
|
-
inputs.projectName,
|
|
700
|
-
'WORKSPACE_ID'
|
|
701
|
-
);
|
|
702
|
-
workspaceId = repoWorkspaceId;
|
|
643
|
+
let explicitWorkspaceId = inputs.workspaceId;
|
|
644
|
+
if (!explicitWorkspaceId && resourcesState?.workspace?.id) {
|
|
645
|
+
explicitWorkspaceId = resourcesState.workspace.id;
|
|
646
|
+
dependencies.core.info('Resolved workspace-id from .postman/resources.yaml');
|
|
703
647
|
}
|
|
704
648
|
|
|
649
|
+
const repoWorkspaceId = explicitWorkspaceId;
|
|
650
|
+
let workspaceId = explicitWorkspaceId;
|
|
651
|
+
|
|
705
652
|
let teamId = inputs.teamId || '';
|
|
706
653
|
if (!teamId) {
|
|
707
654
|
teamId = await dependencies.postman.getAutoDerivedTeamId() || '';
|
|
@@ -738,11 +685,66 @@ export async function runBootstrap(
|
|
|
738
685
|
dependencies.core.info(`Using existing workspace: ${workspaceId}`);
|
|
739
686
|
}
|
|
740
687
|
|
|
688
|
+
// Parse workspace-team-id from already-resolved inputs
|
|
689
|
+
let workspaceTeamId: number | undefined;
|
|
690
|
+
if (inputs.workspaceTeamId) {
|
|
691
|
+
workspaceTeamId = parseInt(inputs.workspaceTeamId, 10);
|
|
692
|
+
if (Number.isNaN(workspaceTeamId)) {
|
|
693
|
+
throw new Error(`workspace-team-id must be a numeric sub-team ID, got: ${inputs.workspaceTeamId}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Org-mode detection: only check if we need to create a workspace (not reuse existing)
|
|
698
|
+
if (!workspaceId && !workspaceTeamId) {
|
|
699
|
+
try {
|
|
700
|
+
const teams = await dependencies.postman.getTeams();
|
|
701
|
+
if (teams.length > 1 && teams.every(t => t.organizationId == null)) {
|
|
702
|
+
dependencies.core.warning(
|
|
703
|
+
'GET /teams returned multiple teams but none include organizationId. ' +
|
|
704
|
+
'Org-mode detection may be degraded due to an upstream API change. ' +
|
|
705
|
+
'If workspace creation fails, set workspace-team-id explicitly.'
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
const orgIds = new Set(teams.filter(t => t.organizationId != null).map(t => t.organizationId));
|
|
709
|
+
const meTeamId = parseInt(teamId, 10);
|
|
710
|
+
const isOrgMode = teams.length > 1
|
|
711
|
+
&& orgIds.size === 1
|
|
712
|
+
&& orgIds.has(meTeamId);
|
|
713
|
+
|
|
714
|
+
if (isOrgMode) {
|
|
715
|
+
const teamList = teams
|
|
716
|
+
.map(t => ` ${t.id} ${t.name}`)
|
|
717
|
+
.join('\n');
|
|
718
|
+
throw new Error(
|
|
719
|
+
`Org-mode account detected. Workspace creation requires a specific sub-team ID.\n\n` +
|
|
720
|
+
`Available sub-teams:\n${teamList}\n\n` +
|
|
721
|
+
`To fix this, set the workspace-team-id input in your workflow:\n` +
|
|
722
|
+
` workspace-team-id: '<id>'\n\n` +
|
|
723
|
+
`Or for reuse across runs, create a repository variable and reference it:\n` +
|
|
724
|
+
` workspace-team-id: \${{ vars.POSTMAN_WORKSPACE_TEAM_ID }}\n\n` +
|
|
725
|
+
`For CLI usage, pass --workspace-team-id <id> or export POSTMAN_WORKSPACE_TEAM_ID=<id>.`
|
|
726
|
+
);
|
|
727
|
+
} else if (teams.length > 1) {
|
|
728
|
+
dependencies.core.warning(
|
|
729
|
+
`API key has access to ${teams.length} teams but org-mode could not be confirmed. ` +
|
|
730
|
+
`Proceeding without teamId. If workspace creation fails, set workspace-team-id explicitly.`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
} catch (err) {
|
|
734
|
+
if (err instanceof Error && err.message.includes('Org-mode account detected')) {
|
|
735
|
+
throw err;
|
|
736
|
+
}
|
|
737
|
+
dependencies.core.warning(
|
|
738
|
+
`Could not check for org-mode sub-teams: ${err instanceof Error ? err.message : String(err)}`
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
741
743
|
if (!workspaceId) {
|
|
742
744
|
const workspace = await runGroup(
|
|
743
745
|
dependencies.core,
|
|
744
746
|
'Create Postman Workspace',
|
|
745
|
-
async () => dependencies.postman.createWorkspace(workspaceName, aboutText)
|
|
747
|
+
async () => dependencies.postman.createWorkspace(workspaceName, aboutText, workspaceTeamId)
|
|
746
748
|
);
|
|
747
749
|
workspaceId = workspace.id;
|
|
748
750
|
}
|
|
@@ -750,14 +752,6 @@ export async function runBootstrap(
|
|
|
750
752
|
outputs['workspace-id'] = workspaceId || '';
|
|
751
753
|
outputs['workspace-url'] = `https://go.postman.co/workspace/${workspaceId}`;
|
|
752
754
|
outputs['workspace-name'] = workspaceName;
|
|
753
|
-
await writeVariable(
|
|
754
|
-
dependencies.github,
|
|
755
|
-
inputs.projectName,
|
|
756
|
-
'WORKSPACE_ID',
|
|
757
|
-
outputs['workspace-id'],
|
|
758
|
-
dependencies.core
|
|
759
|
-
);
|
|
760
|
-
|
|
761
755
|
|
|
762
756
|
if (inputs.domain && dependencies.internalIntegration) {
|
|
763
757
|
await runGroup(
|
|
@@ -818,37 +812,49 @@ export async function runBootstrap(
|
|
|
818
812
|
);
|
|
819
813
|
}
|
|
820
814
|
|
|
821
|
-
|
|
822
815
|
let specId = inputs.specId;
|
|
823
|
-
if (!specId
|
|
824
|
-
specId =
|
|
816
|
+
if (!specId) {
|
|
817
|
+
specId = getFirstCloudResourceId(resourcesState?.cloudResources?.specs);
|
|
818
|
+
if (specId) {
|
|
819
|
+
dependencies.core.info('Resolved spec-id from .postman/resources.yaml');
|
|
820
|
+
}
|
|
825
821
|
}
|
|
826
822
|
|
|
827
|
-
let baselineCollectionId =
|
|
828
|
-
|
|
829
|
-
let
|
|
823
|
+
let baselineCollectionId =
|
|
824
|
+
inputs.collectionSyncMode === 'refresh' ? undefined : inputs.baselineCollectionId;
|
|
825
|
+
let smokeCollectionId =
|
|
826
|
+
inputs.collectionSyncMode === 'refresh' ? undefined : inputs.smokeCollectionId;
|
|
827
|
+
let contractCollectionId =
|
|
828
|
+
inputs.collectionSyncMode === 'refresh' ? undefined : inputs.contractCollectionId;
|
|
830
829
|
|
|
831
|
-
if (
|
|
830
|
+
if (inputs.collectionSyncMode !== 'refresh') {
|
|
831
|
+
const cloudCollections = resourcesState?.cloudResources?.collections;
|
|
832
832
|
if (!baselineCollectionId) {
|
|
833
|
-
baselineCollectionId =
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
'BASELINE_COLLECTION_UID'
|
|
833
|
+
baselineCollectionId = findCloudResourceId(
|
|
834
|
+
cloudCollections,
|
|
835
|
+
(filePath) => filePath.includes('[Baseline]')
|
|
837
836
|
);
|
|
837
|
+
if (baselineCollectionId) {
|
|
838
|
+
dependencies.core.info('Resolved baseline-collection-id from .postman/resources.yaml');
|
|
839
|
+
}
|
|
838
840
|
}
|
|
839
841
|
if (!smokeCollectionId) {
|
|
840
|
-
smokeCollectionId =
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
'SMOKE_COLLECTION_UID'
|
|
842
|
+
smokeCollectionId = findCloudResourceId(
|
|
843
|
+
cloudCollections,
|
|
844
|
+
(filePath) => filePath.includes('[Smoke]')
|
|
844
845
|
);
|
|
846
|
+
if (smokeCollectionId) {
|
|
847
|
+
dependencies.core.info('Resolved smoke-collection-id from .postman/resources.yaml');
|
|
848
|
+
}
|
|
845
849
|
}
|
|
846
850
|
if (!contractCollectionId) {
|
|
847
|
-
contractCollectionId =
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
'CONTRACT_COLLECTION_UID'
|
|
851
|
+
contractCollectionId = findCloudResourceId(
|
|
852
|
+
cloudCollections,
|
|
853
|
+
(filePath) => filePath.includes('[Contract]')
|
|
851
854
|
);
|
|
855
|
+
if (contractCollectionId) {
|
|
856
|
+
dependencies.core.info('Resolved contract-collection-id from .postman/resources.yaml');
|
|
857
|
+
}
|
|
852
858
|
}
|
|
853
859
|
}
|
|
854
860
|
|
|
@@ -874,7 +880,10 @@ export async function runBootstrap(
|
|
|
874
880
|
} else {
|
|
875
881
|
specId = await dependencies.postman.uploadSpec(
|
|
876
882
|
workspaceId || '',
|
|
877
|
-
|
|
883
|
+
createAssetProjectName(
|
|
884
|
+
inputs,
|
|
885
|
+
inputs.specSyncMode === 'version' ? releaseLabel : undefined
|
|
886
|
+
),
|
|
878
887
|
document
|
|
879
888
|
);
|
|
880
889
|
}
|
|
@@ -884,13 +893,6 @@ export async function runBootstrap(
|
|
|
884
893
|
);
|
|
885
894
|
|
|
886
895
|
void specContent;
|
|
887
|
-
await writeVariable(
|
|
888
|
-
dependencies.github,
|
|
889
|
-
inputs.projectName,
|
|
890
|
-
'SPEC_UID',
|
|
891
|
-
outputs['spec-id'],
|
|
892
|
-
dependencies.core
|
|
893
|
-
);
|
|
894
896
|
|
|
895
897
|
const lintSummary = await runGroup(
|
|
896
898
|
dependencies.core,
|
|
@@ -936,14 +938,23 @@ export async function runBootstrap(
|
|
|
936
938
|
dependencies.core,
|
|
937
939
|
'Generate Collections from Spec',
|
|
938
940
|
async () => {
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
941
|
+
const shouldReuseCollections = inputs.collectionSyncMode !== 'refresh';
|
|
942
|
+
const assetProjectName =
|
|
943
|
+
inputs.collectionSyncMode === 'version'
|
|
944
|
+
? createAssetProjectName(inputs, releaseLabel)
|
|
945
|
+
: inputs.projectName;
|
|
946
|
+
|
|
947
|
+
outputs['baseline-collection-id'] =
|
|
948
|
+
shouldReuseCollections ? baselineCollectionId || '' : '';
|
|
949
|
+
outputs['smoke-collection-id'] =
|
|
950
|
+
shouldReuseCollections ? smokeCollectionId || '' : '';
|
|
951
|
+
outputs['contract-collection-id'] =
|
|
952
|
+
shouldReuseCollections ? contractCollectionId || '' : '';
|
|
942
953
|
|
|
943
954
|
if (!outputs['baseline-collection-id']) {
|
|
944
955
|
outputs['baseline-collection-id'] = await dependencies.postman.generateCollection(
|
|
945
956
|
outputs['spec-id'],
|
|
946
|
-
|
|
957
|
+
assetProjectName,
|
|
947
958
|
'[Baseline]'
|
|
948
959
|
);
|
|
949
960
|
} else {
|
|
@@ -951,18 +962,10 @@ export async function runBootstrap(
|
|
|
951
962
|
`Using existing baseline collection: ${outputs['baseline-collection-id']}`
|
|
952
963
|
);
|
|
953
964
|
}
|
|
954
|
-
await writeVariable(
|
|
955
|
-
dependencies.github,
|
|
956
|
-
inputs.projectName,
|
|
957
|
-
'BASELINE_COLLECTION_UID',
|
|
958
|
-
outputs['baseline-collection-id'],
|
|
959
|
-
dependencies.core
|
|
960
|
-
);
|
|
961
|
-
|
|
962
965
|
if (!outputs['smoke-collection-id']) {
|
|
963
966
|
outputs['smoke-collection-id'] = await dependencies.postman.generateCollection(
|
|
964
967
|
outputs['spec-id'],
|
|
965
|
-
|
|
968
|
+
assetProjectName,
|
|
966
969
|
'[Smoke]'
|
|
967
970
|
);
|
|
968
971
|
} else {
|
|
@@ -970,18 +973,10 @@ export async function runBootstrap(
|
|
|
970
973
|
`Using existing smoke collection: ${outputs['smoke-collection-id']}`
|
|
971
974
|
);
|
|
972
975
|
}
|
|
973
|
-
await writeVariable(
|
|
974
|
-
dependencies.github,
|
|
975
|
-
inputs.projectName,
|
|
976
|
-
'SMOKE_COLLECTION_UID',
|
|
977
|
-
outputs['smoke-collection-id'],
|
|
978
|
-
dependencies.core
|
|
979
|
-
);
|
|
980
|
-
|
|
981
976
|
if (!outputs['contract-collection-id']) {
|
|
982
977
|
outputs['contract-collection-id'] = await dependencies.postman.generateCollection(
|
|
983
978
|
outputs['spec-id'],
|
|
984
|
-
|
|
979
|
+
assetProjectName,
|
|
985
980
|
'[Contract]'
|
|
986
981
|
);
|
|
987
982
|
} else {
|
|
@@ -989,13 +984,6 @@ export async function runBootstrap(
|
|
|
989
984
|
`Using existing contract collection: ${outputs['contract-collection-id']}`
|
|
990
985
|
);
|
|
991
986
|
}
|
|
992
|
-
await writeVariable(
|
|
993
|
-
dependencies.github,
|
|
994
|
-
inputs.projectName,
|
|
995
|
-
'CONTRACT_COLLECTION_UID',
|
|
996
|
-
outputs['contract-collection-id'],
|
|
997
|
-
dependencies.core
|
|
998
|
-
);
|
|
999
987
|
}
|
|
1000
988
|
);
|
|
1001
989
|
|
|
@@ -1037,28 +1025,6 @@ export async function runBootstrap(
|
|
|
1037
1025
|
}
|
|
1038
1026
|
);
|
|
1039
1027
|
|
|
1040
|
-
if (dependencies.github) {
|
|
1041
|
-
const github = dependencies.github;
|
|
1042
|
-
await runGroup(
|
|
1043
|
-
dependencies.core,
|
|
1044
|
-
'Store Postman UIDs as Repo Variables',
|
|
1045
|
-
async () => {
|
|
1046
|
-
await persistBootstrapRepositoryVariables(
|
|
1047
|
-
github,
|
|
1048
|
-
inputs.projectName,
|
|
1049
|
-
outputs,
|
|
1050
|
-
systemEnvMap,
|
|
1051
|
-
environments,
|
|
1052
|
-
{
|
|
1053
|
-
lintErrors: lintSummary.errors,
|
|
1054
|
-
lintWarnings: lintSummary.warnings
|
|
1055
|
-
},
|
|
1056
|
-
dependencies.core
|
|
1057
|
-
);
|
|
1058
|
-
}
|
|
1059
|
-
);
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
1028
|
for (const [name, value] of Object.entries(outputs)) {
|
|
1063
1029
|
dependencies.core.setOutput(name, value);
|
|
1064
1030
|
}
|
|
@@ -1079,9 +1045,6 @@ export async function runAction(
|
|
|
1079
1045
|
specFetcher: fetch
|
|
1080
1046
|
});
|
|
1081
1047
|
|
|
1082
|
-
if (!dependencies.github) {
|
|
1083
|
-
actionCore.info('GitHub repository variable persistence disabled for this run');
|
|
1084
|
-
}
|
|
1085
1048
|
if (inputs.domain && !dependencies.internalIntegration) {
|
|
1086
1049
|
actionCore.warning(
|
|
1087
1050
|
'Skipping governance assignment because postman-access-token is not configured'
|
|
@@ -1097,25 +1060,12 @@ export function createBootstrapDependencies(
|
|
|
1097
1060
|
): BootstrapExecutionDependencies {
|
|
1098
1061
|
const secretMasker = createSecretMasker([
|
|
1099
1062
|
inputs.postmanApiKey,
|
|
1100
|
-
inputs.postmanAccessToken
|
|
1101
|
-
inputs.githubToken,
|
|
1102
|
-
inputs.ghFallbackToken
|
|
1063
|
+
inputs.postmanAccessToken
|
|
1103
1064
|
]);
|
|
1104
1065
|
const postman = new PostmanAssetsClient({
|
|
1105
1066
|
apiKey: inputs.postmanApiKey,
|
|
1106
1067
|
secretMasker
|
|
1107
1068
|
});
|
|
1108
|
-
const repository = extractRepositorySlug(inputs.repoUrl);
|
|
1109
|
-
const github =
|
|
1110
|
-
inputs.githubToken && inputs.repoUrl && repository
|
|
1111
|
-
? new GitHubApiClient({
|
|
1112
|
-
authMode: inputs.githubAuthMode as GitHubApiClientAuthMode,
|
|
1113
|
-
fallbackToken: inputs.ghFallbackToken,
|
|
1114
|
-
repository,
|
|
1115
|
-
secretMasker,
|
|
1116
|
-
token: inputs.githubToken
|
|
1117
|
-
})
|
|
1118
|
-
: undefined;
|
|
1119
1069
|
const internalIntegration =
|
|
1120
1070
|
inputs.postmanAccessToken
|
|
1121
1071
|
? createInternalIntegrationAdapter({
|
|
@@ -1129,7 +1079,6 @@ export function createBootstrapDependencies(
|
|
|
1129
1079
|
return {
|
|
1130
1080
|
core: factories.core,
|
|
1131
1081
|
exec: factories.exec,
|
|
1132
|
-
github,
|
|
1133
1082
|
io: factories.io,
|
|
1134
1083
|
internalIntegration,
|
|
1135
1084
|
postman,
|
|
@@ -1137,25 +1086,6 @@ export function createBootstrapDependencies(
|
|
|
1137
1086
|
};
|
|
1138
1087
|
}
|
|
1139
1088
|
|
|
1140
|
-
export function extractRepositorySlug(repoUrl: string | undefined): string | undefined {
|
|
1141
|
-
const normalized = normalizeInputValue(repoUrl);
|
|
1142
|
-
if (!normalized) {
|
|
1143
|
-
return undefined;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
try {
|
|
1147
|
-
const parsed = new URL(normalized);
|
|
1148
|
-
const pathname = parsed.pathname.replace(/^\/+|\/+$/g, '').replace(/\.git$/, '');
|
|
1149
|
-
const segments = pathname.split('/').filter(Boolean);
|
|
1150
|
-
if (segments.length >= 2) {
|
|
1151
|
-
return `${segments[0]}/${segments[1]}`;
|
|
1152
|
-
}
|
|
1153
|
-
return undefined;
|
|
1154
|
-
} catch {
|
|
1155
|
-
return undefined;
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
1089
|
const currentModulePath = typeof __filename === 'string' ? __filename : '';
|
|
1160
1090
|
const entrypoint = process.argv[1];
|
|
1161
1091
|
|