@postman-cse/onboarding-repo-sync 0.11.0 → 0.12.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 CHANGED
@@ -207,8 +207,29 @@ Collections are exported in the Postman Collection v3 format, producing a multi-
207
207
 
208
208
  The generated CI workflow reads `.postman/resources.yaml` directly to resolve the smoke/contract collection IDs and environment ID for Postman CLI runs. It does not depend on repository variables for those asset mappings.
209
209
 
210
+ To match the app scaffold more closely, repo-sync also ensures these directories exist under `postman/`:
211
+
212
+ - `collections`
213
+ - `environments`
214
+ - `flows`
215
+ - `globals`
216
+ - `mocks`
217
+ - `specs`
218
+
219
+ It also writes `postman/globals/workspace.globals.yaml` when missing.
220
+
210
221
  Folder and request **names are truncated to 120 characters** per path segment when writing files (with an ellipsis). That avoids `ENAMETOOLONG` when Postman item names are very long (for example, copied from long OpenAPI operation summaries).
211
222
 
223
+ ### Local spec metadata
224
+
225
+ Repo-sync now scans the repository for local OpenAPI files and records them in `.postman/resources.yaml` under `localResources.specs`.
226
+
227
+ - If `spec-path` is provided, it is used as the preferred local spec for `cloudResources.specs` and `.postman/workflows.yaml`.
228
+ - If `spec-path` is omitted and exactly one local OpenAPI file is found, that file is used automatically.
229
+ - If the local spec target is ambiguous or missing, repo-sync skips the spec cloud map and `workflows.yaml` rather than emitting incorrect relationships.
230
+
231
+ When a local spec file and exported collections are both available, repo-sync writes `.postman/workflows.yaml` with `syncSpecToCollection` entries so the spec↔collection relationship metadata matches the app more closely.
232
+
212
233
  ### Lifecycle and versioning
213
234
 
214
235
  `collection-sync-mode` controls collection lifecycle behavior:
@@ -234,6 +255,7 @@ Folder and request **names are truncated to 120 characters** per path segment wh
234
255
  | `collection-sync-mode` | `refresh` | Collection lifecycle mode: `refresh`, `reuse`, or `version`. |
235
256
  | `spec-sync-mode` | `update` | Spec lifecycle mode: `update` or `version`. |
236
257
  | `release-label` | | Optional release label for versioned naming. Falls back to `github-ref-name` when omitted. |
258
+ | `spec-path` | | Optional repo-root-relative path to the local spec file for `resources.yaml` and `workflows.yaml` metadata. |
237
259
  | `environments-json` | `["prod"]` | Environment slugs to create or update. |
238
260
  | `repo-url` | | Explicit repository URL (GitHub or GitLab). Defaults to `https://github.com/$GITHUB_REPOSITORY` on GitHub Actions, or `$CI_PROJECT_URL` on GitLab CI. |
239
261
  | `integration-backend` | `bifrost` | Public open-alpha starts with Bifrost only. |
package/action.yml CHANGED
@@ -134,6 +134,9 @@ inputs:
134
134
  spec-id:
135
135
  description: Spec UID from bootstrap, persisted into .postman/resources.yaml cloudResources.
136
136
  required: false
137
+ spec-path:
138
+ description: Optional repo-root-relative path to the local spec file for resources/workflows metadata.
139
+ required: false
137
140
  outputs:
138
141
  integration-backend:
139
142
  description: Resolved integration backend for the open-alpha run.
package/dist/cli.cjs CHANGED
@@ -25394,6 +25394,7 @@ function resolveInputs(env = process.env) {
25394
25394
  smokeCollectionId: getInput("smoke-collection-id", env),
25395
25395
  contractCollectionId: getInput("contract-collection-id", env),
25396
25396
  specId: getInput("spec-id", env),
25397
+ specPath: getInput("spec-path", env),
25397
25398
  collectionSyncMode: normalizeCollectionSyncMode(getInput("collection-sync-mode", env) || "refresh"),
25398
25399
  specSyncMode: normalizeSpecSyncMode(getInput("spec-sync-mode", env) || "update"),
25399
25400
  releaseLabel: normalizeReleaseLabel(getInput("release-label", env)) || void 0,
@@ -25469,6 +25470,85 @@ function getEnvironmentUidsFromResources(resourcesState) {
25469
25470
  }).filter((entry) => Boolean(entry))
25470
25471
  );
25471
25472
  }
