@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/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 asStringMap(value: unknown, inputName: string): Record<string, string> {
189
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
190
- throw new Error(`${inputName} must be a JSON object`);
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 Object.fromEntries(
193
- Object.entries(value as Record<string, unknown>).map(([key, entry]) => [
194
- key,
195
- String(entry)
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
- githubToken: getInput('github-token', env),
265
- ghFallbackToken: getInput('gh-fallback-token', env),
266
- githubAuthMode:
267
- getInput('github-auth-mode', env) ??
268
- openAlphaActionContract.inputs['github-auth-mode'].default ??
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 environments = asStringArray(
678
- parseJsonValue(inputs.environmentsJson, ['prod'], 'environments-json'),
679
- 'environments-json'
680
- );
681
- const systemEnvMap = asStringMap(
682
- parseJsonValue(inputs.systemEnvMapJson, {}, 'system-env-map-json'),
683
- 'system-env-map-json'
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
- const explicitWorkspaceId = inputs.workspaceId;
694
- let repoWorkspaceId: string | undefined;
695
- let workspaceId = explicitWorkspaceId;
696
- if (!workspaceId && dependencies.github) {
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 && dependencies.github) {
824
- specId = await readVariable(dependencies.github, inputs.projectName, 'SPEC_UID');
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 = inputs.baselineCollectionId;
828
- let smokeCollectionId = inputs.smokeCollectionId;
829
- let contractCollectionId = inputs.contractCollectionId;
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 (dependencies.github) {
830
+ if (inputs.collectionSyncMode !== 'refresh') {
831
+ const cloudCollections = resourcesState?.cloudResources?.collections;
832
832
  if (!baselineCollectionId) {
833
- baselineCollectionId = await readVariable(
834
- dependencies.github,
835
- inputs.projectName,
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 = await readVariable(
841
- dependencies.github,
842
- inputs.projectName,
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 = await readVariable(
848
- dependencies.github,
849
- inputs.projectName,
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
- inputs.projectName,
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
- outputs['baseline-collection-id'] = baselineCollectionId || '';
940
- outputs['smoke-collection-id'] = smokeCollectionId || '';
941
- outputs['contract-collection-id'] = contractCollectionId || '';
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
- inputs.projectName,
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
- inputs.projectName,
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
- inputs.projectName,
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