@postman-cse/onboarding-insights 2.0.0 → 2.1.1

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/dist/cli.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  "use strict";
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
@@ -18685,9 +18686,10 @@ __export(cli_exports, {
18685
18686
  toDotenv: () => toDotenv
18686
18687
  });
18687
18688
  module.exports = __toCommonJS(cli_exports);
18688
- var import_node_fs = require("node:fs");
18689
+ var import_node_crypto2 = require("node:crypto");
18690
+ var import_node_fs2 = require("node:fs");
18689
18691
  var import_promises = require("node:fs/promises");
18690
- var import_node_path = __toESM(require("node:path"), 1);
18692
+ var import_node_path2 = __toESM(require("node:path"), 1);
18691
18693
 
18692
18694
  // src/lib/retry.ts
18693
18695
  function sleep(delayMs) {
@@ -18891,6 +18893,53 @@ var AccessTokenProvider = class {
18891
18893
  return token;
18892
18894
  }
18893
18895
  };
18896
+ async function describeMintFailure(mintError, apiKey, apiBaseUrl, fetchImpl) {
18897
+ const raw = mintError instanceof Error ? mintError.message : String(mintError);
18898
+ const rejected = /HTTP 40[13]|PMAK rejected/.test(raw);
18899
+ if (!rejected) {
18900
+ return raw;
18901
+ }
18902
+ try {
18903
+ const me = await fetchImpl(`${apiBaseUrl}/me`, { headers: { "x-api-key": apiKey } });
18904
+ if (me.ok) {
18905
+ const body = await me.json().catch(() => void 0);
18906
+ const user = body?.user;
18907
+ const looksPersonal = Boolean(user && (user.username || user.email));
18908
+ if (looksPersonal) {
18909
+ return "Personal API key detected, cannot mint a service-account access token. POST /service-account-tokens only accepts a SERVICE-ACCOUNT API key; this postman-api-key belongs to a user account" + (user?.teamId ? ` (team ${user.teamId})` : "") + ". Create a service account in Team Settings and use its PMAK, or mint the token elsewhere and pass postman-access-token.";
18910
+ }
18911
+ return "The postman-api-key authenticates (GET /me OK) but was rejected by POST /service-account-tokens" + (user?.teamId ? ` (team ${user.teamId})` : "") + ". The service account likely lacks permission to mint access tokens, or service accounts are restricted for this team. Check the service account role in Team Settings, or pass a pre-minted postman-access-token.";
18912
+ }
18913
+ return "The postman-api-key is invalid, disabled, or expired (rejected by both POST /service-account-tokens and GET /me). Generate a fresh service-account PMAK in Team Settings and update the secret.";
18914
+ } catch {
18915
+ return raw;
18916
+ }
18917
+ }
18918
+ async function mintAccessTokenIfNeeded(inputs, log, setSecret2, fetchImpl = fetch) {
18919
+ if (inputs.postmanAccessToken || !inputs.postmanApiKey) {
18920
+ return;
18921
+ }
18922
+ const apiBaseUrl = String(
18923
+ inputs.postmanApiBase || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl
18924
+ ).replace(/\/+$/, "");
18925
+ const provider = new AccessTokenProvider({
18926
+ apiKey: inputs.postmanApiKey,
18927
+ apiBaseUrl,
18928
+ fetchImpl,
18929
+ onToken: (token) => setSecret2?.(token)
18930
+ });
18931
+ try {
18932
+ inputs.postmanAccessToken = await provider.refresh();
18933
+ log.info(
18934
+ "postman: no postman-access-token configured - minted a short-lived service-account access token from the postman-api-key."
18935
+ );
18936
+ } catch (error2) {
18937
+ const diagnosis = await describeMintFailure(error2, inputs.postmanApiKey, apiBaseUrl, fetchImpl);
18938
+ log.warning(
18939
+ "postman: could not mint an access token from the postman-api-key. " + diagnosis + " Continuing without an access token - access-token-only functionality will be unavailable unless postman-access-token is provided."
18940
+ );
18941
+ }
18942
+ }
18894
18943
 
18895
18944
  // node_modules/@actions/core/lib/core.js
18896
18945
  var core_exports = {};