25473
+ function normalizeToPosix(filePath) {
25474
+ return filePath.split(path2.sep).join("/");
25475
+ }
25476
+ function isOpenApiSpecFile(filePath) {
25477
+ if (!(filePath.endsWith(".json") || filePath.endsWith(".yaml") || filePath.endsWith(".yml"))) {
25478
+ return false;
25479
+ }
25480
+ try {
25481
+ const raw = (0, import_node_fs.readFileSync)(filePath, "utf8");
25482
+ const parsed = filePath.endsWith(".json") ? JSON.parse(raw) : load(raw);
25483
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
25484
+ return false;
25485
+ }
25486
+ const doc = parsed;
25487
+ if (doc.swagger === "2.0" && doc.paths && typeof doc.paths === "object") {
25488
+ return true;
25489
+ }
25490
+ if (typeof doc.openapi === "string" && doc.paths && typeof doc.paths === "object") {
25491
+ return true;
25492
+ }
25493
+ } catch {
25494
+ return false;
25495
+ }
25496
+ return false;
25497
+ }
25498
+ function scanLocalSpecReferences(baseDir = ".") {
25499
+ const ignoredDirs = /* @__PURE__ */ new Set([
25500
+ ".git",
25501
+ ".omc",
25502
+ ".omx",
25503
+ ".llm-plans",
25504
+ "node_modules",
25505
+ "dist"
25506
+ ]);
25507
+ const found = /* @__PURE__ */ new Set();
25508
+ const refs = [];
25509
+ const visit = (currentDir) => {
25510
+ for (const entry of (0, import_node_fs.readdirSync)(currentDir, { withFileTypes: true })) {
25511
+ if (ignoredDirs.has(entry.name)) {
25512
+ continue;
25513
+ }
25514
+ const fullPath = path2.join(currentDir, entry.name);
25515
+ if (entry.isDirectory()) {
25516
+ visit(fullPath);
25517
+ continue;
25518
+ }
25519
+ if (!entry.isFile() || !isOpenApiSpecFile(fullPath)) {
25520
+ continue;
25521
+ }
25522
+ const repoRelativePath = normalizeToPosix(path2.relative(baseDir, fullPath));
25523
+ if (found.has(repoRelativePath)) {
25524
+ continue;
25525
+ }
25526
+ found.add(repoRelativePath);
25527
+ refs.push({
25528
+ repoRelativePath,
25529
+ configRelativePath: normalizeToPosix(path2.join("..", repoRelativePath))
25530
+ });
25531
+ }
25532
+ };
25533
+ visit(baseDir);
25534
+ return refs.sort((left, right) => left.repoRelativePath.localeCompare(right.repoRelativePath));
25535
+ }
25536
+ function resolveMappedSpecReference(explicitSpecPath, discoveredSpecs) {
25537
+ const normalizedExplicitPath = normalizeToPosix(explicitSpecPath.trim());
25538
+ if (normalizedExplicitPath) {
25539
+ const explicitFullPath = path2.resolve(normalizedExplicitPath);
25540
+ if ((0, import_node_fs.existsSync)(explicitFullPath) && (0, import_node_fs.statSync)(explicitFullPath).isFile()) {
25541
+ return {
25542
+ repoRelativePath: normalizedExplicitPath,
25543
+ configRelativePath: normalizeToPosix(path2.join("..", normalizedExplicitPath))
25544
+ };
25545
+ }
25546
+ }
25547
+ if (discoveredSpecs.length === 1) {
25548
+ return discoveredSpecs[0];
25549
+ }
25550
+ return void 0;
25551
+ }
25472
25552
  function createOutputs(inputs) {
25473
25553
  return {
25474
25554
  "integration-backend": inputs.integrationBackend,
@@ -25688,7 +25768,7 @@ function writeJsonFile(path4, content, normalize2 = false) {
25688
25768
  const data = normalize2 ? stripVolatileFields(content) : content;
25689
25769
  (0, import_node_fs.writeFileSync)(path4, JSON.stringify(data, null, 2));
25690
25770
  }
25691
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, specId) {
25771
+ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId) {
25692
25772
  const manifest = {
25693
25773
  workspace: { id: workspaceId }
25694
25774
  };
@@ -25709,9 +25789,11 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
25709
25789
  cloudResources.environments[`../${artifactDir}/environments/${envName}.postman_environment.json`] = envUid;
25710
25790
  }
25711
25791
  }
