@sanlam-fintech-digital/mfe-platform-cli 0.0.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.
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getStoredConfigSource = getStoredConfigSource;
7
+ exports.createInitCommand = createInitCommand;
8
+ const commander_1 = require("commander");
9
+ const loader_js_1 = require("../config/loader.js");
10
+ const manager_js_1 = require("../npmrc/manager.js");
11
+ const orchestrator_js_1 = require("../auth/orchestrator.js");
12
+ const router_js_1 = require("../feed/router.js");
13
+ const manager_js_2 = require("../nuget/manager.js");
14
+ const promises_1 = __importDefault(require("fs/promises"));
15
+ const path_1 = __importDefault(require("path"));
16
+ const os_1 = __importDefault(require("os"));
17
+ const chalk_1 = __importDefault(require("chalk"));
18
+ /**
19
+ * Store location for tracking initial config source
20
+ */
21
+ const CONFIG_STORE_PATH = path_1.default.join(os_1.default.homedir(), '.sft-mfe-platform', 'config-source.json');
22
+ /**
23
+ * Execute init command
24
+ */
25
+ async function executeInit(options) {
26
+ try {
27
+ const isDryRun = options.dryRun === true;
28
+ console.log(chalk_1.default.bold.cyan(`\nšŸš€ Initializing Sanlam Platform CLI${isDryRun ? ' (Dry Run)' : ''}\n`));
29
+ // Step 1: Load and validate configuration
30
+ console.log(chalk_1.default.blue('Step 1: Loading configuration...'));
31
+ const config = await (0, loader_js_1.loadConfig)(options.config, buildConfigAuthOptions(options));
32
+ // Flatten all registries for display and processing
33
+ const allRegistries = flattenRegistries(config);
34
+ console.log(chalk_1.default.green(`āœ“ Loaded ${allRegistries.length} registry/registries\n`));
35
+ // Step 2: Analyze feed configuration
36
+ console.log(chalk_1.default.blue('Step 2: Analyzing feed configuration...'));
37
+ const feedGroups = (0, router_js_1.groupFeedsByType)(allRegistries);
38
+ console.log(chalk_1.default.green(`āœ“ Found ${feedGroups.npm.length} npm registries, ${feedGroups.nuget.length} NuGet feeds\n`));
39
+ // Step 3: Track config source
40
+ console.log(chalk_1.default.blue('Step 3: Tracking configuration source...'));
41
+ if (isDryRun) {
42
+ console.log(chalk_1.default.yellow('⊘ Skipped (dry run): Would track configuration source\n'));
43
+ }
44
+ else {
45
+ await storeConfigSource(options.config, buildConfigAuthOptions(options));
46
+ console.log(chalk_1.default.green('āœ“ Configuration source tracked\n'));
47
+ }
48
+ // Step 4: Setup authentication (before configuration updates)
49
+ console.log(chalk_1.default.blue('Step 4: Setting up authentication...'));
50
+ let tokenMap;
51
+ if (isDryRun) {
52
+ console.log(chalk_1.default.yellow('⊘ Skipped (dry run): Would setup authentication for feeds\n'));
53
+ }
54
+ else {
55
+ tokenMap = await (0, orchestrator_js_1.orchestrateAuthentication)(config.authGroups, options.configRedirectUri);
56
+ console.log(chalk_1.default.green('āœ“ Authentication configured\n'));
57
+ }
58
+ // Step 5: Creating backups
59
+ console.log(chalk_1.default.blue('Step 5: Creating backups...'));
60
+ if (isDryRun) {
61
+ console.log(chalk_1.default.yellow('⊘ Skipped (dry run): Would backup configuration files\n'));
62
+ }
63
+ else {
64
+ const backupPromises = [];
65
+ if (feedGroups.npm.length > 0) {
66
+ backupPromises.push((0, manager_js_1.backupNpmrc)());
67
+ }
68
+ if (feedGroups.nuget.length > 0) {
69
+ backupPromises.push((0, manager_js_2.backupNugetConfig)());
70
+ }
71
+ await Promise.all(backupPromises);
72
+ console.log(chalk_1.default.green('āœ“ Backup completed\n'));
73
+ }
74
+ // Step 6: Configuring package feeds
75
+ console.log(chalk_1.default.blue('Step 6: Configuring package feeds...'));
76
+ await (0, router_js_1.applyFeeds)(feedGroups, tokenMap || new Map(), isDryRun);
77
+ if (!isDryRun) {
78
+ console.log(chalk_1.default.green('āœ“ Feed configuration updated\n'));
79
+ }
80
+ // Success message
81
+ if (isDryRun) {
82
+ console.log(chalk_1.default.bold.yellow('āœ“ Dry run completed successfully!\n'));
83
+ console.log(chalk_1.default.gray('Run without --dry-run to apply these changes.\n'));
84
+ }
85
+ else {
86
+ console.log(chalk_1.default.bold.green('āœ… Initialization complete!\n'));
87
+ if (feedGroups.npm.length > 0) {
88
+ const npmScopes = Array.from(new Set(feedGroups.npm.map(r => r.scope)));
89
+ console.log(chalk_1.default.gray('npm packages can now be installed:'));
90
+ for (const scope of npmScopes) {
91
+ console.log(chalk_1.default.gray(` npm install ${scope}/some-package`));
92
+ }
93
+ }
94
+ if (feedGroups.nuget.length > 0) {
95
+ const nugetSources = Array.from(new Set(feedGroups.nuget.map(r => r.scope)));
96
+ console.log(chalk_1.default.gray('\nNuGet packages can now be installed:'));
97
+ for (const source of nugetSources) {
98
+ console.log(chalk_1.default.gray(` dotnet add package SomePackage --source ${source}`));
99
+ }
100
+ }
101
+ console.log('');
102
+ }
103
+ }
104
+ catch (error) {
105
+ console.error(chalk_1.default.red.bold('\nāŒ Initialization failed\n'));
106
+ if (error instanceof Error) {
107
+ console.error(chalk_1.default.red(`Error: ${error.message}\n`));
108
+ }
109
+ else {
110
+ console.error(chalk_1.default.red(`Unknown error occurred\n`));
111
+ }
112
+ // Offer to restore from backup
113
+ if (error instanceof Error && error.message.includes('Authentication')) {
114
+ console.log(chalk_1.default.yellow('Restore your .npmrc from backup? Run:'));
115
+ console.log(chalk_1.default.gray(' sft-mfe-platform restore\n'));
116
+ }
117
+ process.exit(2);
118
+ }
119
+ }
120
+ /**
121
+ * Store the config source for later updates
122
+ */
123
+ async function storeConfigSource(source, authOptions) {
124
+ try {
125
+ const storeDir = path_1.default.dirname(CONFIG_STORE_PATH);
126
+ await promises_1.default.mkdir(storeDir, { recursive: true });
127
+ const configSource = {
128
+ source,
129
+ timestamp: Date.now(),
130
+ type: (0, loader_js_1.isUrlSource)(source) ? 'url' : 'file',
131
+ authOptions, // Store auth options if provided
132
+ };
133
+ await promises_1.default.writeFile(CONFIG_STORE_PATH, JSON.stringify(configSource, null, 2), 'utf-8');
134
+ }
135
+ catch (error) {
136
+ console.warn(chalk_1.default.yellow(`⚠ Warning: Could not store config source: ${error instanceof Error ? error.message : String(error)}`));
137
+ }
138
+ }
139
+ /**
140
+ * Flatten registries from authGroups and top-level registries array
141
+ */
142
+ function flattenRegistries(config) {
143
+ const allRegistries = [];
144
+ // Add registries from auth groups
145
+ if (config.authGroups) {
146
+ for (const group of config.authGroups) {
147
+ allRegistries.push(...group.registries);
148
+ }
149
+ }
150
+ // Add top-level public registries
151
+ if (config.registries) {
152
+ allRegistries.push(...config.registries);
153
+ }
154
+ return allRegistries;
155
+ }
156
+ /**
157
+ * Get stored config source and auth options for update command
158
+ */
159
+ async function getStoredConfigSource() {
160
+ try {
161
+ const content = await promises_1.default.readFile(CONFIG_STORE_PATH, 'utf-8');
162
+ const configSource = JSON.parse(content);
163
+ return configSource;
164
+ }
165
+ catch {
166
+ return null;
167
+ }
168
+ }
169
+ /**
170
+ * Create and configure the init command
171
+ */
172
+ function createInitCommand() {
173
+ const command = new commander_1.Command('init');
174
+ command
175
+ .description('Initialize developer machine with npm registry configuration')
176
+ .requiredOption('--config <source>', 'Configuration source: URL or file path')
177
+ .option('--config-auth-endpoint <url>', 'Config URL auth endpoint (device code endpoint)')
178
+ .option('--config-token-endpoint <url>', 'Config URL token endpoint (optional, derived if omitted)')
179
+ .option('--config-client-id <id>', 'Config URL OAuth client ID')
180
+ .option('--config-token-kind <kind>', 'Config URL token to use as bearer (access or id)')
181
+ .option('--config-auth-scope <scope>', 'Config URL OAuth scope (optional)')
182
+ .option('--config-redirect-uri <uri>', 'Custom redirect URI for PKCE auth')
183
+ .option('-i, --interactive', 'Interactive mode', false)
184
+ .option('-f, --force', 'Skip confirmations', false)
185
+ .option('--dry-run', 'Preview changes without applying them', false)
186
+ .action(async (options) => {
187
+ try {
188
+ await executeInit(options);
189
+ }
190
+ catch (error) {
191
+ if (!(error instanceof Error && error.message)) {
192
+ console.error(chalk_1.default.red('Unknown error occurred'));
193
+ }
194
+ process.exit(1);
195
+ }
196
+ });
197
+ return command;
198
+ }
199
+ function buildConfigAuthOptions(options) {
200
+ if (!options.configAuthEndpoint && !options.configClientId && !options.configTokenKind && !options.configTokenEndpoint && !options.configAuthScope) {
201
+ return undefined;
202
+ }
203
+ if (!options.configAuthEndpoint || !options.configClientId) {
204
+ throw new Error('Config URL auth requires both --config-auth-endpoint and --config-client-id');
205
+ }
206
+ return {
207
+ authEndpoint: options.configAuthEndpoint,
208
+ tokenEndpoint: options.configTokenEndpoint,
209
+ clientId: options.configClientId,
210
+ tokenKind: options.configTokenKind,
211
+ scope: options.configAuthScope,
212
+ redirectUri: options.configRedirectUri,
213
+ };
214
+ }
@@ -0,0 +1,5 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Create and configure the update command
4
+ */
5
+ export declare function createUpdateCommand(): Command;
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createUpdateCommand = createUpdateCommand;
7
+ const commander_1 = require("commander");
8
+ const loader_js_1 = require("../config/loader.js");
9
+ const manager_js_1 = require("../npmrc/manager.js");
10
+ const orchestrator_js_1 = require("../auth/orchestrator.js");
11
+ const init_js_1 = require("./init.js");
12
+ const router_js_1 = require("../feed/router.js");
13
+ const manager_js_2 = require("../nuget/manager.js");
14
+ const chalk_1 = __importDefault(require("chalk"));
15
+ /**
16
+ * Execute update command
17
+ */
18
+ async function executeUpdate(options) {
19
+ try {
20
+ const isDryRun = options.dryRun === true;
21
+ console.log(chalk_1.default.bold.cyan(`\nšŸ”„ Updating Sanlam Platform CLI Configuration${isDryRun ? ' (Dry Run)' : ''}\n`));
22
+ // Step 1: Determine config source and auth options (provided or stored)
23
+ console.log(chalk_1.default.blue('Step 1: Determining configuration source...'));
24
+ let configSource;
25
+ let mergedAuthOptions;
26
+ if (options.config) {
27
+ // Use provided config source
28
+ configSource = options.config;
29
+ mergedAuthOptions = buildConfigAuthOptions(options);
30
+ console.log(chalk_1.default.green(`āœ“ Using provided config: ${configSource}\n`));
31
+ }
32
+ else {
33
+ // Use stored config source and auth options
34
+ const storedConfig = await (0, init_js_1.getStoredConfigSource)();
35
+ if (!storedConfig) {
36
+ throw new Error('No configuration source found.\n' +
37
+ 'Either provide --config <source> or run "sft-mfe-platform init" first.');
38
+ }
39
+ configSource = storedConfig.source;
40
+ // Merge stored auth options with CLI-provided options (CLI takes precedence)
41
+ const cliAuthOptions = buildConfigAuthOptions(options);
42
+ mergedAuthOptions = mergeAuthOptions(storedConfig.authOptions, cliAuthOptions);
43
+ console.log(chalk_1.default.green(`āœ“ Using stored config: ${configSource}\n`));
44
+ }
45
+ // Step 2: Load and validate configuration
46
+ console.log(chalk_1.default.blue('Step 2: Loading configuration...'));
47
+ const config = await (0, loader_js_1.loadConfig)(configSource, mergedAuthOptions);
48
+ // Flatten all registries for display and processing
49
+ const allRegistries = flattenRegistries(config);
50
+ console.log(chalk_1.default.green(`āœ“ Loaded ${allRegistries.length} registry/registries\n`));
51
+ // Step 3: Analyze feed configuration
52
+ console.log(chalk_1.default.blue('Step 3: Analyzing feed configuration...'));
53
+ const feedGroups = (0, router_js_1.groupFeedsByType)(allRegistries);
54
+ console.log(chalk_1.default.green(`āœ“ Found ${feedGroups.npm.length} npm registries, ${feedGroups.nuget.length} NuGet feeds\n`));
55
+ // Step 4: Setup authentication (before configuration updates)
56
+ console.log(chalk_1.default.blue('Step 4: Setting up authentication...'));
57
+ let tokenMap;
58
+ if (isDryRun) {
59
+ console.log(chalk_1.default.yellow('⊘ Skipped (dry run): Would setup authentication for feeds\n'));
60
+ }
61
+ else {
62
+ tokenMap = await (0, orchestrator_js_1.orchestrateAuthentication)(config.authGroups, options.configRedirectUri);
63
+ console.log(chalk_1.default.green('āœ“ Authentication configured\n'));
64
+ }
65
+ // Step 5: Creating backups
66
+ console.log(chalk_1.default.blue('Step 5: Creating backups...'));
67
+ if (isDryRun) {
68
+ console.log(chalk_1.default.yellow('⊘ Skipped (dry run): Would backup configuration files\n'));
69
+ }
70
+ else {
71
+ const backupPromises = [];
72
+ if (feedGroups.npm.length > 0) {
73
+ backupPromises.push((0, manager_js_1.backupNpmrc)());
74
+ }
75
+ if (feedGroups.nuget.length > 0) {
76
+ backupPromises.push((0, manager_js_2.backupNugetConfig)());
77
+ }
78
+ await Promise.all(backupPromises);
79
+ console.log(chalk_1.default.green('āœ“ Backup completed\n'));
80
+ }
81
+ // Step 6: Configuring package feeds
82
+ console.log(chalk_1.default.blue('Step 6: Configuring package feeds...'));
83
+ await (0, router_js_1.applyFeeds)(feedGroups, tokenMap || new Map(), isDryRun);
84
+ if (!isDryRun) {
85
+ console.log(chalk_1.default.green('āœ“ Feed configuration updated\n'));
86
+ }
87
+ // Success message
88
+ if (isDryRun) {
89
+ console.log(chalk_1.default.bold.yellow('āœ“ Dry run completed successfully!\n'));
90
+ console.log(chalk_1.default.gray('Run without --dry-run to apply these changes.\n'));
91
+ }
92
+ else {
93
+ console.log(chalk_1.default.bold.green('āœ… Update complete!\n'));
94
+ if (feedGroups.npm.length > 0) {
95
+ const npmScopes = Array.from(new Set(feedGroups.npm.map(r => r.scope)));
96
+ console.log(chalk_1.default.gray('npm packages can now be installed:'));
97
+ for (const scope of npmScopes) {
98
+ console.log(chalk_1.default.gray(` npm install ${scope}/some-package`));
99
+ }
100
+ }
101
+ if (feedGroups.nuget.length > 0) {
102
+ const nugetSources = Array.from(new Set(feedGroups.nuget.map(r => r.scope)));
103
+ console.log(chalk_1.default.gray('\nNuGet packages can now be installed:'));
104
+ for (const source of nugetSources) {
105
+ console.log(chalk_1.default.gray(` dotnet add package SomePackage --source ${source}`));
106
+ }
107
+ }
108
+ console.log('');
109
+ }
110
+ }
111
+ catch (error) {
112
+ console.error(chalk_1.default.red.bold('\nāŒ Update failed\n'));
113
+ if (error instanceof Error) {
114
+ console.error(chalk_1.default.red(`Error: ${error.message}\n`));
115
+ }
116
+ else {
117
+ console.error(chalk_1.default.red(`Unknown error occurred\n`));
118
+ }
119
+ // Offer to restore from backup
120
+ if (error instanceof Error && error.message.includes('Authentication')) {
121
+ console.log(chalk_1.default.yellow('Restore your .npmrc from backup? Run:'));
122
+ console.log(chalk_1.default.gray(' sft-mfe-platform restore\n'));
123
+ }
124
+ process.exit(2);
125
+ }
126
+ }
127
+ /**
128
+ * Merge stored auth options with CLI-provided options
129
+ * CLI options take precedence over stored options
130
+ */
131
+ function mergeAuthOptions(stored, cli) {
132
+ // If CLI options are provided, use them (they override everything)
133
+ if (cli) {
134
+ return cli;
135
+ }
136
+ // Otherwise use stored options
137
+ return stored;
138
+ }
139
+ /**
140
+ * Flatten registries from authGroups and top-level registries array
141
+ */
142
+ function flattenRegistries(config) {
143
+ const allRegistries = [];
144
+ // Add registries from auth groups
145
+ if (config.authGroups) {
146
+ for (const group of config.authGroups) {
147
+ allRegistries.push(...group.registries);
148
+ }
149
+ }
150
+ // Add top-level public registries
151
+ if (config.registries) {
152
+ allRegistries.push(...config.registries);
153
+ }
154
+ return allRegistries;
155
+ }
156
+ /**
157
+ * Create and configure the update command
158
+ */
159
+ function createUpdateCommand() {
160
+ const command = new commander_1.Command('update');
161
+ command
162
+ .description('Update npm registry configuration (uses stored config source from init)')
163
+ .option('--config <source>', 'Configuration source: URL or file path (overrides stored source)')
164
+ .option('--config-auth-endpoint <url>', 'Config URL auth endpoint (device code or authorize endpoint)')
165
+ .option('--config-token-endpoint <url>', 'Config URL token endpoint (optional, derived if omitted)')
166
+ .option('--config-client-id <id>', 'Config URL OAuth client ID')
167
+ .option('--config-token-kind <kind>', 'Config URL token to use as bearer (access or id)')
168
+ .option('--config-auth-scope <scope>', 'Config URL OAuth scope (optional)')
169
+ .option('--config-redirect-uri <uri>', 'Custom redirect URI for PKCE auth')
170
+ .option('--dry-run', 'Preview changes without applying them', false)
171
+ .action(async (options) => {
172
+ try {
173
+ await executeUpdate(options);
174
+ }
175
+ catch (error) {
176
+ if (!(error instanceof Error && error.message)) {
177
+ console.error(chalk_1.default.red('Unknown error occurred'));
178
+ }
179
+ process.exit(1);
180
+ }
181
+ });
182
+ return command;
183
+ }
184
+ function buildConfigAuthOptions(options) {
185
+ if (!options.configAuthEndpoint && !options.configClientId && !options.configTokenKind && !options.configTokenEndpoint && !options.configAuthScope) {
186
+ return undefined;
187
+ }
188
+ if (!options.configAuthEndpoint || !options.configClientId) {
189
+ throw new Error('Config URL auth requires both --config-auth-endpoint and --config-client-id');
190
+ }
191
+ return {
192
+ authEndpoint: options.configAuthEndpoint,
193
+ tokenEndpoint: options.configTokenEndpoint,
194
+ clientId: options.configClientId,
195
+ tokenKind: options.configTokenKind,
196
+ scope: options.configAuthScope,
197
+ redirectUri: options.configRedirectUri,
198
+ };
199
+ }
@@ -0,0 +1,10 @@
1
+ export interface CachedConfigAuthToken {
2
+ clientId: string;
3
+ accessToken?: string;
4
+ idToken?: string;
5
+ providerHint: 'github' | 'entra' | 'other';
6
+ tenantId?: string;
7
+ scopes?: string[];
8
+ }
9
+ export declare function getCachedConfigAuthToken(): CachedConfigAuthToken | undefined;
10
+ export declare function setCachedConfigAuthToken(token: CachedConfigAuthToken | undefined): void;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCachedConfigAuthToken = getCachedConfigAuthToken;
4
+ exports.setCachedConfigAuthToken = setCachedConfigAuthToken;
5
+ let cachedConfigAuthToken;
6
+ function getCachedConfigAuthToken() {
7
+ return cachedConfigAuthToken;
8
+ }
9
+ function setCachedConfigAuthToken(token) {
10
+ cachedConfigAuthToken = token;
11
+ }
@@ -0,0 +1,9 @@
1
+ import { RegistryConfig, ConfigAuthOptions } from '../types/config';
2
+ /**
3
+ * Load registry configuration from URL or local file
4
+ */
5
+ export declare function loadConfig(source: string, authOptions?: ConfigAuthOptions): Promise<RegistryConfig>;
6
+ /**
7
+ * Determine if source is URL or file path
8
+ */
9
+ export declare function isUrlSource(source: string): boolean;
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadConfig = loadConfig;
7
+ exports.isUrlSource = isUrlSource;
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const promises_1 = __importDefault(require("fs/promises"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const config_1 = require("../types/config");
12
+ const pkce_flow_1 = require("../auth/pkce-flow");
13
+ const device_flow_1 = require("../auth/device-flow");
14
+ const auth_token_cache_1 = require("./auth-token-cache");
15
+ const azure_devops_1 = require("../auth/providers/azure-devops");
16
+ const constants_1 = require("../auth/constants");
17
+ /**
18
+ * Load registry configuration from URL or local file
19
+ */
20
+ async function loadConfig(source, authOptions) {
21
+ const isUrl = source.startsWith('http://') || source.startsWith('https://');
22
+ let spinner = null;
23
+ try {
24
+ let configData;
25
+ // Check if source is a URL or file path
26
+ if (isUrl) {
27
+ const authHeaders = authOptions ? await buildAuthHeaders(authOptions) : {};
28
+ spinner = (0, ora_1.default)('Loading configuration...').start();
29
+ configData = await loadConfigFromUrl(source, authHeaders);
30
+ }
31
+ else {
32
+ spinner = (0, ora_1.default)('Loading configuration...').start();
33
+ configData = await loadConfigFromFile(source);
34
+ }
35
+ // Parse and validate (after any auth flow completes)
36
+ spinner.text = 'Validating configuration...';
37
+ const parsed = JSON.parse(configData);
38
+ const validated = config_1.RegistryConfigSchema.parse(parsed);
39
+ spinner?.succeed('Configuration loaded successfully');
40
+ return validated;
41
+ }
42
+ catch (error) {
43
+ spinner?.fail('Failed to load configuration');
44
+ if (error instanceof Error) {
45
+ throw new Error(`Configuration loading failed: ${error.message}`);
46
+ }
47
+ throw error;
48
+ }
49
+ }
50
+ /**
51
+ * Load configuration from remote URL
52
+ */
53
+ async function loadConfigFromUrl(url, headers) {
54
+ try {
55
+ const response = await fetch(url, {
56
+ headers: {
57
+ 'Accept': 'application/octet-stream',
58
+ ...headers,
59
+ },
60
+ });
61
+ if (!response.ok) {
62
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
63
+ }
64
+ return await response.text();
65
+ }
66
+ catch (error) {
67
+ if (error instanceof Error) {
68
+ throw new Error(`Failed to fetch from ${url}: ${error.message}`);
69
+ }
70
+ throw error;
71
+ }
72
+ }
73
+ async function buildAuthHeaders(options) {
74
+ if (!options.authEndpoint || !options.clientId) {
75
+ throw new Error('Config URL auth requires authEndpoint and clientId');
76
+ }
77
+ const tokenKind = options.tokenKind ?? getDefaultTokenKind();
78
+ const scope = options.scope ?? getDefaultScopes(options.authEndpoint);
79
+ const authorizeEndpoint = options.authEndpoint;
80
+ const tokenEndpoint = options.tokenEndpoint ?? deriveTokenEndpoint(authorizeEndpoint);
81
+ const tokenResponse = isDeviceFlowEndpoint(authorizeEndpoint)
82
+ ? await runDeviceFlow(authorizeEndpoint, tokenEndpoint, options.clientId, scope)
83
+ : await runAuthorizePkce(authorizeEndpoint, tokenEndpoint, options.clientId, scope, options.redirectUri);
84
+ const selectedToken = tokenKind === 'id'
85
+ ? tokenResponse.id_token
86
+ : tokenResponse.access_token;
87
+ if (!selectedToken) {
88
+ throw new Error(`Requested ${tokenKind} token but it was not returned by the auth provider`);
89
+ }
90
+ (0, auth_token_cache_1.setCachedConfigAuthToken)({
91
+ clientId: options.clientId,
92
+ accessToken: tokenResponse.access_token,
93
+ idToken: tokenResponse.id_token,
94
+ providerHint: getProviderHint(options.authEndpoint),
95
+ tenantId: extractTenantId(options.authEndpoint),
96
+ scopes: extractScopes(tokenResponse),
97
+ });
98
+ return {
99
+ Authorization: `Bearer ${selectedToken}`,
100
+ };
101
+ }
102
+ async function runDeviceFlow(deviceAuthEndpoint, tokenEndpoint, clientId, scope) {
103
+ const deviceAuth = await (0, device_flow_1.initiateDeviceFlow)(deviceAuthEndpoint, clientId, scope);
104
+ (0, device_flow_1.displayUserInstructions)(deviceAuth.user_code, deviceAuth.verification_uri, 'Config URL');
105
+ return (0, device_flow_1.pollForToken)(tokenEndpoint, clientId, deviceAuth.device_code, deviceAuth.interval, deviceAuth.expires_in);
106
+ }
107
+ async function runAuthorizePkce(authorizeEndpoint, tokenEndpoint, clientId, scope, redirectUri) {
108
+ const effectiveRedirectUri = redirectUri || constants_1.DEFAULT_REDIRECT_URI;
109
+ return (0, pkce_flow_1.runPkceFlow)({
110
+ authorizeEndpoint,
111
+ tokenEndpoint,
112
+ clientId,
113
+ redirectUri: effectiveRedirectUri,
114
+ scope,
115
+ providerName: 'Config URL',
116
+ });
117
+ }
118
+ function isDeviceFlowEndpoint(authEndpoint) {
119
+ return authEndpoint.includes('/devicecode') || authEndpoint.includes('/device/code');
120
+ }
121
+ function isGitHubAuthEndpoint(authEndpoint) {
122
+ try {
123
+ const url = new URL(authEndpoint);
124
+ return url.hostname === 'github.com' || url.hostname.endsWith('.github.com');
125
+ }
126
+ catch {
127
+ return false;
128
+ }
129
+ }
130
+ function getProviderHint(authEndpoint) {
131
+ if (isGitHubAuthEndpoint(authEndpoint)) {
132
+ return 'github';
133
+ }
134
+ if (isEntraIdAuthEndpoint(authEndpoint)) {
135
+ return 'entra';
136
+ }
137
+ return 'other';
138
+ }
139
+ function getDefaultScopes(authEndpoint) {
140
+ if (isGitHubAuthEndpoint(authEndpoint)) {
141
+ return 'repo read:packages';
142
+ }
143
+ if (isEntraIdAuthEndpoint(authEndpoint)) {
144
+ return `openid profile ${azure_devops_1.AZURE_DEVOPS_OAUTH_SCOPE}`;
145
+ }
146
+ return 'openid profile';
147
+ }
148
+ function getDefaultTokenKind() {
149
+ return 'access';
150
+ }
151
+ function deriveTokenEndpoint(authorizeEndpoint) {
152
+ if (authorizeEndpoint.includes('/authorize')) {
153
+ return authorizeEndpoint.replace(/\/authorize(\?.*)?$/, '/token');
154
+ }
155
+ if (authorizeEndpoint.includes('/devicecode')) {
156
+ return authorizeEndpoint.replace(/\/devicecode(\?.*)?$/, '/token');
157
+ }
158
+ if (authorizeEndpoint.includes('/device/code')) {
159
+ return authorizeEndpoint.replace(/\/device\/code(\?.*)?$/, '/oauth/access_token');
160
+ }
161
+ throw new Error('Config URL auth requires tokenEndpoint when it cannot be derived from authEndpoint');
162
+ }
163
+ function isEntraIdAuthEndpoint(authEndpoint) {
164
+ try {
165
+ const url = new URL(authEndpoint);
166
+ return url.hostname === 'login.microsoftonline.com'
167
+ || url.hostname === 'login.microsoft.com';
168
+ }
169
+ catch {
170
+ return false;
171
+ }
172
+ }
173
+ function extractTenantId(authEndpoint) {
174
+ if (!isEntraIdAuthEndpoint(authEndpoint)) {
175
+ return undefined;
176
+ }
177
+ try {
178
+ const url = new URL(authEndpoint);
179
+ const segments = url.pathname.split('/').filter(Boolean);
180
+ return segments.length > 0 ? segments[0] : undefined;
181
+ }
182
+ catch {
183
+ return undefined;
184
+ }
185
+ }
186
+ function extractScopes(tokenResponse) {
187
+ const scope = tokenResponse?.scope;
188
+ if (!scope) {
189
+ return undefined;
190
+ }
191
+ return scope.split(' ').map((s) => s.trim()).filter(Boolean);
192
+ }
193
+ /**
194
+ * Load configuration from local file
195
+ */
196
+ async function loadConfigFromFile(filePath) {
197
+ try {
198
+ const resolvedPath = path_1.default.resolve(filePath);
199
+ const content = await promises_1.default.readFile(resolvedPath, 'utf-8');
200
+ return content;
201
+ }
202
+ catch (error) {
203
+ if (error instanceof Error) {
204
+ throw new Error(`Failed to read config file at ${filePath}: ${error.message}`);
205
+ }
206
+ throw error;
207
+ }
208
+ }
209
+ /**
210
+ * Determine if source is URL or file path
211
+ */
212
+ function isUrlSource(source) {
213
+ return source.startsWith('http://') || source.startsWith('https://');
214
+ }
@@ -0,0 +1,13 @@
1
+ import { Registry } from '../types/config.js';
2
+ export interface FeedGroup {
3
+ npm: Registry[];
4
+ nuget: Registry[];
5
+ }
6
+ /**
7
+ * Group feeds by type
8
+ */
9
+ export declare function groupFeedsByType(registries: Registry[]): FeedGroup;
10
+ /**
11
+ * Apply feeds to respective configuration files
12
+ */
13
+ export declare function applyFeeds(feedGroups: FeedGroup, tokenMap: Map<string, string>, dryRun: boolean): Promise<void>;