@@ -21167,12 +21216,62 @@ function getIDToken(aud) {
21167
21216
 
21168
21217
  // src/lib/credential-identity.ts
21169
21218
  var sessionPath = "/api/sessions/current";
21219
+ var SESSION_MAX_ATTEMPTS = 3;
21220
+ var SESSION_RETRY_BASE_DELAY_MS = 500;
21221
+ var SESSION_RETRY_MAX_DELAY_MS = 8e3;
21170
21222
  var pmakMemo = /* @__PURE__ */ new Map();
21171
21223
  var sessionMemo = /* @__PURE__ */ new Map();
21172
21224
  var memoizedSessionIdentity;
21225
+ var memoizedSessionFailure;
21226
+ function defaultSessionSleep(ms) {
21227
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
21228
+ }
21229
+ function defaultRandom() {
21230
+ return Math.random();
21231
+ }
21232
+ function parseRetryAfterMs(value) {
21233
+ const trimmed = value?.trim();
21234
+ if (!trimmed) {
21235
+ return void 0;
21236
+ }
21237
+ if (/^\d+$/.test(trimmed)) {
21238
+ return Number(trimmed) * 1e3;
21239
+ }
21240
+ const dateMs = Date.parse(trimmed);
21241
+ if (!Number.isNaN(dateMs)) {
21242
+ return Math.max(0, dateMs - Date.now());
21243
+ }
21244
+ return void 0;
21245
+ }
21246
+ function parseRateLimitResetMs(value) {
21247
+ const trimmed = value?.trim();
21248
+ if (!trimmed || !/^\d+$/.test(trimmed)) {
21249
+ return void 0;
21250
+ }
21251
+ const seconds = Number(trimmed);
21252
+ const nowSeconds = Date.now() / 1e3;
21253
+ if (seconds > nowSeconds) {
21254
+ return Math.max(0, (seconds - nowSeconds) * 1e3);
21255
+ }
21256
+ return seconds * 1e3;
21257
+ }
21258
+ function computeSessionRetryDelayMs(response, attempt, random) {
21259
+ const headers = response?.headers;
21260
+ const signal = parseRetryAfterMs(headers?.get("retry-after") ?? null) ?? parseRateLimitResetMs(
21261
+ headers?.get("ratelimit-reset") ?? headers?.get("x-ratelimit-reset") ?? null
21262
+ );
21263
+ if (signal !== void 0) {
21264
+ return Math.min(Math.max(0, signal), SESSION_RETRY_MAX_DELAY_MS);
21265
+ }
21266
+ const ceiling = Math.min(SESSION_RETRY_MAX_DELAY_MS, SESSION_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1));
21267
+ return Math.round(random() * ceiling);
21268
+ }
21173
21269
  function getMemoizedSessionIdentity() {
21174
21270
  return memoizedSessionIdentity;
21175
21271
  }