25712
- localResources.specs = ["../index.yaml"];
25713
- if (specId) {
25714
- cloudResources.specs = { "../index.yaml": specId };
25792
+ if (localSpecRefs.length > 0) {
25793
+ localResources.specs = localSpecRefs;
25794
+ }
25795
+ if (mappedSpecRef && specId) {
25796
+ cloudResources.specs = { [mappedSpecRef]: specId };
25715
25797
  }
25716
25798
  if (Object.keys(localResources).length > 0) {
25717
25799
  manifest.localResources = localResources;
@@ -25725,6 +25807,23 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
25725
25807
  sortKeys: false
25726
25808
  });
25727
25809
  }
25810
+ function buildSpecCollectionWorkflowManifest(specRef, collectionRefs) {
25811
+ return dump(
25812
+ {
25813
+ workflows: {
25814
+ syncSpecToCollection: collectionRefs.map((collectionRef) => ({
25815
+ spec: specRef,
25816
+ collection: collectionRef
25817
+ }))
25818
+ }
25819
+ },
25820
+ {
25821
+ lineWidth: -1,
25822
+ noRefs: true,
25823
+ sortKeys: false
25824
+ }
25825
+ );
25826
+ }
25728
25827
  function assertPathWithinCwd(targetPath, fieldName) {
25729
25828
  const base = path2.resolve(".");
25730
25829
  const resolved = path2.resolve(base, targetPath);
@@ -25743,15 +25842,27 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName)
25743
25842
  }
25744
25843
  const collectionsDir = `${inputs.artifactDir}/collections`;
25745
25844
  const environmentsDir = `${inputs.artifactDir}/environments`;
25845
+ const flowsDir = `${inputs.artifactDir}/flows`;
25846
+ const globalsDir = `${inputs.artifactDir}/globals`;
25746
25847
  const mocksDir = `${inputs.artifactDir}/mocks`;
25848
+ const specsDir = `${inputs.artifactDir}/specs`;
25747
25849
  ensureDir(collectionsDir);
25748
25850
  ensureDir(environmentsDir);
25851
+ ensureDir(flowsDir);
25852
+ ensureDir(globalsDir);
25749
25853
  ensureDir(mocksDir);
25854
+ ensureDir(specsDir);
25750
25855
  ensureDir(".postman");
25856
+ const globalsFilePath = `${globalsDir}/workspace.globals.yaml`;
25857
+ if (!(0, import_node_fs.existsSync)(globalsFilePath)) {
25858
+ (0, import_node_fs.writeFileSync)(globalsFilePath, "name: Globals\nvalues: []\n");
25859
+ }
25751
25860
  if (inputs.generateCiWorkflow) {
25752
25861
  ensureDir(".github/workflows");
25753
25862
  }
25754
25863
  const manifestCollections = {};
25864
+ const discoveredSpecs = scanLocalSpecReferences();
25865
+ const mappedSpec = resolveMappedSpecReference(inputs.specPath, discoveredSpecs);
25755
25866
  if (inputs.baselineCollectionId) {
25756
25867
  const col = stripVolatileFields(
25757
25868
  await dependencies.postman.getCollection(inputs.baselineCollectionId)
@@ -25788,8 +25899,19 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName)
25788
25899
  manifestCollections,
25789
25900
  envUids,
25790
25901
  inputs.artifactDir,
25902
+ discoveredSpecs.map((spec) => spec.configRelativePath),
25903
+ mappedSpec?.configRelativePath,
25791
25904
  inputs.specId || void 0
25792
25905
  ));
25906
+ if (mappedSpec && Object.keys(manifestCollections).length > 0) {
25907
+ (0, import_node_fs.writeFileSync)(
25908
+ ".postman/workflows.yaml",
25909
+ buildSpecCollectionWorkflowManifest(
25910
+ mappedSpec.configRelativePath,
25911
+ Object.keys(manifestCollections)
25912
+ )
25913
+ );
25914
+ }
25793
25915
  }
25794
25916
  function renderCiWorkflow(inputs) {
25795
25917
  if (inputs.ciWorkflowBase64) {
@@ -26315,6 +26437,7 @@ function parseCliArgs(argv, env = process.env) {
26315
26437
  "ssl-client-passphrase",
26316
26438
  "ssl-extra-ca-certs",
26317
26439
  "spec-id",
26440
+ "spec-path",
26318
26441
  "team-id"
26319
26442
  ];
26320
26443
  const inputEnv = { ...env };
package/dist/index.cjs CHANGED
@@ -25389,6 +25389,7 @@ function resolveInputs(env = process.env) {
25389
25389
  smokeCollectionId: getInput("smoke-collection-id", env),
25390
25390
  contractCollectionId: getInput("contract-collection-id", env),
25391
25391
  specId: getInput("spec-id", env),
25392
+ specPath: getInput("spec-path", env),
25392
25393
  collectionSyncMode: normalizeCollectionSyncMode(getInput("collection-sync-mode", env) || "refresh"),
25393
25394
  specSyncMode: normalizeSpecSyncMode(getInput("spec-sync-mode", env) || "update"),
25394
25395
  releaseLabel: normalizeReleaseLabel(getInput("release-label", env)) || void 0,
@@ -25464,6 +25465,85 @@ function getEnvironmentUidsFromResources(resourcesState) {
25464
25465
  }).filter((entry) => Boolean(entry))
25465
25466
  );
25466
25467
  }
