bktide 1.0.1755267617 → 1.0.1755559112
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 +107 -1
- package/WORKFLOW_README.md +1 -1
- package/completions/bktide-dynamic.fish +171 -0
- package/completions/bktide.bash +124 -0
- package/completions/bktide.fish +107 -0
- package/completions/bktide.zsh +139 -0
- package/dist/commands/BaseCommand.js +7 -7
- package/dist/commands/BaseCommand.js.map +1 -1
- package/dist/commands/GenerateCompletions.js +238 -0
- package/dist/commands/GenerateCompletions.js.map +1 -0
- package/dist/commands/ListAnnotations.js +7 -0
- package/dist/commands/ListAnnotations.js.map +1 -1
- package/dist/commands/ListBuilds.js +67 -3
- package/dist/commands/ListBuilds.js.map +1 -1
- package/dist/commands/ListOrganizations.js +6 -0
- package/dist/commands/ListOrganizations.js.map +1 -1
- package/dist/commands/ListPipelines.js +87 -12
- package/dist/commands/ListPipelines.js.map +1 -1
- package/dist/commands/ManageToken.js +32 -9
- package/dist/commands/ManageToken.js.map +1 -1
- package/dist/commands/ShowBuild.js +88 -0
- package/dist/commands/ShowBuild.js.map +1 -0
- package/dist/commands/ShowViewer.js +7 -1
- package/dist/commands/ShowViewer.js.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/formatters/FormatterFactory.js +4 -0
- package/dist/formatters/FormatterFactory.js.map +1 -1
- package/dist/formatters/annotations/PlainTextFormatter.js +37 -9
- package/dist/formatters/annotations/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/build-detail/AlfredFormatter.js +113 -0
- package/dist/formatters/build-detail/AlfredFormatter.js.map +1 -0
- package/dist/formatters/build-detail/Formatter.js +3 -0
- package/dist/formatters/build-detail/Formatter.js.map +1 -0
- package/dist/formatters/build-detail/JsonFormatter.js +132 -0
- package/dist/formatters/build-detail/JsonFormatter.js.map +1 -0
- package/dist/formatters/build-detail/PlainTextFormatter.js +680 -0
- package/dist/formatters/build-detail/PlainTextFormatter.js.map +1 -0
- package/dist/formatters/build-detail/index.js +21 -0
- package/dist/formatters/build-detail/index.js.map +1 -0
- package/dist/formatters/builds/PlainTextFormatter.js +82 -60
- package/dist/formatters/builds/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/errors/AlfredFormatter.js +20 -0
- package/dist/formatters/errors/AlfredFormatter.js.map +1 -1
- package/dist/formatters/errors/PlainTextFormatter.js +121 -23
- package/dist/formatters/errors/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/organizations/PlainTextFormatter.js +37 -6
- package/dist/formatters/organizations/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/pipelines/AlfredFormatter.js.map +1 -1
- package/dist/formatters/pipelines/Formatter.js.map +1 -1
- package/dist/formatters/pipelines/JsonFormatter.js.map +1 -1
- package/dist/formatters/pipelines/PlainTextFormatter.js +165 -19
- package/dist/formatters/pipelines/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/token/AlfredFormatter.js +15 -2
- package/dist/formatters/token/AlfredFormatter.js.map +1 -1
- package/dist/formatters/token/PlainTextFormatter.js +56 -18
- package/dist/formatters/token/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/viewer/PlainTextFormatter.js +8 -7
- package/dist/formatters/viewer/PlainTextFormatter.js.map +1 -1
- package/dist/graphql/queries.js +181 -0
- package/dist/graphql/queries.js.map +1 -1
- package/dist/index.js +67 -6
- package/dist/index.js.map +1 -1
- package/dist/services/BuildkiteClient.js +61 -1
- package/dist/services/BuildkiteClient.js.map +1 -1
- package/dist/services/CredentialManager.js +80 -10
- package/dist/services/CredentialManager.js.map +1 -1
- package/dist/ui/help.js +69 -0
- package/dist/ui/help.js.map +1 -0
- package/dist/ui/progress.js +356 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/reporter.js +111 -0
- package/dist/ui/reporter.js.map +1 -0
- package/dist/ui/responsive-table.js +183 -0
- package/dist/ui/responsive-table.js.map +1 -0
- package/dist/ui/spinner.js +20 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/symbols.js +46 -0
- package/dist/ui/symbols.js.map +1 -0
- package/dist/ui/table.js +32 -0
- package/dist/ui/table.js.map +1 -0
- package/dist/ui/theme.js +280 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/width.js +111 -0
- package/dist/ui/width.js.map +1 -0
- package/dist/utils/alfred.js +6 -0
- package/dist/utils/alfred.js.map +1 -0
- package/dist/utils/cli-error-handler.js +35 -20
- package/dist/utils/cli-error-handler.js.map +1 -1
- package/dist/utils/pagination.js +92 -0
- package/dist/utils/pagination.js.map +1 -0
- package/info.plist +51 -218
- package/package.json +24 -5
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Entry } from '@napi-rs/keyring';
|
|
2
1
|
import { logger } from './logger.js';
|
|
3
2
|
import { BuildkiteClient } from './BuildkiteClient.js';
|
|
4
3
|
import { BuildkiteRestClient } from './BuildkiteRestClient.js';
|
|
4
|
+
import { isRunningInAlfred } from '../utils/alfred.js';
|
|
5
|
+
import { Progress } from '../ui/progress.js';
|
|
5
6
|
const SERVICE_NAME = 'bktide';
|
|
6
7
|
const ACCOUNT_KEY = 'default';
|
|
7
8
|
/**
|
|
@@ -10,7 +11,28 @@ const ACCOUNT_KEY = 'default';
|
|
|
10
11
|
export class CredentialManager {
|
|
11
12
|
entry;
|
|
12
13
|
constructor(serviceName = SERVICE_NAME, accountName = ACCOUNT_KEY) {
|
|
13
|
-
|
|
14
|
+
// Do not load keyring when running under Alfred
|
|
15
|
+
if (!isRunningInAlfred()) {
|
|
16
|
+
// Lazy init via dynamic import to avoid resolving native module in Alfred context
|
|
17
|
+
// Note: constructor remains sync; actual instantiation is deferred in ensureEntry
|
|
18
|
+
void this.ensureEntry(serviceName, accountName);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async ensureEntry(serviceName = SERVICE_NAME, accountName = ACCOUNT_KEY) {
|
|
22
|
+
if (this.entry)
|
|
23
|
+
return this.entry;
|
|
24
|
+
if (isRunningInAlfred())
|
|
25
|
+
return undefined;
|
|
26
|
+
try {
|
|
27
|
+
const keyring = await import('@napi-rs/keyring');
|
|
28
|
+
this.entry = new keyring.Entry(serviceName, accountName);
|
|
29
|
+
return this.entry;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logger.debug('Failed to initialize keyring Entry, continuing without keychain', error);
|
|
33
|
+
this.entry = undefined;
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
14
36
|
}
|
|
15
37
|
/**
|
|
16
38
|
* Stores a token in the system keychain
|
|
@@ -18,8 +40,15 @@ export class CredentialManager {
|
|
|
18
40
|
* @returns true if token was successfully stored
|
|
19
41
|
*/
|
|
20
42
|
async saveToken(token) {
|
|
43
|
+
if (isRunningInAlfred()) {
|
|
44
|
+
// In Alfred path, we do not persist tokens programmatically
|
|
45
|
+
throw new Error('In Alfred, set token via Workflow Configuration (User Configuration).');
|
|
46
|
+
}
|
|
21
47
|
try {
|
|
22
|
-
await this.
|
|
48
|
+
const entry = await this.ensureEntry();
|
|
49
|
+
if (!entry)
|
|
50
|
+
throw new Error('Keyring unavailable');
|
|
51
|
+
await entry.setPassword(token);
|
|
23
52
|
logger.debug('Token saved to system keychain');
|
|
24
53
|
return true;
|
|
25
54
|
}
|
|
@@ -33,11 +62,18 @@ export class CredentialManager {
|
|
|
33
62
|
* @returns The stored token or undefined if not found
|
|
34
63
|
*/
|
|
35
64
|
async getToken() {
|
|
65
|
+
// Alfred: use env var only
|
|
66
|
+
if (isRunningInAlfred()) {
|
|
67
|
+
return process.env.BUILDKITE_API_TOKEN || process.env.BK_TOKEN || undefined;
|
|
68
|
+
}
|
|
36
69
|
try {
|
|
37
|
-
const
|
|
70
|
+
const entry = await this.ensureEntry();
|
|
71
|
+
if (!entry)
|
|
72
|
+
return undefined;
|
|
73
|
+
const token = entry.getPassword();
|
|
38
74
|
return token || undefined;
|
|
39
75
|
}
|
|
40
|
-
catch
|
|
76
|
+
catch {
|
|
41
77
|
return undefined;
|
|
42
78
|
}
|
|
43
79
|
}
|
|
@@ -46,8 +82,15 @@ export class CredentialManager {
|
|
|
46
82
|
* @returns true if token was successfully deleted
|
|
47
83
|
*/
|
|
48
84
|
async deleteToken() {
|
|
85
|
+
if (isRunningInAlfred()) {
|
|
86
|
+
// Nothing to delete in keyring under Alfred
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
49
89
|
try {
|
|
50
|
-
await this.
|
|
90
|
+
const entry = await this.ensureEntry();
|
|
91
|
+
if (!entry)
|
|
92
|
+
return false;
|
|
93
|
+
await entry.deletePassword();
|
|
51
94
|
logger.debug('Token deleted from system keychain');
|
|
52
95
|
return true;
|
|
53
96
|
}
|
|
@@ -67,9 +110,10 @@ export class CredentialManager {
|
|
|
67
110
|
/**
|
|
68
111
|
* Validates if a token is valid by making test API calls to both GraphQL and REST APIs
|
|
69
112
|
* @param token Optional token to validate. If not provided, will use the stored token.
|
|
113
|
+
* @param options Optional configuration for progress display
|
|
70
114
|
* @returns Object containing validation status for both GraphQL and REST APIs
|
|
71
115
|
*/
|
|
72
|
-
async validateToken(token) {
|
|
116
|
+
async validateToken(token, options) {
|
|
73
117
|
try {
|
|
74
118
|
// If no token provided, try to get the stored one
|
|
75
119
|
const tokenToValidate = token || await this.getToken();
|
|
@@ -103,6 +147,16 @@ export class CredentialManager {
|
|
|
103
147
|
}
|
|
104
148
|
const organizations = {};
|
|
105
149
|
let allValid = true;
|
|
150
|
+
// Determine if we should show progress
|
|
151
|
+
const showProgress = options?.showProgress !== false &&
|
|
152
|
+
!isRunningInAlfred() &&
|
|
153
|
+
orgSlugs.length > 0;
|
|
154
|
+
const progress = showProgress ? Progress.bar({
|
|
155
|
+
total: orgSlugs.length * 3,
|
|
156
|
+
label: 'Validating token access',
|
|
157
|
+
format: options?.format
|
|
158
|
+
}) : null;
|
|
159
|
+
let checkCount = 0;
|
|
106
160
|
// Validate each organization
|
|
107
161
|
for (const orgSlug of orgSlugs) {
|
|
108
162
|
const orgStatus = {
|
|
@@ -110,8 +164,11 @@ export class CredentialManager {
|
|
|
110
164
|
builds: false,
|
|
111
165
|
organizations: false
|
|
112
166
|
};
|
|
167
|
+
// Check GraphQL access
|
|
168
|
+
if (progress) {
|
|
169
|
+
progress.update(checkCount++, `Checking GraphQL access for ${orgSlug}`);
|
|
170
|
+
}
|
|
113
171
|
try {
|
|
114
|
-
// Check GraphQL access
|
|
115
172
|
await graphqlClient.getViewer();
|
|
116
173
|
orgStatus.graphql = true;
|
|
117
174
|
}
|
|
@@ -119,8 +176,11 @@ export class CredentialManager {
|
|
|
119
176
|
logger.debug(`GraphQL validation failed for organization ${orgSlug}`, error);
|
|
120
177
|
allValid = false;
|
|
121
178
|
}
|
|
179
|
+
// Check build access
|
|
180
|
+
if (progress) {
|
|
181
|
+
progress.update(checkCount++, `Checking build access for ${orgSlug}`);
|
|
182
|
+
}
|
|
122
183
|
try {
|
|
123
|
-
// Check build access
|
|
124
184
|
await restClient.hasBuildAccess(orgSlug);
|
|
125
185
|
orgStatus.builds = true;
|
|
126
186
|
}
|
|
@@ -128,8 +188,11 @@ export class CredentialManager {
|
|
|
128
188
|
logger.debug(`Build access validation failed for organization ${orgSlug}`, error);
|
|
129
189
|
allValid = false;
|
|
130
190
|
}
|
|
191
|
+
// Check organization access
|
|
192
|
+
if (progress) {
|
|
193
|
+
progress.update(checkCount++, `Checking organization access for ${orgSlug}`);
|
|
194
|
+
}
|
|
131
195
|
try {
|
|
132
|
-
// Check organization access
|
|
133
196
|
await restClient.hasOrganizationAccess(orgSlug);
|
|
134
197
|
orgStatus.organizations = true;
|
|
135
198
|
}
|
|
@@ -139,6 +202,13 @@ export class CredentialManager {
|
|
|
139
202
|
}
|
|
140
203
|
organizations[orgSlug] = orgStatus;
|
|
141
204
|
}
|
|
205
|
+
// Complete the progress bar
|
|
206
|
+
if (progress) {
|
|
207
|
+
const successCount = Object.values(organizations)
|
|
208
|
+
.filter(org => org.graphql && org.builds && org.organizations)
|
|
209
|
+
.length;
|
|
210
|
+
progress.complete(`✓ Validated ${orgSlugs.length} organizations (${successCount} fully accessible)`);
|
|
211
|
+
}
|
|
142
212
|
return {
|
|
143
213
|
valid: allValid,
|
|
144
214
|
canListOrganizations: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CredentialManager.js","sourceRoot":"/","sources":["services/CredentialManager.ts"],"names":[],"mappings":"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","sourcesContent":["import { Entry } from '@napi-rs/keyring';\nimport { logger } from './logger.js';\nimport { BuildkiteClient } from './BuildkiteClient.js';\nimport { BuildkiteRestClient } from './BuildkiteRestClient.js';\nimport { TokenValidationStatus, OrganizationValidationStatus } from '../types/credentials.js';\n\nconst SERVICE_NAME = 'bktide';\nconst ACCOUNT_KEY = 'default';\n\n/**\n * Manages secure storage of credentials using the system's keychain\n */\nexport class CredentialManager {\n private entry: Entry;\n\n constructor(serviceName = SERVICE_NAME, accountName = ACCOUNT_KEY) {\n this.entry = new Entry(serviceName, accountName);\n }\n\n /**\n * Stores a token in the system keychain\n * @param token The Buildkite API token to store\n * @returns true if token was successfully stored\n */\n async saveToken(token: string): Promise<boolean> {\n try {\n await this.entry.setPassword(token);\n logger.debug('Token saved to system keychain');\n return true;\n } catch (error) {\n logger.error('Failed to save token to system keychain', error);\n return false;\n }\n }\n\n /**\n * Retrieves the stored token from the system keychain\n * @returns The stored token or undefined if not found\n */\n async getToken(): Promise<string | undefined> {\n try {\n const token = this.entry.getPassword();\n return token || undefined;\n } catch (error) {\n return undefined;\n }\n }\n\n /**\n * Deletes the stored token from the system keychain\n * @returns true if token was successfully deleted\n */\n async deleteToken(): Promise<boolean> {\n try {\n await this.entry.deletePassword();\n logger.debug('Token deleted from system keychain');\n return true;\n } catch (error) {\n logger.error('Failed to delete token from system keychain', error);\n return false;\n }\n }\n\n /**\n * Checks if a token exists in the system keychain\n * @returns true if a token exists\n */\n async hasToken(): Promise<boolean> {\n const token = await this.getToken();\n return !!token;\n }\n\n /**\n * Validates if a token is valid by making test API calls to both GraphQL and REST APIs\n * @param token Optional token to validate. If not provided, will use the stored token.\n * @returns Object containing validation status for both GraphQL and REST APIs\n */\n async validateToken(token?: string): Promise<TokenValidationStatus> {\n try {\n // If no token provided, try to get the stored one\n const tokenToValidate = token || await this.getToken();\n if (!tokenToValidate) {\n logger.debug('No token provided for validation');\n return { \n valid: false, \n canListOrganizations: false,\n organizations: {} \n };\n }\n\n // Create clients with the token\n const graphqlClient = new BuildkiteClient(tokenToValidate, { debug: false, caching: false });\n const restClient = new BuildkiteRestClient(tokenToValidate, { debug: false });\n \n // First check if we can list organizations\n let orgSlugs: string[] = [];\n try {\n orgSlugs = await graphqlClient.getOrganizations().then(orgs => orgs.map(org => org.slug));\n logger.debug('Successfully retrieved organization slugs');\n } catch (error) {\n logger.debug('Failed to retrieve organization slugs', {\n error: error instanceof Error ? error.message : String(error),\n cause: error instanceof Error && error.cause ? error.cause : undefined\n });\n return { \n valid: false, \n canListOrganizations: false,\n organizations: {} \n };\n }\n\n const organizations: Record<string, OrganizationValidationStatus> = {};\n let allValid = true;\n\n // Validate each organization\n for (const orgSlug of orgSlugs) {\n const orgStatus: OrganizationValidationStatus = {\n graphql: false,\n builds: false,\n organizations: false\n };\n\n try {\n // Check GraphQL access\n await graphqlClient.getViewer();\n orgStatus.graphql = true;\n } catch (error) {\n logger.debug(`GraphQL validation failed for organization ${orgSlug}`, error);\n allValid = false;\n }\n\n try {\n // Check build access\n await restClient.hasBuildAccess(orgSlug);\n orgStatus.builds = true;\n } catch (error) {\n logger.debug(`Build access validation failed for organization ${orgSlug}`, error);\n allValid = false;\n }\n\n try {\n // Check organization access\n await restClient.hasOrganizationAccess(orgSlug);\n orgStatus.organizations = true;\n } catch (error) {\n logger.debug(`Organization access validation failed for organization ${orgSlug}`, error);\n allValid = false;\n }\n\n organizations[orgSlug] = orgStatus;\n }\n\n return {\n valid: allValid,\n canListOrganizations: true,\n organizations\n };\n } catch (error) {\n logger.debug('Token validation failed', error);\n return { \n valid: false, \n canListOrganizations: false,\n organizations: {} \n };\n }\n }\n}"]}
|
|
1
|
+
{"version":3,"file":"CredentialManager.js","sourceRoot":"/","sources":["services/CredentialManager.ts"],"names":[],"mappings":"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","sourcesContent":["// Avoid importing native module eagerly; import lazily when needed\nimport type { Entry } from '@napi-rs/keyring';\nimport { logger } from './logger.js';\nimport { BuildkiteClient } from './BuildkiteClient.js';\nimport { BuildkiteRestClient } from './BuildkiteRestClient.js';\nimport { TokenValidationStatus, OrganizationValidationStatus } from '../types/credentials.js';\nimport { isRunningInAlfred } from '../utils/alfred.js';\nimport { Progress } from '../ui/progress.js';\n\nconst SERVICE_NAME = 'bktide';\nconst ACCOUNT_KEY = 'default';\n\n/**\n * Manages secure storage of credentials using the system's keychain\n */\nexport class CredentialManager {\n private entry: Entry | undefined;\n\n constructor(serviceName = SERVICE_NAME, accountName = ACCOUNT_KEY) {\n // Do not load keyring when running under Alfred\n if (!isRunningInAlfred()) {\n // Lazy init via dynamic import to avoid resolving native module in Alfred context\n // Note: constructor remains sync; actual instantiation is deferred in ensureEntry\n void this.ensureEntry(serviceName, accountName);\n }\n }\n\n private async ensureEntry(serviceName = SERVICE_NAME, accountName = ACCOUNT_KEY): Promise<Entry | undefined> {\n if (this.entry) return this.entry;\n if (isRunningInAlfred()) return undefined;\n try {\n const keyring = await import('@napi-rs/keyring');\n this.entry = new keyring.Entry(serviceName, accountName) as Entry;\n return this.entry;\n } catch (error) {\n logger.debug('Failed to initialize keyring Entry, continuing without keychain', error);\n this.entry = undefined;\n return undefined;\n }\n }\n\n /**\n * Stores a token in the system keychain\n * @param token The Buildkite API token to store\n * @returns true if token was successfully stored\n */\n async saveToken(token: string): Promise<boolean> {\n if (isRunningInAlfred()) {\n // In Alfred path, we do not persist tokens programmatically\n throw new Error('In Alfred, set token via Workflow Configuration (User Configuration).');\n }\n try {\n const entry = await this.ensureEntry();\n if (!entry) throw new Error('Keyring unavailable');\n await entry.setPassword(token);\n logger.debug('Token saved to system keychain');\n return true;\n } catch (error) {\n logger.error('Failed to save token to system keychain', error);\n return false;\n }\n }\n\n /**\n * Retrieves the stored token from the system keychain\n * @returns The stored token or undefined if not found\n */\n async getToken(): Promise<string | undefined> {\n // Alfred: use env var only\n if (isRunningInAlfred()) {\n return process.env.BUILDKITE_API_TOKEN || process.env.BK_TOKEN || undefined;\n }\n try {\n const entry = await this.ensureEntry();\n if (!entry) return undefined;\n const token = entry.getPassword();\n return token || undefined;\n } catch {\n return undefined;\n }\n }\n\n /**\n * Deletes the stored token from the system keychain\n * @returns true if token was successfully deleted\n */\n async deleteToken(): Promise<boolean> {\n if (isRunningInAlfred()) {\n // Nothing to delete in keyring under Alfred\n return true;\n }\n try {\n const entry = await this.ensureEntry();\n if (!entry) return false;\n await entry.deletePassword();\n logger.debug('Token deleted from system keychain');\n return true;\n } catch (error) {\n logger.error('Failed to delete token from system keychain', error);\n return false;\n }\n }\n\n /**\n * Checks if a token exists in the system keychain\n * @returns true if a token exists\n */\n async hasToken(): Promise<boolean> {\n const token = await this.getToken();\n return !!token;\n }\n\n /**\n * Validates if a token is valid by making test API calls to both GraphQL and REST APIs\n * @param token Optional token to validate. If not provided, will use the stored token.\n * @param options Optional configuration for progress display\n * @returns Object containing validation status for both GraphQL and REST APIs\n */\n async validateToken(token?: string, options?: { format?: string; showProgress?: boolean }): Promise<TokenValidationStatus> {\n try {\n // If no token provided, try to get the stored one\n const tokenToValidate = token || await this.getToken();\n if (!tokenToValidate) {\n logger.debug('No token provided for validation');\n return { \n valid: false, \n canListOrganizations: false,\n organizations: {} \n };\n }\n\n // Create clients with the token\n const graphqlClient = new BuildkiteClient(tokenToValidate, { debug: false, caching: false });\n const restClient = new BuildkiteRestClient(tokenToValidate, { debug: false });\n \n // First check if we can list organizations\n let orgSlugs: string[] = [];\n try {\n orgSlugs = await graphqlClient.getOrganizations().then(orgs => orgs.map(org => org.slug));\n logger.debug('Successfully retrieved organization slugs');\n } catch (error) {\n logger.debug('Failed to retrieve organization slugs', {\n error: error instanceof Error ? error.message : String(error),\n cause: error instanceof Error && error.cause ? error.cause : undefined\n });\n return { \n valid: false, \n canListOrganizations: false,\n organizations: {} \n };\n }\n\n const organizations: Record<string, OrganizationValidationStatus> = {};\n let allValid = true;\n\n // Determine if we should show progress\n const showProgress = options?.showProgress !== false && \n !isRunningInAlfred() && \n orgSlugs.length > 0;\n \n const progress = showProgress ? Progress.bar({\n total: orgSlugs.length * 3,\n label: 'Validating token access',\n format: options?.format\n }) : null;\n\n let checkCount = 0;\n\n // Validate each organization\n for (const orgSlug of orgSlugs) {\n const orgStatus: OrganizationValidationStatus = {\n graphql: false,\n builds: false,\n organizations: false\n };\n\n // Check GraphQL access\n if (progress) {\n progress.update(checkCount++, `Checking GraphQL access for ${orgSlug}`);\n }\n try {\n await graphqlClient.getViewer();\n orgStatus.graphql = true;\n } catch (error) {\n logger.debug(`GraphQL validation failed for organization ${orgSlug}`, error);\n allValid = false;\n }\n\n // Check build access\n if (progress) {\n progress.update(checkCount++, `Checking build access for ${orgSlug}`);\n }\n try {\n await restClient.hasBuildAccess(orgSlug);\n orgStatus.builds = true;\n } catch (error) {\n logger.debug(`Build access validation failed for organization ${orgSlug}`, error);\n allValid = false;\n }\n\n // Check organization access\n if (progress) {\n progress.update(checkCount++, `Checking organization access for ${orgSlug}`);\n }\n try {\n await restClient.hasOrganizationAccess(orgSlug);\n orgStatus.organizations = true;\n } catch (error) {\n logger.debug(`Organization access validation failed for organization ${orgSlug}`, error);\n allValid = false;\n }\n\n organizations[orgSlug] = orgStatus;\n }\n\n // Complete the progress bar\n if (progress) {\n const successCount = Object.values(organizations)\n .filter(org => org.graphql && org.builds && org.organizations)\n .length;\n progress.complete(`✓ Validated ${orgSlugs.length} organizations (${successCount} fully accessible)`);\n }\n\n return {\n valid: allValid,\n canListOrganizations: true,\n organizations\n };\n } catch (error) {\n logger.debug('Token validation failed', error);\n return { \n valid: false, \n canListOrganizations: false,\n organizations: {} \n };\n }\n }\n}"]}
|
package/dist/ui/help.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Help formatter for Commander with width-aware text wrapping
|
|
3
|
+
*/
|
|
4
|
+
import { Help } from 'commander';
|
|
5
|
+
import { termWidth, wrapText } from './width.js';
|
|
6
|
+
export class WidthAwareHelp extends Help {
|
|
7
|
+
/**
|
|
8
|
+
* Get the terminal width with a max limit for readability
|
|
9
|
+
*/
|
|
10
|
+
getHelpWidth() {
|
|
11
|
+
// Cap at 100 chars for readability, even on ultra-wide terminals
|
|
12
|
+
return Math.min(termWidth(), 100);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Wrap text to fit terminal width
|
|
16
|
+
*/
|
|
17
|
+
wrapLine(text, indent = 0) {
|
|
18
|
+
const width = this.getHelpWidth();
|
|
19
|
+
const effectiveWidth = Math.max(40, width - indent);
|
|
20
|
+
const lines = wrapText(text, effectiveWidth);
|
|
21
|
+
const indentStr = ' '.repeat(indent);
|
|
22
|
+
return lines.map((line, i) => i === 0 ? line : indentStr + line).join('\n');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Override formatHelp to apply width-aware formatting
|
|
26
|
+
*/
|
|
27
|
+
formatHelp(cmd, helper) {
|
|
28
|
+
const width = this.getHelpWidth();
|
|
29
|
+
// Get the default help text
|
|
30
|
+
let helpText = super.formatHelp(cmd, helper);
|
|
31
|
+
// Process each line to apply wrapping
|
|
32
|
+
const lines = helpText.split('\n');
|
|
33
|
+
const processedLines = [];
|
|
34
|
+
for (const line of lines) {
|
|
35
|
+
// Skip empty lines
|
|
36
|
+
if (line.trim() === '') {
|
|
37
|
+
processedLines.push(line);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// Detect option/command lines (they start with spaces and contain two or more spaces for alignment)
|
|
41
|
+
const optionMatch = line.match(/^(\s+)(\S+)\s{2,}(.*)$/);
|
|
42
|
+
if (optionMatch) {
|
|
43
|
+
const [, indent, option, description] = optionMatch;
|
|
44
|
+
const optionIndent = indent.length;
|
|
45
|
+
const descIndent = optionIndent + option.length + 2;
|
|
46
|
+
// If the line is too long, wrap the description
|
|
47
|
+
if (line.length > width) {
|
|
48
|
+
const wrappedDesc = this.wrapLine(description, descIndent);
|
|
49
|
+
processedLines.push(`${indent}${option} ${wrappedDesc}`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
processedLines.push(line);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (line.length > width) {
|
|
56
|
+
// For other long lines (like descriptions), wrap them
|
|
57
|
+
const leadingSpaces = line.match(/^(\s*)/)?.[1] || '';
|
|
58
|
+
const content = line.trim();
|
|
59
|
+
const wrapped = this.wrapLine(content, leadingSpaces.length);
|
|
60
|
+
processedLines.push(leadingSpaces + wrapped);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
processedLines.push(line);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return processedLines.join('\n');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=help.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.js","sourceRoot":"/","sources":["ui/help.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Custom Help formatter for Commander with width-aware text wrapping\n */\n\nimport { Help } from 'commander';\nimport { termWidth, wrapText } from './width.js';\n\nexport class WidthAwareHelp extends Help {\n /**\n * Get the terminal width with a max limit for readability\n */\n private getHelpWidth(): number {\n // Cap at 100 chars for readability, even on ultra-wide terminals\n return Math.min(termWidth(), 100);\n }\n\n /**\n * Wrap text to fit terminal width\n */\n private wrapLine(text: string, indent = 0): string {\n const width = this.getHelpWidth();\n const effectiveWidth = Math.max(40, width - indent);\n const lines = wrapText(text, effectiveWidth);\n const indentStr = ' '.repeat(indent);\n return lines.map((line, i) => i === 0 ? line : indentStr + line).join('\\n');\n }\n\n /**\n * Override formatHelp to apply width-aware formatting\n */\n formatHelp(cmd: any, helper: Help): string {\n const width = this.getHelpWidth();\n \n // Get the default help text\n let helpText = super.formatHelp(cmd, helper);\n \n // Process each line to apply wrapping\n const lines = helpText.split('\\n');\n const processedLines: string[] = [];\n \n for (const line of lines) {\n // Skip empty lines\n if (line.trim() === '') {\n processedLines.push(line);\n continue;\n }\n \n // Detect option/command lines (they start with spaces and contain two or more spaces for alignment)\n const optionMatch = line.match(/^(\\s+)(\\S+)\\s{2,}(.*)$/);\n if (optionMatch) {\n const [, indent, option, description] = optionMatch;\n const optionIndent = indent.length;\n const descIndent = optionIndent + option.length + 2;\n \n // If the line is too long, wrap the description\n if (line.length > width) {\n const wrappedDesc = this.wrapLine(description, descIndent);\n processedLines.push(`${indent}${option} ${wrappedDesc}`);\n } else {\n processedLines.push(line);\n }\n } else if (line.length > width) {\n // For other long lines (like descriptions), wrap them\n const leadingSpaces = line.match(/^(\\s*)/)?.[1] || '';\n const content = line.trim();\n const wrapped = this.wrapLine(content, leadingSpaces.length);\n processedLines.push(leadingSpaces + wrapped);\n } else {\n processedLines.push(line);\n }\n }\n \n return processedLines.join('\\n');\n }\n\n\n}\n"]}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified progress indicator system
|
|
3
|
+
* Provides both determinate (bar) and indeterminate (spinner) progress indicators
|
|
4
|
+
*/
|
|
5
|
+
import { COLORS, SYMBOLS } from './theme.js';
|
|
6
|
+
import { termWidth, truncate } from './width.js';
|
|
7
|
+
/**
|
|
8
|
+
* Check if output format is machine-readable
|
|
9
|
+
*/
|
|
10
|
+
function isMachineFormat(format) {
|
|
11
|
+
const f = (format || '').toLowerCase();
|
|
12
|
+
return f === 'json' || f === 'alfred';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Check if we should show progress indicators
|
|
16
|
+
*/
|
|
17
|
+
function shouldShowProgress(format) {
|
|
18
|
+
// Don't show in non-TTY environments
|
|
19
|
+
if (!process.stderr.isTTY)
|
|
20
|
+
return false;
|
|
21
|
+
// Don't show for machine formats
|
|
22
|
+
if (format && isMachineFormat(format))
|
|
23
|
+
return false;
|
|
24
|
+
// Don't show in CI environments
|
|
25
|
+
if (process.env.CI)
|
|
26
|
+
return false;
|
|
27
|
+
// Don't show if NO_COLOR is set (indicates non-interactive)
|
|
28
|
+
if (process.env.NO_COLOR)
|
|
29
|
+
return false;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Spinner for indeterminate progress
|
|
34
|
+
* Shows animated spinner with updating label
|
|
35
|
+
*/
|
|
36
|
+
class Spinner {
|
|
37
|
+
format;
|
|
38
|
+
interval;
|
|
39
|
+
frame = 0;
|
|
40
|
+
frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
41
|
+
label = '';
|
|
42
|
+
lastLineLength = 0;
|
|
43
|
+
isActive = false;
|
|
44
|
+
stream = process.stderr;
|
|
45
|
+
constructor(label, format) {
|
|
46
|
+
this.format = format;
|
|
47
|
+
if (label)
|
|
48
|
+
this.label = label;
|
|
49
|
+
}
|
|
50
|
+
shouldShow() {
|
|
51
|
+
return shouldShowProgress(this.format);
|
|
52
|
+
}
|
|
53
|
+
start() {
|
|
54
|
+
if (!this.shouldShow() || this.isActive)
|
|
55
|
+
return;
|
|
56
|
+
this.isActive = true;
|
|
57
|
+
this.interval = setInterval(() => {
|
|
58
|
+
this.render();
|
|
59
|
+
this.frame = (this.frame + 1) % this.frames.length;
|
|
60
|
+
}, 80);
|
|
61
|
+
}
|
|
62
|
+
update(_value, label) {
|
|
63
|
+
// For spinner, we only care about label updates
|
|
64
|
+
if (label !== undefined) {
|
|
65
|
+
this.label = label;
|
|
66
|
+
}
|
|
67
|
+
if (!this.isActive) {
|
|
68
|
+
this.start();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
stop() {
|
|
72
|
+
if (!this.isActive)
|
|
73
|
+
return;
|
|
74
|
+
if (this.interval) {
|
|
75
|
+
clearInterval(this.interval);
|
|
76
|
+
this.interval = undefined;
|
|
77
|
+
}
|
|
78
|
+
this.clear();
|
|
79
|
+
this.isActive = false;
|
|
80
|
+
}
|
|
81
|
+
complete(message) {
|
|
82
|
+
this.stop();
|
|
83
|
+
if (message && this.shouldShow()) {
|
|
84
|
+
this.stream.write(COLORS.success(`${SYMBOLS.success} ${message}\n`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
fail(message) {
|
|
88
|
+
this.stop();
|
|
89
|
+
if (message && this.shouldShow()) {
|
|
90
|
+
this.stream.write(COLORS.error(`${SYMBOLS.error} ${message}\n`));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
clear() {
|
|
94
|
+
if (!this.stream.isTTY)
|
|
95
|
+
return;
|
|
96
|
+
this.stream.write('\r' + ' '.repeat(this.lastLineLength) + '\r');
|
|
97
|
+
}
|
|
98
|
+
render() {
|
|
99
|
+
if (!this.shouldShow() || !this.isActive)
|
|
100
|
+
return;
|
|
101
|
+
const spinner = COLORS.info(this.frames[this.frame]);
|
|
102
|
+
const line = this.label ? `${spinner} ${this.label}` : spinner;
|
|
103
|
+
this.clear();
|
|
104
|
+
this.stream.write(line);
|
|
105
|
+
this.lastLineLength = line.length;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Progress bar for determinate progress
|
|
110
|
+
* Shows percentage and optional counts
|
|
111
|
+
*/
|
|
112
|
+
class Bar {
|
|
113
|
+
current = 0;
|
|
114
|
+
total;
|
|
115
|
+
barWidth = 30;
|
|
116
|
+
label;
|
|
117
|
+
lastLineLength = 0;
|
|
118
|
+
isActive = false;
|
|
119
|
+
stream = process.stderr;
|
|
120
|
+
format;
|
|
121
|
+
constructor(options) {
|
|
122
|
+
this.total = options.total || 100;
|
|
123
|
+
this.label = options.label;
|
|
124
|
+
this.barWidth = options.barWidth || 30;
|
|
125
|
+
this.format = options.format;
|
|
126
|
+
}
|
|
127
|
+
shouldShow() {
|
|
128
|
+
return shouldShowProgress(this.format);
|
|
129
|
+
}
|
|
130
|
+
start() {
|
|
131
|
+
if (!this.shouldShow() || this.isActive)
|
|
132
|
+
return;
|
|
133
|
+
this.isActive = true;
|
|
134
|
+
this.render();
|
|
135
|
+
}
|
|
136
|
+
update(value, label) {
|
|
137
|
+
if (!this.isActive) {
|
|
138
|
+
this.start();
|
|
139
|
+
}
|
|
140
|
+
// For bar, value should be a number
|
|
141
|
+
if (typeof value === 'number') {
|
|
142
|
+
this.current = Math.min(value, this.total);
|
|
143
|
+
}
|
|
144
|
+
if (label !== undefined) {
|
|
145
|
+
this.label = label;
|
|
146
|
+
}
|
|
147
|
+
if (this.shouldShow() && this.isActive) {
|
|
148
|
+
this.render();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
stop() {
|
|
152
|
+
if (!this.isActive)
|
|
153
|
+
return;
|
|
154
|
+
this.clear();
|
|
155
|
+
this.isActive = false;
|
|
156
|
+
}
|
|
157
|
+
complete(message) {
|
|
158
|
+
if (!this.shouldShow())
|
|
159
|
+
return;
|
|
160
|
+
this.current = this.total;
|
|
161
|
+
this.render();
|
|
162
|
+
this.clear();
|
|
163
|
+
if (message) {
|
|
164
|
+
this.stream.write(COLORS.success(`${SYMBOLS.success} ${message}\n`));
|
|
165
|
+
}
|
|
166
|
+
this.isActive = false;
|
|
167
|
+
}
|
|
168
|
+
fail(message) {
|
|
169
|
+
this.stop();
|
|
170
|
+
if (message && this.shouldShow()) {
|
|
171
|
+
this.stream.write(COLORS.error(`${SYMBOLS.error} ${message}\n`));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
clear() {
|
|
175
|
+
if (!this.stream.isTTY)
|
|
176
|
+
return;
|
|
177
|
+
this.stream.write('\r' + ' '.repeat(this.lastLineLength) + '\r');
|
|
178
|
+
}
|
|
179
|
+
render() {
|
|
180
|
+
if (!this.shouldShow() || !this.isActive)
|
|
181
|
+
return;
|
|
182
|
+
const percentage = Math.round((this.current / this.total) * 100);
|
|
183
|
+
const filledLength = Math.round((this.current / this.total) * this.barWidth);
|
|
184
|
+
const emptyLength = this.barWidth - filledLength;
|
|
185
|
+
const filled = '█'.repeat(filledLength);
|
|
186
|
+
const empty = '░'.repeat(emptyLength);
|
|
187
|
+
const bar = `[${filled}${empty}]`;
|
|
188
|
+
const parts = [];
|
|
189
|
+
if (this.label) {
|
|
190
|
+
const maxLabelWidth = Math.max(20, termWidth() - this.barWidth - 20);
|
|
191
|
+
parts.push(truncate(this.label, maxLabelWidth));
|
|
192
|
+
}
|
|
193
|
+
parts.push(bar);
|
|
194
|
+
parts.push(`${percentage}%`);
|
|
195
|
+
parts.push(`(${this.current}/${this.total})`);
|
|
196
|
+
const line = parts.join(' ');
|
|
197
|
+
this.clear();
|
|
198
|
+
this.stream.write(line);
|
|
199
|
+
this.lastLineLength = line.length;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* No-op progress for non-interactive environments
|
|
204
|
+
*/
|
|
205
|
+
class NoOpProgress {
|
|
206
|
+
update() { }
|
|
207
|
+
stop() { }
|
|
208
|
+
complete() { }
|
|
209
|
+
fail() { }
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Main Progress API - factory methods for creating progress indicators
|
|
213
|
+
*/
|
|
214
|
+
export class Progress {
|
|
215
|
+
/**
|
|
216
|
+
* Create a spinner (indeterminate progress)
|
|
217
|
+
* Use for operations of unknown duration
|
|
218
|
+
*/
|
|
219
|
+
static spinner(label, options) {
|
|
220
|
+
if (!shouldShowProgress(options?.format)) {
|
|
221
|
+
return new NoOpProgress();
|
|
222
|
+
}
|
|
223
|
+
const spinner = new Spinner(label, options?.format);
|
|
224
|
+
if (label) {
|
|
225
|
+
spinner.start();
|
|
226
|
+
}
|
|
227
|
+
return spinner;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Create a progress bar (determinate progress)
|
|
231
|
+
* Use when you know the total number of items
|
|
232
|
+
*/
|
|
233
|
+
static bar(options) {
|
|
234
|
+
if (!shouldShowProgress(options.format)) {
|
|
235
|
+
return new NoOpProgress();
|
|
236
|
+
}
|
|
237
|
+
const bar = new Bar(options);
|
|
238
|
+
bar.start();
|
|
239
|
+
return bar;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Smart factory that creates appropriate progress type
|
|
243
|
+
* Creates bar if total is provided, spinner otherwise
|
|
244
|
+
*/
|
|
245
|
+
static create(options) {
|
|
246
|
+
if (!options) {
|
|
247
|
+
return Progress.spinner();
|
|
248
|
+
}
|
|
249
|
+
if (options.total !== undefined && options.total > 0) {
|
|
250
|
+
return Progress.bar(options);
|
|
251
|
+
}
|
|
252
|
+
return Progress.spinner(options.label, options);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Helper for async operations with progress tracking
|
|
257
|
+
*/
|
|
258
|
+
export async function withProgress(operation, options) {
|
|
259
|
+
const progress = Progress.create(options);
|
|
260
|
+
try {
|
|
261
|
+
const result = await operation(progress);
|
|
262
|
+
progress.complete(options?.successMessage);
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
progress.fail(error instanceof Error ? error.message : 'Operation failed');
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// Legacy API - for backward compatibility during migration
|
|
272
|
+
// ============================================================================
|
|
273
|
+
/**
|
|
274
|
+
* @deprecated Use Progress.bar() instead
|
|
275
|
+
*/
|
|
276
|
+
export class ProgressBar {
|
|
277
|
+
progress;
|
|
278
|
+
constructor(options) {
|
|
279
|
+
this.progress = Progress.bar({
|
|
280
|
+
total: options.total,
|
|
281
|
+
label: options.label,
|
|
282
|
+
format: options.format
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
start() {
|
|
286
|
+
// Already started in constructor
|
|
287
|
+
}
|
|
288
|
+
update(value, label) {
|
|
289
|
+
this.progress.update(value, label);
|
|
290
|
+
}
|
|
291
|
+
stop() {
|
|
292
|
+
this.progress.stop();
|
|
293
|
+
}
|
|
294
|
+
complete(message) {
|
|
295
|
+
this.progress.complete(message);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* @deprecated Use Progress.spinner() instead
|
|
300
|
+
*/
|
|
301
|
+
export class IndeterminateProgress {
|
|
302
|
+
progress;
|
|
303
|
+
constructor(label, format) {
|
|
304
|
+
this.progress = Progress.spinner(label, { format });
|
|
305
|
+
}
|
|
306
|
+
start() {
|
|
307
|
+
// Already started if label provided
|
|
308
|
+
}
|
|
309
|
+
updateLabel(label) {
|
|
310
|
+
this.progress.update(label, label);
|
|
311
|
+
}
|
|
312
|
+
stop() {
|
|
313
|
+
this.progress.stop();
|
|
314
|
+
}
|
|
315
|
+
complete(message) {
|
|
316
|
+
this.progress.complete(message);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* @deprecated Use withProgress() instead
|
|
321
|
+
*/
|
|
322
|
+
export async function withCountedProgress(items, operation, options = {}) {
|
|
323
|
+
if (!items || items.length === 0) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const progress = Progress.bar({
|
|
327
|
+
total: items.length,
|
|
328
|
+
label: options.label || 'Processing',
|
|
329
|
+
format: options.format
|
|
330
|
+
});
|
|
331
|
+
try {
|
|
332
|
+
for (let i = 0; i < items.length; i++) {
|
|
333
|
+
const label = options.itemLabel ?
|
|
334
|
+
options.itemLabel(items[i], i) :
|
|
335
|
+
`Processing item ${i + 1}/${items.length}`;
|
|
336
|
+
progress.update(i, label);
|
|
337
|
+
await operation(items[i], i);
|
|
338
|
+
}
|
|
339
|
+
progress.update(items.length, 'Complete');
|
|
340
|
+
const completeMessage = options.onComplete ?
|
|
341
|
+
options.onComplete(items.length) :
|
|
342
|
+
`Processed ${items.length} items`;
|
|
343
|
+
progress.complete(completeMessage);
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
progress.stop();
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* @deprecated Use withProgress() instead
|
|
352
|
+
*/
|
|
353
|
+
export async function withIndeterminateProgress(operation, label, format, successMessage) {
|
|
354
|
+
return withProgress(operation, { label, format, successMessage });
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=progress.js.map
|