21272
+ function getSessionResolutionFailure() {
21273
+ return memoizedSessionFailure;
21274
+ }
21176
21275
  function asRecord(value) {
21177
21276
  if (!value || typeof value !== "object" || Array.isArray(value)) {
21178
21277
  return void 0;
@@ -21241,46 +21340,90 @@ async function resolveSessionIdentity(opts) {
21241
21340
  const memoKey = `${baseUrl}::${accessToken}`;
21242
21341
  let pending = sessionMemo.get(memoKey);
21243
21342
  if (!pending) {
21244
- pending = probeSessionIdentity(baseUrl, accessToken, opts.fetchImpl ?? fetch);
21343
+ pending = probeSessionIdentity(
21344
+ baseUrl,
21345
+ accessToken,
21346
+ opts.fetchImpl ?? fetch,
21347
+ Math.max(1, opts.maxAttempts ?? SESSION_MAX_ATTEMPTS),
21348
+ opts.sleepImpl ?? defaultSessionSleep,
21349
+ opts.randomImpl ?? defaultRandom
21350
+ );
21245
21351
  sessionMemo.set(memoKey, pending);
21246
21352
  }
21247
21353
  return pending;
21248
21354
  }
21249
- async function probeSessionIdentity(baseUrl, accessToken, fetchImpl) {
21355
+ async function parseSessionResponse(response) {
21356
+ let payload;
21250
21357
  try {
21251
- const response = await fetchImpl(`${baseUrl}${sessionPath}`, {
21252
- method: "GET",
21253
- headers: { "x-access-token": accessToken }
21254
- });
21255
- if (!response.ok) {
21256
- return void 0;
21257
- }
21258
- const payload = asRecord(await response.json());
21259
- if (!payload) {
21260
- return void 0;
21261
- }
21262
- const root = asRecord(payload.session) ?? payload;
21263
- const identity = asRecord(root.identity);
21264
- const data = asRecord(root.data);
21265
- const user = asRecord(data?.user);
21266
- const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
21267
- const singleRole = coerceText(user?.role);
21268
- const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
21269
- const resolved = {
21270
- source: "iapub/sessions",
21271
- userId: coerceId(identity?.user) ?? coerceId(user?.id),
21272
- fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
21273
- teamId: coerceId(identity?.team),
21274
- teamName: coerceText(user?.teamName),
21275
- teamDomain: coerceText(identity?.domain),
21276
- ...roles ? { roles } : {},
21277
- consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
21278
- };
21279
- memoizedSessionIdentity = resolved;
21280
- return resolved;
21358
+ payload = asRecord(await response.json());
21281
21359
  } catch {
21282
21360
  return void 0;
21283
21361
  }
21362
+ if (!payload) {
21363
+ return void 0;
21364
+ }
21365
+ const root = asRecord(payload.session) ?? payload;
21366
+ const identity = asRecord(root.identity);
21367
+ const data = asRecord(root.data);
21368
+ const user = asRecord(data?.user);
21369
+ const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
21370
+ const singleRole = coerceText(user?.role);
21371
+ const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
21372
+ return {
21373
+ source: "iapub/sessions",
21374
+ userId: coerceId(identity?.user) ?? coerceId(user?.id),
21375
+ fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
21376
+ teamId: coerceId(identity?.team),
21377
+ teamName: coerceText(user?.teamName),
21378
+ teamDomain: coerceText(identity?.domain),
21379
+ ...roles ? { roles } : {},
21380
+ consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
21381
+ };
21382
+ }
21383
+ async function probeSessionIdentity(baseUrl, accessToken, fetchImpl, maxAttempts, sleepImpl, random) {
21384
+ let failure = "unavailable";
21385
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
21386
+ let response;
21387
+ try {
21388
+ response = await fetchImpl(`${baseUrl}${sessionPath}`, {
21389
+ method: "GET",
21390
+ headers: { "x-access-token": accessToken }
21391
+ });
21392
+ } catch {
21393
+ failure = "unavailable";
21394
+ if (attempt < maxAttempts) {
21395
+ await sleepImpl(computeSessionRetryDelayMs(void 0, attempt, random));
21396
+ continue;
21397
+ }
21398
+ break;
21399
+ }
21400
+ if (response.ok) {
21401
+ const resolved = await parseSessionResponse(response);
21402
+ if (resolved) {
21403
+ memoizedSessionIdentity = resolved;
21404
+ memoizedSessionFailure = void 0;
21405
+ return resolved;
21406
+ }
21407
+ failure = "unavailable";
21408
+ break;
21409
+ }
21410
+ if (response.status === 401 || response.status === 403) {
21411
+ failure = "auth";
21412
+ break;
21413
+ }
21414
+ if (response.status === 429 || response.status >= 500) {
21415
+ failure = "unavailable";
21416
+ if (attempt < maxAttempts) {
21417
+ await sleepImpl(computeSessionRetryDelayMs(response, attempt, random));
21418
+ continue;
21419
+ }
21420
+ break;
21421
+ }
21422
+ failure = "unavailable";
21423
+ break;
21424
+ }
21425
+ memoizedSessionFailure = failure;
21426
+ return void 0;
21284
21427
  }
21285
21428
  function describeTeam(id) {
21286
21429
  const label = id?.teamName ?? id?.teamDomain;
@@ -21373,7 +21516,9 @@ async function runCredentialPreflight(args) {
21373
21516
  session = await resolveSessionIdentity({
21374
21517
  iapubBaseUrl: args.iapubBaseUrl,
21375
21518
  accessToken,
21376
- fetchImpl: args.fetchImpl
21519
+ fetchImpl: args.fetchImpl,
21520
+ ...args.sleepImpl ? { sleepImpl: args.sleepImpl } : {},
21521
+ ...args.randomImpl ? { randomImpl: args.randomImpl } : {}
21377
21522
  });
21378
21523
  } catch (error2) {
21379
21524
  args.log.warning(
@@ -21393,11 +21538,20 @@ async function runCredentialPreflight(args) {
21393
21538
  );
21394
21539
  }
21395
21540
  } else {
21541
+ const failure = getSessionResolutionFailure();
21542
+ const detail = failure === "auth" ? "the access token was rejected by iapub (401/403), so it is invalid or expired. Re-mint it with postman-resolve-service-token-action (or POST https://api.getpostman.com/service-account-tokens) and re-run." : "iapub was unreachable after retries (network or 5xx). This is usually transient; re-run the job.";
21543
+ const base = "postman: credential preflight could not resolve the access-token session identity from iapub: " + detail;
21544
+ if (args.mode === "enforce") {
21545
+ throw new Error(
21546
+ mask(
21547
+ `${base} (credential-preflight: enforce requires a resolvable session identity; use credential-preflight: warn to continue with reactive error guidance only.)`
21548
+ )
21549
+ );
21550
+ }
21396
21551
  args.log.warning(
21397
- mask(
21398
- "postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
21399
- )
21552
+ mask(`${base} Continuing with reactive error guidance only (credential-preflight: warn).`)
21400
21553
  );
21554
+ return;
21401
21555
  }
21402
21556
  const result = crossCheckIdentities({
21403
21557
  pmak,
@@ -22010,6 +22164,35 @@ function getFinalServiceSegment(serviceName) {
22010
22164
  return lastSlash === -1 ? serviceName : serviceName.slice(lastSlash + 1);
22011
22165
  }
22012
22166
 
22167
+ // src/lib/input.ts
22168
+ function normalizeInputValue(value) {
22169
+ return String(value ?? "").trim();
22170
+ }
22171
+ function runnerInputEnvName(name) {
22172
+ return `INPUT_${name.replace(/ /g, "_").toUpperCase()}`;
22173
+ }
22174
+ function normalizedInputEnvName(name) {
22175
+ return `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
22176
+ }
22177
+ function getInput2(name, env = process.env) {
22178
+ const normalizedName = normalizedInputEnvName(name);
22179
+ const runnerName = runnerInputEnvName(name);
22180
+ const normalizedRaw = env[normalizedName];
22181
+ const runnerRaw = runnerName === normalizedName ? void 0 : env[runnerName];
22182
+ const hasNormalized = normalizedRaw !== void 0;
22183
+ const hasRunner = runnerRaw !== void 0;
22184
+ if (hasNormalized && hasRunner) {
22185
+ const normalizedValue = normalizeInputValue(normalizedRaw);
22186
+ const runnerValue = normalizeInputValue(runnerRaw);
22187
+ if (normalizedValue !== runnerValue) {
22188
+ throw new Error(
22189
+ `Conflicting values for ${name}: ${normalizedName}=${JSON.stringify(normalizedValue)} vs ${runnerName}=${JSON.stringify(runnerValue)}`
22190
+ );
22191
+ }
22192
+ }
22193
+ return normalizeInputValue(hasNormalized ? normalizedRaw : runnerRaw);
22194
+ }
22195
+
22013
22196
  // node_modules/@postman-cse/automation-telemetry-core/dist/ci-context.js
22014
22197
  function norm(value) {
22015
22198
  const trimmed = (value ?? "").trim();
@@ -22264,7 +22447,7 @@ function resolveActionVersion(explicit) {
22264
22447
  if (explicit) {
22265
22448
  return explicit;
22266
22449
  }
22267
- return "2.0.0" ? "2.0.0" : "unknown";
22450
+ return typeof __ACTION_VERSION__ !== "undefined" && __ACTION_VERSION__ ? __ACTION_VERSION__ : "unknown";
22268
22451
  }
22269
22452
  function telemetryDisabled(env) {
22270
22453
  const flag = String(env.POSTMAN_ACTIONS_TELEMETRY ?? "").trim().toLowerCase();
@@ -22386,6 +22569,18 @@ function createTelemetryContext(options) {
22386
22569
  };
22387
22570
  }
22388
22571
 
22572
+ // src/action-version.ts
22573
+ var import_node_fs = require("node:fs");
22574
+ var import_node_path = require("node:path");
22575
+ function resolveActionVersion2() {
22576
+ try {
22577
+ const raw = (0, import_node_fs.readFileSync)((0, import_node_path.join)(__dirname, "..", "package.json"), "utf8");
22578
+ return JSON.parse(raw).version ?? "unknown";
22579
+ } catch {
22580
+ return "unknown";
22581
+ }
22582
+ }
22583
+
22389
22584
  // src/index.ts
22390
22585
  var POLL_TIMEOUT_MIN = 10;
22391
22586
  var POLL_TIMEOUT_MAX = 600;
@@ -22447,12 +22642,16 @@ function clamp(value, min, max, fallback) {
22447
22642
  return Math.min(max, Math.max(min, parsed));
22448
22643
  }
22449
22644
  function resolveInputs(env = process.env) {
22450
- const get = (name, fallback = "") => env[`INPUT_${name.toUpperCase().replace(/-/g, "_")}`]?.trim() || fallback;
22645
+ const get = (name, fallback = "") => getInput2(name, env) || fallback;
22451
22646
  const projectName = get("project-name");
22452
22647
  if (!projectName) throw new Error("project-name is required");
22453
22648
  const postmanAccessToken = get("postman-access-token");
22454
- if (!postmanAccessToken) throw new Error("postman-access-token is required");
22455
22649
  const postmanApiKey = get("postman-api-key");
22650
+ if (!postmanAccessToken && !postmanApiKey) {
22651
+ throw new Error(
22652
+ "postman-access-token is required (or provide a service-account postman-api-key so the action can mint one)."
22653
+ );
22654
+ }
22456
22655
  const postmanTeamId = get("postman-team-id") || env.POSTMAN_TEAM_ID?.trim() || "";
22457
22656
  const workspaceId = get("workspace-id") || env.POSTMAN_WORKSPACE_ID?.trim() || "";
22458
22657
  if (!workspaceId) {
@@ -22678,6 +22877,24 @@ function createInsightsBifrostClient(inputs, tokenProvider, teamId, apiKey) {
22678
22877
  }
22679
22878
 
22680
22879
  // src/cli.ts
22880
+ var INPUT_NAMES = [
22881
+ "project-name",
22882
+ "workspace-id",
22883
+ "environment-id",
22884
+ "system-environment-id",
22885
+ "cluster-name",
22886
+ "repo-url",
22887
+ "postman-access-token",
22888
+ "postman-api-key",
22889
+ "credential-preflight",
22890
+ "postman-team-id",
22891
+ "github-token",
22892
+ "poll-timeout-seconds",
22893
+ "poll-interval-seconds",
22894
+ "postman-region",
22895
+ "postman-stack"
22896
+ ];
22897
+ var OUTPUT_OPTION_NAMES = ["result-json", "dotenv-path"];
22681
22898
  var ConsoleReporter = class {
22682
22899
  secretValues = [];
22683
22900
  info(message) {
@@ -22699,51 +22916,101 @@ var ConsoleReporter = class {
22699
22916
  return masked;
22700
22917
  }
22701
22918
  };
22702
- function readFlag(argv, name) {
22703
- const prefix = `--${name}=`;
22704
- for (let index = 0; index < argv.length; index += 1) {
22705
- const arg = argv[index];
22706
- if (arg === `--${name}`) {
22707
- return argv[index + 1];
22708
- }
22709
- if (arg?.startsWith(prefix)) {
22710
- return arg.slice(prefix.length);
22919
+ function normalizeCliFlag(name) {
22920
+ return normalizedInputEnvName(name);
22921
+ }
22922
+ function resolvePackageVersion() {
22923
+ const candidates = [];
22924
+ if (typeof __filename === "string") {
22925
+ candidates.push(import_node_path2.default.join(import_node_path2.default.dirname(__filename), "..", "package.json"));
22926
+ }
22927
+ candidates.push(import_node_path2.default.join(process.cwd(), "package.json"));
22928
+ for (const candidate of candidates) {
22929
+ try {
22930
+ const packageJson = JSON.parse((0, import_node_fs2.readFileSync)(candidate, "utf8"));
22931
+ if (packageJson.name === "@postman-cse/onboarding-insights" && packageJson.version) {
22932
+ return String(packageJson.version).trim();
22933
+ }
22934
+ } catch {
22711
22935
  }
22712
22936
  }
22713
- return void 0;
22937
+ return resolveActionVersion2();
22714
22938
  }
22715
- function normalizeCliFlag(name) {
22716
- return `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
22939
+ function renderHelp() {
22940
+ const inputFlags = INPUT_NAMES.map((name) => ` --${name} <value>`).join("\n");
22941
+ return [
22942
+ "Usage: postman-insights-onboard [options]",
22943
+ "",
22944
+ "Options:",
22945
+ inputFlags,
22946
+ " --result-json <path> Optional JSON output file (opt-in)",
22947
+ " --dotenv-path <path> Optional dotenv output file",
22948
+ " --help Show this help and exit",
22949
+ " --version Print version and exit",
22950
+ ""
22951
+ ].join("\n");
22717
22952
  }
22718
22953
  function parseCliArgs(argv, env = process.env) {
22719
- const inputNames = [
22720
- "project-name",
22721
- "workspace-id",
22722
- "environment-id",
22723
- "system-environment-id",
22724
- "cluster-name",
22725
- "repo-url",
22726
- "postman-access-token",
22727
- "postman-api-key",
22728
- "credential-preflight",
22729
- "postman-team-id",
22730
- "github-token",
22731
- "poll-timeout-seconds",
22732
- "poll-interval-seconds",
22733
- "postman-region",
22734
- "postman-stack"
22735
- ];
22954
+ if (argv.includes("--help") || argv.includes("-h")) {
22955
+ return { kind: "help" };
22956
+ }
22957
+ if (argv.includes("--version") || argv.includes("-V")) {
22958
+ return { kind: "version" };
22959
+ }
22960
+ const allowed = /* @__PURE__ */ new Set([...INPUT_NAMES, ...OUTPUT_OPTION_NAMES]);
22961
+ const seen = /* @__PURE__ */ new Set();
22736
22962
  const inputEnv = { ...env };
22737
- for (const name of inputNames) {
22738
- const value = readFlag(argv, name);
22739
- if (value !== void 0) {
22740
- inputEnv[normalizeCliFlag(name)] = value;
22963
+ let resultJsonPath;
22964
+ let dotenvPath;
22965
+ for (let index = 0; index < argv.length; index += 1) {
22966
+ const arg = argv[index];
22967
+ if (!arg) {
22968
+ continue;
22741
22969
  }
22970
+ if (!arg.startsWith("--")) {
22971
+ throw new Error(`Unexpected positional argument: ${arg}`);
22972
+ }
22973
+ const equalsIndex = arg.indexOf("=");
22974
+ const name = equalsIndex >= 0 ? arg.slice(2, equalsIndex) : arg.slice(2);
22975
+ if (!allowed.has(name)) {
22976
+ throw new Error(`Unknown option: --${name}`);
22977
+ }
22978
+ if (seen.has(name)) {
22979
+ throw new Error(`Duplicate option: --${name}`);
22980
+ }
22981
+ let value;
22982
+ if (equalsIndex >= 0) {
22983
+ value = arg.slice(equalsIndex + 1);
22984
+ } else {
22985
+ const next = argv[index + 1];
22986
+ if (next === void 0 || next.startsWith("--")) {
22987
+ throw new Error(`Missing value for --${name}`);
22988
+ }
22989
+ value = next;
22990
+ index += 1;
22991
+ }
22992
+ if (value.length === 0) {
22993
+ throw new Error(`Missing value for --${name}`);
22994
+ }
22995
+ seen.add(name);
22996
+ if (name === "result-json") {
22997
+ resultJsonPath = value;
22998
+ continue;
22999
+ }
23000
+ if (name === "dotenv-path") {
23001
+ dotenvPath = value;
23002
+ continue;
23003
+ }
23004
+ const normalizedName = normalizedInputEnvName(name);
23005
+ delete inputEnv[runnerInputEnvName(name)];
23006
+ delete inputEnv[normalizedName];
23007
+ inputEnv[normalizedName] = value;
22742
23008
  }
22743
23009
  return {
23010
+ kind: "run",
22744
23011
  inputEnv,
22745
- resultJsonPath: readFlag(argv, "result-json") ?? "postman-insights-onboarding-result.json",
22746
- dotenvPath: readFlag(argv, "dotenv-path")
23012
+ resultJsonPath,
23013
+ dotenvPath
22747
23014
  };
22748
23015
  }
22749
23016
  function toDotenv(outputs) {
@@ -22752,18 +23019,63 @@ function toDotenv(outputs) {
22752
23019
  value
22753
23020
  ]).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n");
22754
23021
  }
22755
- async function writeOptionalFile(filePath, content) {
23022
+ function assertWithinWorkspace(workspaceRoot, resolved, filePath) {
23023
+ const relative2 = import_node_path2.default.relative(workspaceRoot, resolved);
23024
+ if (relative2 === ".." || relative2.startsWith(`..${import_node_path2.default.sep}`) || import_node_path2.default.isAbsolute(relative2)) {
23025
+ throw new Error(`Output path must stay within workspace: ${filePath}`);
23026
+ }
23027
+ }
23028
+ async function findExistingAncestor(candidate) {
23029
+ let current = candidate;
23030
+ while (true) {
23031
+ try {
23032
+ return await (0, import_promises.realpath)(current);
23033
+ } catch (error2) {
23034
+ if (error2.code !== "ENOENT") {
23035
+ throw error2;
23036
+ }
23037
+ const parent = import_node_path2.default.dirname(current);
23038
+ if (parent === current) {
23039
+ throw error2;
23040
+ }
23041
+ current = parent;
23042
+ }
23043
+ }
23044
+ }
23045
+ async function validateOutputPath(filePath) {
22756
23046
  if (!filePath) {
22757
23047
  return;
22758
23048
  }
22759
- const workspaceRoot = import_node_path.default.resolve(process.cwd());
22760
- const resolved = import_node_path.default.resolve(workspaceRoot, filePath);
22761
- const relative2 = import_node_path.default.relative(workspaceRoot, resolved);
22762
- if (relative2.startsWith("..") || import_node_path.default.isAbsolute(relative2)) {
22763
- throw new Error(`Output path must stay within workspace: ${filePath}`);
23049
+ const workspaceRoot = await (0, import_promises.realpath)(process.cwd());
23050
+ const resolved = import_node_path2.default.resolve(workspaceRoot, filePath);
23051
+ assertWithinWorkspace(workspaceRoot, resolved, filePath);
23052
+ const existingParent = await findExistingAncestor(import_node_path2.default.dirname(resolved));
23053
+ assertWithinWorkspace(workspaceRoot, existingParent, filePath);
23054
+ }
23055
+ async function writeAtomicFile(filePath, content) {
23056
+ const workspaceRoot = await (0, import_promises.realpath)(process.cwd());
23057
+ const resolved = import_node_path2.default.resolve(workspaceRoot, filePath);
23058
+ assertWithinWorkspace(workspaceRoot, resolved, filePath);
23059
+ await (0, import_promises.mkdir)(import_node_path2.default.dirname(resolved), { recursive: true });
23060
+ const resolvedParent = await (0, import_promises.realpath)(import_node_path2.default.dirname(resolved));
23061
+ assertWithinWorkspace(workspaceRoot, resolvedParent, filePath);
23062
+ const safeTarget = import_node_path2.default.join(resolvedParent, import_node_path2.default.basename(resolved));
23063
+ const tempPath = import_node_path2.default.join(
23064
+ resolvedParent,
23065
+ `.${import_node_path2.default.basename(resolved)}.${process.pid}.${(0, import_node_crypto2.randomUUID)()}.tmp`
23066
+ );
23067
+ try {
23068
+ await (0, import_promises.writeFile)(tempPath, content, { encoding: "utf8", flag: "wx", mode: 384 });
23069
+ await (0, import_promises.rename)(tempPath, safeTarget);
23070
+ } finally {
23071
+ await (0, import_promises.rm)(tempPath, { force: true });
22764
23072
  }
22765
- await (0, import_promises.mkdir)(import_node_path.default.dirname(resolved), { recursive: true });
22766
- await (0, import_promises.writeFile)(resolved, content, "utf8");
23073
+ }
23074
+ async function writeOptionalFile(filePath, content) {
23075
+ if (!filePath) {
23076
+ return;
23077
+ }
23078
+ await writeAtomicFile(filePath, content);
22767
23079
  }
22768
23080
  function toOutputs(result) {
22769
23081
  return {
@@ -22777,10 +23089,30 @@ function toOutputs(result) {
22777
23089
  }
22778
23090
  async function runCli(argv = process.argv.slice(2), runtime = {}) {
22779
23091
  const env = runtime.env ?? process.env;
22780
- const config = parseCliArgs(argv, env);
23092
+ const writeStdout = runtime.writeStdout ?? ((chunk) => process.stdout.write(chunk));
23093
+ const parsed = parseCliArgs(argv, env);
23094
+ if (parsed.kind === "help") {
23095
+ writeStdout(renderHelp());
23096
+ return;
23097
+ }
23098
+ if (parsed.kind === "version") {
23099
+ writeStdout(`${resolvePackageVersion()}
23100
+ `);
23101
+ return;
23102
+ }
23103
+ const config = parsed;
23104
+ await validateOutputPath(config.resultJsonPath);
23105
+ await validateOutputPath(config.dotenvPath);
22781
23106
  const inputs = resolveInputs(config.inputEnv);
22782
23107
  const reporter = new ConsoleReporter();
22783
- reporter.setSecret(inputs.postmanAccessToken);
23108
+ const mintHolder = {
23109
+ postmanAccessToken: inputs.postmanAccessToken,
23110
+ postmanApiKey: inputs.postmanApiKey,
23111
+ postmanApiBase: inputs.postmanApiBase
23112
+ };
23113
+ await mintAccessTokenIfNeeded(mintHolder, reporter, (secret) => reporter.setSecret(secret));
23114
+ inputs.postmanAccessToken = mintHolder.postmanAccessToken;
23115
+ if (inputs.postmanAccessToken) reporter.setSecret(inputs.postmanAccessToken);
22784
23116
  if (inputs.postmanApiKey) {
22785
23117
  reporter.setSecret(inputs.postmanApiKey);
22786
23118
  }
@@ -22805,7 +23137,7 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
22805
23137
  apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22806
23138
  onToken: (token) => reporter.setSecret(token)
22807
23139
  }) : tokenProvider;
22808
- const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", logger: reporter });
23140
+ const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", actionVersion: resolveActionVersion2(), logger: reporter });
22809
23141
  telemetry.setTeamId(inputs.postmanTeamId || pmakIdentity?.teamId);
22810
23142
  if (apiKey) {
22811
23143
  reporter.setSecret(apiKey);
@@ -22839,7 +23171,6 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
22839
23171
  const jsonOutput = JSON.stringify(outputs, null, 2);
22840
23172
  await writeOptionalFile(config.resultJsonPath, jsonOutput);
22841
23173
  await writeOptionalFile(config.dotenvPath, toDotenv(outputs));
22842
- const writeStdout = runtime.writeStdout ?? ((chunk) => process.stdout.write(chunk));
22843
23174
  writeStdout(`${jsonOutput}
22844
23175
  `);
22845
23176
  }
@@ -22850,9 +23181,9 @@ function isEntrypoint(currentPath, entrypointPath) {
22850
23181
  return false;
22851
23182
  }
22852
23183
  try {
22853
- return (0, import_node_fs.realpathSync)(currentPath) === (0, import_node_fs.realpathSync)(entrypointPath);
23184
+ return (0, import_node_fs2.realpathSync)(currentPath) === (0, import_node_fs2.realpathSync)(entrypointPath);
22854
23185
  } catch {
22855
- return import_node_path.default.resolve(currentPath) === import_node_path.default.resolve(entrypointPath);
23186
+ return import_node_path2.default.resolve(currentPath) === import_node_path2.default.resolve(entrypointPath);
22856
23187
  }
22857
23188
  }
22858
23189
  if (isEntrypoint(currentModulePath, entrypoint)) {