25468
+ function normalizeToPosix(filePath) {
25469
+ return filePath.split(path2.sep).join("/");
25470
+ }
25471
+ function isOpenApiSpecFile(filePath) {
25472
+ if (!(filePath.endsWith(".json") || filePath.endsWith(".yaml") || filePath.endsWith(".yml"))) {
25473
+ return false;
25474
+ }
25475
+ try {
25476
+ const raw = (0, import_node_fs.readFileSync)(filePath, "utf8");
25477
+ const parsed = filePath.endsWith(".json") ? JSON.parse(raw) : load(raw);
25478
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
25479
+ return false;
25480
+ }
25481
+ const doc = parsed;
25482
+ if (doc.swagger === "2.0" && doc.paths && typeof doc.paths === "object") {
25483
+ return true;
25484
+ }
25485
+ if (typeof doc.openapi === "string" && doc.paths && typeof doc.paths === "object") {
25486
+ return true;
25487
+ }
25488
+ } catch {
25489
+ return false;
25490
+ }
25491
+ return false;
25492
+ }
25493
+ function scanLocalSpecReferences(baseDir = ".") {
25494
+ const ignoredDirs = /* @__PURE__ */ new Set([
25495
+ ".git",
25496
+ ".omc",
25497
+ ".omx",
25498
+ ".llm-plans",
25499
+ "node_modules",
25500
+ "dist"
25501
+ ]);
25502
+ const found = /* @__PURE__ */ new Set();
25503
+ const refs = [];
25504
+ const visit = (currentDir) => {
25505
+ for (const entry of (0, import_node_fs.readdirSync)(currentDir, { withFileTypes: true })) {
25506
+ if (ignoredDirs.has(entry.name)) {
25507
+ continue;
25508
+ }
25509
+ const fullPath = path2.join(currentDir, entry.name);
25510
+ if (entry.isDirectory()) {
25511
+ visit(fullPath);
25512
+ continue;
25513
+ }
25514
+ if (!entry.isFile() || !isOpenApiSpecFile(fullPath)) {
25515
+ continue;
25516
+ }
25517
+ const repoRelativePath = normalizeToPosix(path2.relative(baseDir, fullPath));
25518
+ if (found.has(repoRelativePath)) {
25519
+ continue;
25520
+ }
25521
+ found.add(repoRelativePath);
25522
+ refs.push({
25523
+ repoRelativePath,
25524
+ configRelativePath: normalizeToPosix(path2.join("..", repoRelativePath))
25525
+ });
25526
+ }
25527
+ };
25528
+ visit(baseDir);
25529
+ return refs.sort((left, right) => left.repoRelativePath.localeCompare(right.repoRelativePath));
25530
+ }
25531
+ function resolveMappedSpecReference(explicitSpecPath, discoveredSpecs) {
25532
+ const normalizedExplicitPath = normalizeToPosix(explicitSpecPath.trim());
25533
+ if (normalizedExplicitPath) {
25534
+ const explicitFullPath = path2.resolve(normalizedExplicitPath);
25535
+ if ((0, import_node_fs.existsSync)(explicitFullPath) && (0, import_node_fs.statSync)(explicitFullPath).isFile()) {
25536
+ return {
25537
+ repoRelativePath: normalizedExplicitPath,
25538
+ configRelativePath: normalizeToPosix(path2.join("..", normalizedExplicitPath))
25539
+ };
25540
+ }
25541
+ }
25542
+ if (discoveredSpecs.length === 1) {
25543
+ return discoveredSpecs[0];
25544
+ }
25545
+ return void 0;
25546
+ }
25467
25547
  function createOutputs(inputs) {
25468
25548
  return {
25469
25549
  "integration-backend": inputs.integrationBackend,
@@ -25683,7 +25763,7 @@ function writeJsonFile(path3, content, normalize2 = false) {
25683
25763
  const data = normalize2 ? stripVolatileFields(content) : content;
25684
25764
  (0, import_node_fs.writeFileSync)(path3, JSON.stringify(data, null, 2));
25685
25765
  }
25686
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, specId) {
25766
+ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId) {
25687
25767
  const manifest = {
25688
25768
  workspace: { id: workspaceId }
25689
25769
  };
@@ -25704,9 +25784,11 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
25704
25784
  cloudResources.environments[`../${artifactDir}/environments/${envName}.postman_environment.json`] = envUid;
25705
25785
  }
25706
25786
  }
