@postman-cse/onboarding-bootstrap 0.10.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.
@@ -1,121 +0,0 @@
1
- import { normalizeGitRepoUrl } from './postman-assets-client.js';
2
-
3
- type WorkspaceCandidate = {
4
- id: string;
5
- linkedRepoUrl?: string | null;
6
- };
7
-
8
- type ChooseCanonicalWorkspaceArgs = {
9
- repoWorkspaceId?: string;
10
- repoUrl: string;
11
- matchingWorkspaces: WorkspaceCandidate[];
12
- };
13
-
14
- type WorkspaceLookupClient = {
15
- findWorkspacesByName(name: string): Promise<Array<{ id: string; name: string }>>;
16
- getWorkspaceGitRepoUrl(workspaceId: string, teamId: string, accessToken: string): Promise<string | null>;
17
- };
18
-
19
- type RepoVariableClient = {
20
- setRepositoryVariable(name: string, value: string): Promise<unknown>;
21
- };
22
-
23
- export type CanonicalWorkspaceSelection =
24
- | { type: 'existing'; workspaceId: string; source: 'linked_match' | 'repo_var' | 'name_match'; warning?: string }
25
- | { type: 'create' }
26
- | { type: 'manual_review'; reason: string };
27
-
28
- export function chooseCanonicalWorkspace(args: ChooseCanonicalWorkspaceArgs): CanonicalWorkspaceSelection {
29
- const repoWorkspaceId = String(args.repoWorkspaceId || '').trim();
30
- const normalizedRepoUrl = normalizeGitRepoUrl(args.repoUrl);
31
- const matchingWorkspaces = [...args.matchingWorkspaces].sort((a, b) => a.id.localeCompare(b.id));
32
-
33
- const linkedMatches = matchingWorkspaces.filter((workspace) =>
34
- normalizeGitRepoUrl(workspace.linkedRepoUrl) === normalizedRepoUrl,
35
- );
36
-
37
- if (linkedMatches.length === 1) {
38
- const linked = linkedMatches[0];
39
- return {
40
- type: 'existing',
41
- workspaceId: linked.id,
42
- source: 'linked_match',
43
- warning: repoWorkspaceId && repoWorkspaceId !== linked.id
44
- ? `Replacing repo workspace ${repoWorkspaceId} with canonical GitHub-linked workspace ${linked.id}`
45
- : undefined,
46
- };
47
- }
48
-
49
- if (linkedMatches.length > 1) {
50
- if (repoWorkspaceId && linkedMatches.some((workspace) => workspace.id === repoWorkspaceId)) {
51
- return {
52
- type: 'existing',
53
- workspaceId: repoWorkspaceId,
54
- source: 'linked_match',
55
- warning: `Multiple GitHub-linked workspaces matched ${normalizedRepoUrl}; keeping existing linked repo workspace ${repoWorkspaceId} until manual cleanup.`,
56
- };
57
- }
58
- return {
59
- type: 'manual_review',
60
- reason: `Multiple GitHub-linked workspaces matched ${normalizedRepoUrl}: ${linkedMatches.map((workspace) => workspace.id).join(', ')}`,
61
- };
62
- }
63
-
64
- if (repoWorkspaceId) {
65
- const candidate = matchingWorkspaces.find((w) => w.id === repoWorkspaceId);
66
- if (candidate && candidate.linkedRepoUrl && normalizeGitRepoUrl(candidate.linkedRepoUrl) !== normalizedRepoUrl) {
67
- return { type: 'create' };
68
- }
69
- return {
70
- type: 'existing',
71
- workspaceId: repoWorkspaceId,
72
- source: 'repo_var',
73
- };
74
- }
75
-
76
- if (matchingWorkspaces.length > 0) {
77
- const candidate = matchingWorkspaces[0];
78
- if (candidate.linkedRepoUrl && normalizeGitRepoUrl(candidate.linkedRepoUrl) !== normalizedRepoUrl) {
79
- return { type: 'create' };
80
- }
81
- return {
82
- type: 'existing',
83
- workspaceId: candidate.id,
84
- source: 'name_match',
85
- };
86
- }
87
-
88
- return { type: 'create' };
89
- }
90
-
91
- export async function resolveCanonicalWorkspaceSelection(args: {
92
- postman: WorkspaceLookupClient;
93
- workspaceName: string;
94
- repoWorkspaceId?: string;
95
- repoUrl: string;
96
- teamId: string;
97
- accessToken: string;
98
- warn?: (message: string) => void;
99
- }): Promise<CanonicalWorkspaceSelection> {
100
- let matchingWorkspaces: Array<{ id: string; name: string; linkedRepoUrl?: string | null }> = [];
101
-
102
- try {
103
- matchingWorkspaces = await args.postman.findWorkspacesByName(args.workspaceName);
104
- } catch (error) {
105
- if (!args.repoWorkspaceId) throw error;
106
- args.warn?.(`Workspace duplicate check failed; falling back to repo workspace ${args.repoWorkspaceId}: ${error}`);
107
- }
108
-
109
- if (matchingWorkspaces.length > 0) {
110
- matchingWorkspaces = await Promise.all(matchingWorkspaces.map(async (workspace) => ({
111
- ...workspace,
112
- linkedRepoUrl: await args.postman.getWorkspaceGitRepoUrl(workspace.id, args.teamId, args.accessToken),
113
- })));
114
- }
115
-
116
- return chooseCanonicalWorkspace({
117
- repoWorkspaceId: args.repoWorkspaceId,
118
- repoUrl: args.repoUrl,
119
- matchingWorkspaces,
120
- });
121
- }
@@ -1,119 +0,0 @@
1
- export type GitProvider = 'github' | 'gitlab' | 'bitbucket' | 'azure-devops' | 'unknown';
2
-
3
- export interface RepoContextInput {
4
- repoUrl?: string;
5
- repoSlug?: string;
6
- gitProvider?: string;
7
- ref?: string;
8
- sha?: string;
9
- }
10
-
11
- export interface RepoContext {
12
- provider: GitProvider;
13
- repoUrl?: string;
14
- repoSlug?: string;
15
- ref?: string;
16
- sha?: string;
17
- }
18
-
19
- function normalize(value?: string): string | undefined {
20
- const trimmed = (value ?? '').trim();
21
- return trimmed.length > 0 ? trimmed : undefined;
22
- }
23
-
24
- function normalizeRepoUrl(url?: string): string | undefined {
25
- const raw = normalize(url);
26
- if (!raw) {
27
- return undefined;
28
- }
29
-
30
- const sshMatch = raw.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
31
- if (sshMatch) {
32
- const host = sshMatch[1];
33
- const path = sshMatch[2];
34
- return `https://${host}/${path}`;
35
- }
36
-
37
- return raw.replace(/\.git$/, '');
38
- }
39
-
40
- function parseProvider(
41
- explicitProvider: string | undefined,
42
- repoUrl: string | undefined,
43
- env: NodeJS.ProcessEnv
44
- ): GitProvider {
45
- const explicit = normalize(explicitProvider)?.toLowerCase();
46
- if (explicit === 'github' || explicit === 'gitlab' || explicit === 'bitbucket' || explicit === 'azure-devops') {
47
- return explicit;
48
- }
49
-
50
- const url = (repoUrl ?? '').toLowerCase();
51
- if (url.includes('github')) {
52
- return 'github';
53
- }
54
- if (url.includes('gitlab')) {
55
- return 'gitlab';
56
- }
57
- if (url.includes('bitbucket')) {
58
- return 'bitbucket';
59
- }
60
- if (url.includes('dev.azure.com') || url.includes('visualstudio.com')) {
61
- return 'azure-devops';
62
- }
63
-
64
- if (normalize(env.GITHUB_REPOSITORY)) {
65
- return 'github';
66
- }
67
- if (normalize(env.CI_PROJECT_PATH) || normalize(env.GITLAB_CI)) {
68
- return 'gitlab';
69
- }
70
- if (normalize(env.BITBUCKET_REPO_SLUG)) {
71
- return 'bitbucket';
72
- }
73
- if (normalize(env.BUILD_REPOSITORY_URI)) {
74
- return 'azure-devops';
75
- }
76
-
77
- return 'unknown';
78
- }
79
-
80
- export function detectRepoContext(
81
- input: RepoContextInput,
82
- env: NodeJS.ProcessEnv = process.env
83
- ): RepoContext {
84
- const repoUrl =
85
- normalizeRepoUrl(input.repoUrl) ??
86
- normalizeRepoUrl(env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}` : undefined) ??
87
- normalizeRepoUrl(env.CI_PROJECT_URL) ??
88
- normalizeRepoUrl(env.BITBUCKET_GIT_HTTP_ORIGIN) ??
89
- normalizeRepoUrl(env.BUILD_REPOSITORY_URI);
90
- const repoSlug =
91
- normalize(input.repoSlug) ??
92
- normalize(env.GITHUB_REPOSITORY) ??
93
- normalize(env.CI_PROJECT_PATH) ??
94
- (env.BITBUCKET_WORKSPACE && env.BITBUCKET_REPO_SLUG
95
- ? normalize(`${env.BITBUCKET_WORKSPACE}/${env.BITBUCKET_REPO_SLUG}`)
96
- : undefined) ??
97
- normalize(env.BUILD_REPOSITORY_NAME);
98
- const ref =
99
- normalize(input.ref) ??
100
- normalize(env.GITHUB_REF_NAME) ??
101
- normalize(env.CI_COMMIT_REF_NAME) ??
102
- normalize(env.BITBUCKET_BRANCH) ??
103
- normalize(env.BUILD_SOURCEBRANCHNAME);
104
- const sha =
105
- normalize(input.sha) ??
106
- normalize(env.GITHUB_SHA) ??
107
- normalize(env.CI_COMMIT_SHA) ??
108
- normalize(env.BITBUCKET_COMMIT) ??
109
- normalize(env.BUILD_SOURCEVERSION);
110
- const provider = parseProvider(input.gitProvider, repoUrl, env);
111
-
112
- return {
113
- provider,
114
- repoUrl,
115
- repoSlug,
116
- ref,
117
- sha
118
- };
119
- }
package/src/lib/retry.ts DELETED
@@ -1,79 +0,0 @@
1
- export interface RetryDecisionContext {
2
- attempt: number;
3
- maxAttempts: number;
4
- }
5
-
6
- export interface RetryContext extends RetryDecisionContext {
7
- delayMs: number;
8
- error: unknown;
9
- }
10
-
11
- export interface RetryOptions {
12
- maxAttempts?: number;
13
- delayMs?: number;
14
- backoffMultiplier?: number;
15
- maxDelayMs?: number;
16
- onRetry?: (context: RetryContext) => void | Promise<void>;
17
- shouldRetry?: (error: unknown, context: RetryDecisionContext) => boolean;
18
- sleep?: (delayMs: number) => Promise<void>;
19
- }
20
-
21
- export function sleep(delayMs: number): Promise<void> {
22
- return new Promise((resolve) => {
23
- setTimeout(resolve, delayMs);
24
- });
25
- }
26
-
27
- function normalizeRetryOptions(options: RetryOptions): Required<RetryOptions> {
28
- return {
29
- maxAttempts: Math.max(1, options.maxAttempts ?? 3),
30
- delayMs: Math.max(0, options.delayMs ?? 2000),
31
- backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
32
- maxDelayMs:
33
- options.maxDelayMs === undefined
34
- ? Number.POSITIVE_INFINITY
35
- : Math.max(0, options.maxDelayMs),
36
- onRetry: options.onRetry ?? (async () => undefined),
37
- shouldRetry: options.shouldRetry ?? (() => true),
38
- sleep: options.sleep ?? sleep
39
- };
40
- }
41
-
42
- export async function retry<T>(
43
- operation: () => Promise<T>,
44
- options: RetryOptions = {}
45
- ): Promise<T> {
46
- const normalized = normalizeRetryOptions(options);
47
- let nextDelayMs = normalized.delayMs;
48
-
49
- for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
50
- try {
51
- return await operation();
52
- } catch (error) {
53
- const shouldRetry =
54
- attempt < normalized.maxAttempts &&
55
- normalized.shouldRetry(error, {
56
- attempt,
57
- maxAttempts: normalized.maxAttempts
58
- });
59
-
60
- if (!shouldRetry) {
61
- throw error;
62
- }
63
-
64
- await normalized.onRetry({
65
- attempt,
66
- maxAttempts: normalized.maxAttempts,
67
- delayMs: nextDelayMs,
68
- error
69
- });
70
- await normalized.sleep(nextDelayMs);
71
- nextDelayMs = Math.min(
72
- normalized.maxDelayMs,
73
- Math.round(nextDelayMs * normalized.backoffMultiplier)
74
- );
75
- }
76
- }
77
-
78
- throw new Error('Retry exhausted without returning or throwing');
79
- }
@@ -1,104 +0,0 @@
1
- export const REDACTED = '[REDACTED]';
2
- export type SecretMasker = (input: string) => string;
3
- export type HeaderBag =
4
- | Array<[string, string]>
5
- | Headers
6
- | Record<string, string>;
7
-
8
- const SENSITIVE_HEADER_NAMES = new Set([
9
- 'authorization',
10
- 'cookie',
11
- 'proxy-authorization',
12
- 'set-cookie',
13
- 'x-access-token',
14
- 'x-api-key'
15
- ]);
16
-
17
- function isIterable(value: unknown): value is Iterable<unknown> {
18
- return (
19
- value !== null &&
20
- value !== undefined &&
21
- typeof value !== 'string' &&
22
- typeof (value as Iterable<unknown>)[Symbol.iterator] === 'function'
23
- );
24
- }
25
-
26
- function appendSecretValues(value: unknown, results: string[]): void {
27
- if (value === null || value === undefined) {
28
- return;
29
- }
30
- if (typeof value === 'string') {
31
- const normalized = value.trim();
32
- if (normalized) {
33
- results.push(normalized);
34
- }
35
- return;
36
- }
37
- if (typeof value === 'number' || typeof value === 'boolean') {
38
- results.push(String(value));
39
- return;
40
- }
41
- if (Array.isArray(value) || isIterable(value)) {
42
- for (const entry of value) {
43
- appendSecretValues(entry, results);
44
- }
45
- }
46
- }
47
-
48
- export function normalizeSecretValues(secretValues: unknown): string[] {
49
- const values: string[] = [];
50
- appendSecretValues(secretValues, values);
51
- return [...new Set(values)].sort((left, right) => right.length - left.length);
52
- }
53
-
54
- export function redactSecrets(
55
- input: string,
56
- secretValues: unknown,
57
- replacement = REDACTED
58
- ): string {
59
- const source = String(input ?? '');
60
- const secrets = normalizeSecretValues(secretValues);
61
- if (!source || secrets.length === 0) {
62
- return source;
63
- }
64
- return secrets.reduce((sanitized, secret) => {
65
- if (!secret) {
66
- return sanitized;
67
- }
68
- return sanitized.split(secret).join(replacement);
69
- }, source);
70
- }
71
-
72
- export function createSecretMasker(
73
- secretValues: unknown,
74
- replacement = REDACTED
75
- ): SecretMasker {
76
- return (input: string) => redactSecrets(input, secretValues, replacement);
77
- }
78
-
79
- function headerEntries(headers: HeaderBag): Array<[string, string]> {
80
- if (headers instanceof Headers) {
81
- return Array.from(headers.entries());
82
- }
83
- if (Array.isArray(headers)) {
84
- return headers.map(([name, value]) => [name, String(value)]);
85
- }
86
- return Object.entries(headers).map(([name, value]) => [name, String(value)]);
87
- }
88
-
89
- export function sanitizeHeaders(
90
- headers: HeaderBag | undefined,
91
- secretValues: unknown
92
- ): Record<string, string> {
93
- if (!headers) {
94
- return {};
95
- }
96
- const sanitized: Record<string, string> = {};
97
- for (const [name, value] of headerEntries(headers)) {
98
- const normalizedName = name.toLowerCase();
99
- sanitized[normalizedName] = SENSITIVE_HEADER_NAMES.has(normalizedName)
100
- ? REDACTED
101
- : redactSecrets(value, secretValues);
102
- }
103
- return sanitized;
104
- }