25707
- localResources.specs = ["../index.yaml"];
25708
- if (specId) {
25709
- cloudResources.specs = { "../index.yaml": specId };
25787
+ if (localSpecRefs.length > 0) {
25788
+ localResources.specs = localSpecRefs;
25789
+ }
25790
+ if (mappedSpecRef && specId) {
25791
+ cloudResources.specs = { [mappedSpecRef]: specId };
25710
25792
  }
25711
25793
  if (Object.keys(localResources).length > 0) {
25712
25794
  manifest.localResources = localResources;
@@ -25720,6 +25802,23 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
25720
25802
  sortKeys: false
25721
25803
  });
25722
25804
  }
25805
+ function buildSpecCollectionWorkflowManifest(specRef, collectionRefs) {
25806
+ return dump(
25807
+ {
25808
+ workflows: {
25809
+ syncSpecToCollection: collectionRefs.map((collectionRef) => ({
25810
+ spec: specRef,
25811
+ collection: collectionRef
25812
+ }))
25813
+ }
25814
+ },
25815
+ {
25816
+ lineWidth: -1,
25817
+ noRefs: true,
25818
+ sortKeys: false
25819
+ }
25820
+ );
25821
+ }
25723
25822
  function assertPathWithinCwd(targetPath, fieldName) {
25724
25823
  const base = path2.resolve(".");
25725
25824
  const resolved = path2.resolve(base, targetPath);
@@ -25738,15 +25837,27 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName)
25738
25837
  }
25739
25838
  const collectionsDir = `${inputs.artifactDir}/collections`;
25740
25839
  const environmentsDir = `${inputs.artifactDir}/environments`;
25840
+ const flowsDir = `${inputs.artifactDir}/flows`;
25841
+ const globalsDir = `${inputs.artifactDir}/globals`;
25741
25842
  const mocksDir = `${inputs.artifactDir}/mocks`;
25843
+ const specsDir = `${inputs.artifactDir}/specs`;
25742
25844
  ensureDir(collectionsDir);
25743
25845
  ensureDir(environmentsDir);
25846
+ ensureDir(flowsDir);
25847
+ ensureDir(globalsDir);
25744
25848
  ensureDir(mocksDir);
25849
+ ensureDir(specsDir);
25745
25850
  ensureDir(".postman");
25851
+ const globalsFilePath = `${globalsDir}/workspace.globals.yaml`;
25852
+ if (!(0, import_node_fs.existsSync)(globalsFilePath)) {
25853
+ (0, import_node_fs.writeFileSync)(globalsFilePath, "name: Globals\nvalues: []\n");
25854
+ }
25746
25855
  if (inputs.generateCiWorkflow) {
25747
25856
  ensureDir(".github/workflows");
25748
25857
  }
25749
25858
  const manifestCollections = {};
25859
+ const discoveredSpecs = scanLocalSpecReferences();
25860
+ const mappedSpec = resolveMappedSpecReference(inputs.specPath, discoveredSpecs);
25750
25861
  if (inputs.baselineCollectionId) {
25751
25862
  const col = stripVolatileFields(
25752
25863
  await dependencies.postman.getCollection(inputs.baselineCollectionId)
@@ -25783,8 +25894,19 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName)
25783
25894
  manifestCollections,
25784
25895
  envUids,
25785
25896
  inputs.artifactDir,
25897
+ discoveredSpecs.map((spec) => spec.configRelativePath),
25898
+ mappedSpec?.configRelativePath,
25786
25899
  inputs.specId || void 0
25787
25900
  ));
25901
+ if (mappedSpec && Object.keys(manifestCollections).length > 0) {
25902
+ (0, import_node_fs.writeFileSync)(
25903
+ ".postman/workflows.yaml",
25904
+ buildSpecCollectionWorkflowManifest(
25905
+ mappedSpec.configRelativePath,
25906
+ Object.keys(manifestCollections)
25907
+ )
25908
+ );
25909
+ }
25788
25910
  }
25789
25911
  function renderCiWorkflow(inputs) {
25790
25912
  if (inputs.ciWorkflowBase64) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman-cse/onboarding-repo-sync",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Public open-alpha Postman repo sync GitHub Action.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",