@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,58 @@
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.groupFeedsByType = groupFeedsByType;
7
+ exports.applyFeeds = applyFeeds;
8
+ const manager_js_1 = require("../npmrc/manager.js");
9
+ const manager_js_2 = require("../nuget/manager.js");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ /**
12
+ * Group feeds by type
13
+ */
14
+ function groupFeedsByType(registries) {
15
+ const npm = [];
16
+ const nuget = [];
17
+ for (const registry of registries) {
18
+ const feedType = registry.feedType || 'npm'; // Default to npm for backward compatibility
19
+ if (feedType === 'npm') {
20
+ npm.push(registry);
21
+ }
22
+ else if (feedType === 'nuget') {
23
+ nuget.push(registry);
24
+ }
25
+ }
26
+ return { npm, nuget };
27
+ }
28
+ /**
29
+ * Apply feeds to respective configuration files
30
+ */
31
+ async function applyFeeds(feedGroups, tokenMap, dryRun) {
32
+ // Apply npm registries
33
+ if (feedGroups.npm.length > 0) {
34
+ if (dryRun) {
35
+ console.log(chalk_1.default.yellow('⊘ Skipped (dry run): Would configure npm registries (.npmrc):'));
36
+ for (const reg of feedGroups.npm) {
37
+ console.log(chalk_1.default.gray(` ${reg.scope}:registry=${reg.url}`));
38
+ }
39
+ console.log('');
40
+ }
41
+ else {
42
+ await (0, manager_js_1.addRegistries)(feedGroups.npm, tokenMap);
43
+ }
44
+ }
45
+ // Apply NuGet feeds
46
+ if (feedGroups.nuget.length > 0) {
47
+ if (dryRun) {
48
+ console.log(chalk_1.default.yellow('⊘ Skipped (dry run): Would configure NuGet feeds (nuget.config):'));
49
+ for (const feed of feedGroups.nuget) {
50
+ console.log(chalk_1.default.gray(` <add key="${feed.scope}" value="${feed.url}" />`));
51
+ }
52
+ console.log('');
53
+ }
54
+ else {
55
+ await (0, manager_js_2.addNuGetFeeds)(feedGroups.nuget, tokenMap);
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const init_js_1 = require("./commands/init.js");
9
+ const update_js_1 = require("./commands/update.js");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const program = new commander_1.Command();
12
+ program
13
+ .name('sft-mfe-platform')
14
+ .description('Sanlam Platform CLI - Bootstrap and orchestration tool')
15
+ .version('0.0.1');
16
+ /**
17
+ * Init command - Bootstrap developer machine with npm registry configuration
18
+ */
19
+ program.addCommand((0, init_js_1.createInitCommand)());
20
+ /**
21
+ * Update command - Refresh npm registry configuration using stored config source
22
+ */
23
+ program.addCommand((0, update_js_1.createUpdateCommand)());
24
+ /**
25
+ * Help and version
26
+ */
27
+ program
28
+ .on('--help', () => {
29
+ console.log('');
30
+ console.log(chalk_1.default.cyan('Examples:'));
31
+ console.log('');
32
+ console.log(' # Global install');
33
+ console.log(' npm install -g @sanlam-fintech-digital/mfe-platform-cli');
34
+ console.log(' sft-mfe-platform init --config https://example.com/registry-config.json');
35
+ console.log('');
36
+ console.log(' # Update registry configuration (uses stored config from init)');
37
+ console.log(' sft-mfe-platform update');
38
+ console.log('');
39
+ console.log(' # Update with a different config source');
40
+ console.log(' sft-mfe-platform update --config https://example.com/new-config.json');
41
+ console.log('');
42
+ console.log(' # One-off with npx');
43
+ console.log(' npx @sanlam-fintech-digital/mfe-platform-cli init --config ./local-config.json');
44
+ console.log('');
45
+ });
46
+ program.parse(process.argv);
47
+ if (!process.argv.slice(2).length) {
48
+ program.outputHelp();
49
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Backup existing .npmrc before modifications
3
+ */
4
+ export declare function backupNpmrc(npmrcPath?: string): Promise<string>;
5
+ /**
6
+ * Read and parse .npmrc file
7
+ */
8
+ export declare function readNpmrc(npmrcPath?: string): Promise<Map<string, string>>;
9
+ /**
10
+ * Add or update registry entries with authentication tokens
11
+ */
12
+ export declare function addRegistries(registries: Array<{
13
+ scope: string;
14
+ url: string;
15
+ }>, tokenMap?: Map<string, string>, npmrcPath?: string): Promise<void>;
16
+ /**
17
+ * Restore from backup
18
+ */
19
+ export declare function restoreFromBackup(backupPath: string, npmrcPath?: string): Promise<void>;
@@ -0,0 +1,142 @@
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.backupNpmrc = backupNpmrc;
7
+ exports.readNpmrc = readNpmrc;
8
+ exports.addRegistries = addRegistries;
9
+ exports.restoreFromBackup = restoreFromBackup;
10
+ const promises_1 = __importDefault(require("fs/promises"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const os_1 = __importDefault(require("os"));
13
+ const ora_1 = __importDefault(require("ora"));
14
+ /**
15
+ * Backup existing .npmrc before modifications
16
+ */
17
+ async function backupNpmrc(npmrcPath) {
18
+ const resolvedPath = npmrcPath || path_1.default.join(os_1.default.homedir(), '.npmrc');
19
+ const spinner = (0, ora_1.default)('Backing up existing .npmrc...').start();
20
+ try {
21
+ try {
22
+ await promises_1.default.access(resolvedPath);
23
+ }
24
+ catch {
25
+ spinner.succeed('No existing .npmrc to backup');
26
+ return '';
27
+ }
28
+ const content = await promises_1.default.readFile(resolvedPath, 'utf-8');
29
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
30
+ const backupPath = `${resolvedPath}.backup.${timestamp}`;
31
+ await promises_1.default.writeFile(backupPath, content, 'utf-8');
32
+ spinner.succeed(`Backed up to ${backupPath}`);
33
+ return backupPath;
34
+ }
35
+ catch (error) {
36
+ spinner.fail('Backup failed');
37
+ throw new Error(`Failed to backup .npmrc: ${error instanceof Error ? error.message : String(error)}`);
38
+ }
39
+ }
40
+ /**
41
+ * Read and parse .npmrc file
42
+ */
43
+ async function readNpmrc(npmrcPath) {
44
+ const resolvedPath = npmrcPath || path_1.default.join(os_1.default.homedir(), '.npmrc');
45
+ try {
46
+ try {
47
+ await promises_1.default.access(resolvedPath);
48
+ }
49
+ catch {
50
+ return new Map();
51
+ }
52
+ const content = await promises_1.default.readFile(resolvedPath, 'utf-8');
53
+ return parseNpmrc(content);
54
+ }
55
+ catch (error) {
56
+ throw new Error(`Failed to read .npmrc: ${error instanceof Error ? error.message : String(error)}`);
57
+ }
58
+ }
59
+ /**
60
+ * Parse .npmrc content into key-value map, preserving non-registry entries
61
+ */
62
+ function parseNpmrc(content) {
63
+ const lines = content.split('\n');
64
+ const entries = new Map();
65
+ for (const line of lines) {
66
+ const trimmed = line.trim();
67
+ // Skip empty lines and comments
68
+ if (!trimmed || trimmed.startsWith(';') || trimmed.startsWith('#')) {
69
+ continue;
70
+ }
71
+ const [key, ...valueParts] = trimmed.split('=');
72
+ if (key && valueParts.length > 0) {
73
+ entries.set(key.trim(), valueParts.join('=').trim());
74
+ }
75
+ }
76
+ return entries;
77
+ }
78
+ /**
79
+ * Add or update registry entries with authentication tokens
80
+ */
81
+ async function addRegistries(registries, tokenMap, npmrcPath) {
82
+ const spinner = (0, ora_1.default)('Adding registry entries...').start();
83
+ try {
84
+ const entries = await readNpmrc(npmrcPath);
85
+ // Add registry entries
86
+ for (const registry of registries) {
87
+ const key = `${registry.scope}:registry`;
88
+ entries.set(key, registry.url);
89
+ // Set auth token if available
90
+ const authKey = `//${registry.url.replace(/https?:\/\//, '')}/:_authToken`;
91
+ const token = tokenMap?.get(registry.url);
92
+ if (token) {
93
+ // Write actual token
94
+ entries.set(authKey, token);
95
+ }
96
+ else if (!entries.has(authKey)) {
97
+ // No token provided and no existing token - set placeholder
98
+ entries.set(authKey, '${NPM_TOKEN}');
99
+ }
100
+ // If token exists already and no new token, preserve existing
101
+ }
102
+ await writeNpmrc(entries, npmrcPath);
103
+ spinner.succeed('Registry entries added');
104
+ }
105
+ catch (error) {
106
+ spinner.fail('Failed to add registry entries');
107
+ throw error;
108
+ }
109
+ }
110
+ /**
111
+ * Write entries back to .npmrc file
112
+ */
113
+ function writeNpmrc(entries, npmrcPath) {
114
+ const resolvedPath = npmrcPath || path_1.default.join(os_1.default.homedir(), '.npmrc');
115
+ const lines = [];
116
+ // Add comment header
117
+ lines.push('# Configured by @sanlam-fintech-digital/mfe-platform-cli');
118
+ lines.push(`# Generated at ${new Date().toISOString()}`);
119
+ lines.push('');
120
+ // Add entries
121
+ for (const [key, value] of entries) {
122
+ lines.push(`${key}=${value}`);
123
+ }
124
+ const content = lines.join('\n') + '\n';
125
+ return promises_1.default.writeFile(resolvedPath, content, 'utf-8');
126
+ }
127
+ /**
128
+ * Restore from backup
129
+ */
130
+ async function restoreFromBackup(backupPath, npmrcPath) {
131
+ const resolvedPath = npmrcPath || path_1.default.join(os_1.default.homedir(), '.npmrc');
132
+ const spinner = (0, ora_1.default)('Restoring from backup...').start();
133
+ try {
134
+ const content = await promises_1.default.readFile(backupPath, 'utf-8');
135
+ await promises_1.default.writeFile(resolvedPath, content, 'utf-8');
136
+ spinner.succeed('Restored from backup');
137
+ }
138
+ catch (error) {
139
+ spinner.fail('Restore failed');
140
+ throw new Error(`Failed to restore from backup: ${error instanceof Error ? error.message : String(error)}`);
141
+ }
142
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * NuGet configuration structure
3
+ */
4
+ interface NuGetConfig {
5
+ configuration: {
6
+ packageSources?: {
7
+ add?: Array<{
8
+ '@_key': string;
9
+ '@_value': string;
10
+ }>;
11
+ };
12
+ packageSourceCredentials?: Record<string, {
13
+ add?: Array<{
14
+ '@_key': string;
15
+ '@_value': string;
16
+ }>;
17
+ }>;
18
+ [key: string]: any;
19
+ };
20
+ }
21
+ /**
22
+ * Backup existing NuGet.config
23
+ */
24
+ export declare function backupNugetConfig(configPath?: string): Promise<string>;
25
+ /**
26
+ * Read and parse NuGet.config
27
+ */
28
+ export declare function readNugetConfig(configPath?: string): Promise<NuGetConfig>;
29
+ /**
30
+ * Add or update NuGet feeds
31
+ */
32
+ export declare function addNuGetFeeds(feeds: Array<{
33
+ scope: string;
34
+ url: string;
35
+ }>, tokenMap?: Map<string, string>, configPath?: string): Promise<void>;
36
+ /**
37
+ * Restore from backup
38
+ */
39
+ export declare function restoreNugetFromBackup(backupPath: string, configPath?: string): Promise<void>;
40
+ export {};
@@ -0,0 +1,145 @@
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.backupNugetConfig = backupNugetConfig;
7
+ exports.readNugetConfig = readNugetConfig;
8
+ exports.addNuGetFeeds = addNuGetFeeds;
9
+ exports.restoreNugetFromBackup = restoreNugetFromBackup;
10
+ const promises_1 = __importDefault(require("fs/promises"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const os_1 = __importDefault(require("os"));
13
+ const chalk_1 = __importDefault(require("chalk"));
14
+ const fast_xml_parser_1 = require("fast-xml-parser");
15
+ /**
16
+ * Get platform-specific NuGet config path
17
+ */
18
+ function getNugetConfigPath(configPath) {
19
+ if (configPath)
20
+ return configPath;
21
+ const platform = process.platform;
22
+ if (platform === 'win32') {
23
+ const appData = process.env.APPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming');
24
+ return path_1.default.join(appData, 'NuGet', 'NuGet.Config');
25
+ }
26
+ else {
27
+ return path_1.default.join(os_1.default.homedir(), '.config', 'NuGet', 'NuGet.Config');
28
+ }
29
+ }
30
+ /**
31
+ * Backup existing NuGet.config
32
+ */
33
+ async function backupNugetConfig(configPath) {
34
+ const nugetPath = getNugetConfigPath(configPath);
35
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
36
+ const backupPath = `${nugetPath}.backup.${timestamp}`;
37
+ try {
38
+ await promises_1.default.access(nugetPath);
39
+ await promises_1.default.copyFile(nugetPath, backupPath);
40
+ console.log(chalk_1.default.gray(`Backed up: ${backupPath}`));
41
+ return backupPath;
42
+ }
43
+ catch {
44
+ // File doesn't exist, no backup needed
45
+ return '';
46
+ }
47
+ }
48
+ /**
49
+ * Read and parse NuGet.config
50
+ */
51
+ async function readNugetConfig(configPath) {
52
+ const nugetPath = getNugetConfigPath(configPath);
53
+ try {
54
+ const content = await promises_1.default.readFile(nugetPath, 'utf-8');
55
+ const parser = new fast_xml_parser_1.XMLParser({
56
+ ignoreAttributes: false,
57
+ attributeNamePrefix: '@_',
58
+ });
59
+ return parser.parse(content);
60
+ }
61
+ catch {
62
+ // Return default structure if file doesn't exist
63
+ return {
64
+ configuration: {
65
+ packageSources: { add: [] },
66
+ packageSourceCredentials: {},
67
+ }
68
+ };
69
+ }
70
+ }
71
+ /**
72
+ * Add or update NuGet feeds
73
+ */
74
+ async function addNuGetFeeds(feeds, tokenMap, configPath) {
75
+ const config = await readNugetConfig(configPath);
76
+ // Ensure structure exists
77
+ if (!config.configuration.packageSources) {
78
+ config.configuration.packageSources = { add: [] };
79
+ }
80
+ if (!config.configuration.packageSources.add) {
81
+ config.configuration.packageSources.add = [];
82
+ }
83
+ if (!config.configuration.packageSourceCredentials) {
84
+ config.configuration.packageSourceCredentials = {};
85
+ }
86
+ // Normalize 'add' to always be an array (XML parser may return object for single item)
87
+ let sources = config.configuration.packageSources.add;
88
+ if (!Array.isArray(sources)) {
89
+ sources = sources ? [sources] : [];
90
+ config.configuration.packageSources.add = sources;
91
+ }
92
+ const credentials = config.configuration.packageSourceCredentials;
93
+ for (const feed of feeds) {
94
+ const sourceName = feed.scope;
95
+ // Add or update package source
96
+ const existingSourceIndex = sources.findIndex(s => s['@_key'] === sourceName);
97
+ if (existingSourceIndex >= 0) {
98
+ sources[existingSourceIndex]['@_value'] = feed.url;
99
+ }
100
+ else {
101
+ sources.push({ '@_key': sourceName, '@_value': feed.url });
102
+ }
103
+ // Add credentials if token provided
104
+ const token = tokenMap?.get(feed.url);
105
+ if (token) {
106
+ // Normalize credential key (replace invalid XML chars)
107
+ const credKey = sourceName.replace(/[^a-zA-Z0-9_]/g, '_');
108
+ credentials[credKey] = {
109
+ add: [
110
+ { '@_key': 'Username', '@_value': 'PersonalAccessToken' },
111
+ { '@_key': 'ClearTextPassword', '@_value': token }
112
+ ]
113
+ };
114
+ }
115
+ }
116
+ await writeNugetConfig(config, configPath);
117
+ }
118
+ /**
119
+ * Write NuGet.config
120
+ */
121
+ async function writeNugetConfig(config, configPath) {
122
+ const nugetPath = getNugetConfigPath(configPath);
123
+ const dir = path_1.default.dirname(nugetPath);
124
+ // Ensure directory exists
125
+ await promises_1.default.mkdir(dir, { recursive: true });
126
+ const builder = new fast_xml_parser_1.XMLBuilder({
127
+ ignoreAttributes: false,
128
+ attributeNamePrefix: '@_',
129
+ format: true,
130
+ indentBy: ' ',
131
+ });
132
+ let xml = builder.build(config);
133
+ // Only add XML declaration if not already present
134
+ if (!xml.startsWith('<?xml')) {
135
+ xml = '<?xml version="1.0" encoding="utf-8"?>\n' + xml;
136
+ }
137
+ await promises_1.default.writeFile(nugetPath, xml, 'utf-8');
138
+ }
139
+ /**
140
+ * Restore from backup
141
+ */
142
+ async function restoreNugetFromBackup(backupPath, configPath) {
143
+ const nugetPath = getNugetConfigPath(configPath);
144
+ await promises_1.default.copyFile(backupPath, nugetPath);
145
+ }
@@ -0,0 +1,150 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Package feed type
4
+ */
5
+ declare const FeedTypeSchema: z.ZodDefault<z.ZodEnum<{
6
+ npm: "npm";
7
+ nuget: "nuget";
8
+ }>>;
9
+ export type FeedType = z.infer<typeof FeedTypeSchema>;
10
+ /**
11
+ * Individual registry entry (npm or NuGet feed)
12
+ */
13
+ declare const RegistryEntrySchema: z.ZodObject<{
14
+ scope: z.ZodString;
15
+ url: z.ZodURL;
16
+ feedType: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
17
+ npm: "npm";
18
+ nuget: "nuget";
19
+ }>>>;
20
+ }, z.core.$strip>;
21
+ /**
22
+ * Auth group - discriminated union of provider-specific schemas
23
+ */
24
+ declare const AuthGroupSchema: z.ZodUnion<readonly [z.ZodObject<{
25
+ provider: z.ZodLiteral<"azure-devops">;
26
+ clientId: z.ZodString;
27
+ tenantId: z.ZodString;
28
+ registries: z.ZodArray<z.ZodObject<{
29
+ scope: z.ZodString;
30
+ url: z.ZodURL;
31
+ feedType: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
32
+ npm: "npm";
33
+ nuget: "nuget";
34
+ }>>>;
35
+ }, z.core.$strip>>;
36
+ redirectUri: z.ZodOptional<z.ZodString>;
37
+ }, z.core.$strip>, z.ZodObject<{
38
+ provider: z.ZodLiteral<"github">;
39
+ clientId: z.ZodString;
40
+ tenantId: z.ZodOptional<z.ZodNever>;
41
+ registries: z.ZodArray<z.ZodObject<{
42
+ scope: z.ZodString;
43
+ url: z.ZodURL;
44
+ feedType: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
45
+ npm: "npm";
46
+ nuget: "nuget";
47
+ }>>>;
48
+ }, z.core.$strip>>;
49
+ }, z.core.$strip>]>;
50
+ /**
51
+ * Configuration schema for npm registries
52
+ * Loaded from URL or local file
53
+ */
54
+ export declare const RegistryConfigSchema: z.ZodObject<{
55
+ authGroups: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
56
+ provider: z.ZodLiteral<"azure-devops">;
57
+ clientId: z.ZodString;
58
+ tenantId: z.ZodString;
59
+ registries: z.ZodArray<z.ZodObject<{
60
+ scope: z.ZodString;
61
+ url: z.ZodURL;
62
+ feedType: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
63
+ npm: "npm";
64
+ nuget: "nuget";
65
+ }>>>;
66
+ }, z.core.$strip>>;
67
+ redirectUri: z.ZodOptional<z.ZodString>;
68
+ }, z.core.$strip>, z.ZodObject<{
69
+ provider: z.ZodLiteral<"github">;
70
+ clientId: z.ZodString;
71
+ tenantId: z.ZodOptional<z.ZodNever>;
72
+ registries: z.ZodArray<z.ZodObject<{
73
+ scope: z.ZodString;
74
+ url: z.ZodURL;
75
+ feedType: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
76
+ npm: "npm";
77
+ nuget: "nuget";
78
+ }>>>;
79
+ }, z.core.$strip>>;
80
+ }, z.core.$strip>]>>>;
81
+ registries: z.ZodOptional<z.ZodArray<z.ZodObject<{
82
+ scope: z.ZodString;
83
+ url: z.ZodURL;
84
+ feedType: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
85
+ npm: "npm";
86
+ nuget: "nuget";
87
+ }>>>;
88
+ }, z.core.$strip>>>;
89
+ }, z.core.$strip>;
90
+ export type RegistryConfig = z.infer<typeof RegistryConfigSchema>;
91
+ export type AuthGroup = z.infer<typeof AuthGroupSchema>;
92
+ export type Registry = z.infer<typeof RegistryEntrySchema>;
93
+ /**
94
+ * Configuration source tracking
95
+ */
96
+ export interface ConfigSource {
97
+ source: string;
98
+ timestamp: number;
99
+ type: 'url' | 'file';
100
+ authOptions?: ConfigAuthOptions;
101
+ }
102
+ /**
103
+ * Config URL authentication options
104
+ */
105
+ export interface ConfigAuthOptions {
106
+ authEndpoint: string;
107
+ tokenEndpoint?: string;
108
+ clientId: string;
109
+ tokenKind?: 'access' | 'id';
110
+ scope?: string;
111
+ redirectUri?: string;
112
+ }
113
+ /**
114
+ * Init command options
115
+ */
116
+ export interface InitOptions {
117
+ config: string;
118
+ configAuthEndpoint?: string;
119
+ configTokenEndpoint?: string;
120
+ configClientId?: string;
121
+ configTokenKind?: 'access' | 'id';
122
+ configAuthScope?: string;
123
+ configRedirectUri?: string;
124
+ interactive?: boolean;
125
+ force?: boolean;
126
+ dryRun?: boolean;
127
+ }
128
+ /**
129
+ * Update command options
130
+ */
131
+ export interface UpdateOptions {
132
+ config?: string;
133
+ configAuthEndpoint?: string;
134
+ configTokenEndpoint?: string;
135
+ configClientId?: string;
136
+ configTokenKind?: 'access' | 'id';
137
+ configAuthScope?: string;
138
+ configRedirectUri?: string;
139
+ dryRun?: boolean;
140
+ }
141
+ /**
142
+ * Authentication result
143
+ */
144
+ export interface AuthResult {
145
+ scope: string;
146
+ authenticated: boolean;
147
+ method: 'oauth' | 'token';
148
+ error?: string;
149
+ }
150
+ export {};
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RegistryConfigSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ /**
6
+ * Package feed type
7
+ */
8
+ const FeedTypeSchema = zod_1.z.enum(['npm', 'nuget']).default('npm');
9
+ /**
10
+ * Individual registry entry (npm or NuGet feed)
11
+ */
12
+ const RegistryEntrySchema = zod_1.z.object({
13
+ scope: zod_1.z.string().describe('npm scope (e.g., "@org/package") or NuGet source name'),
14
+ url: zod_1.z.url().describe('Registry/Feed URL (Azure DevOps, GitHub, or public)'),
15
+ feedType: FeedTypeSchema.optional().describe('Package feed type (defaults to npm for backward compatibility)'),
16
+ });
17
+ /**
18
+ * Azure DevOps auth group (requires tenantId)
19
+ */
20
+ const AzureDevOpsAuthGroupSchema = zod_1.z.object({
21
+ provider: zod_1.z.literal('azure-devops').describe('Registry provider type'),
22
+ clientId: zod_1.z.string().describe('OAuth client ID for PKCE authentication'),
23
+ tenantId: zod_1.z.string().describe('Azure AD tenant ID (required for azure-devops)'),
24
+ registries: zod_1.z.array(RegistryEntrySchema).min(1).describe('Registries sharing this authentication'),
25
+ redirectUri: zod_1.z.string().optional().describe('Custom redirect URI for PKCE auth (relay support)'),
26
+ });
27
+ /**
28
+ * GitHub auth group (no tenantId required)
29
+ * Note: redirectUri is intentionally omitted because GitHub uses OAuth device flow,
30
+ * which does not require redirect URIs (it uses device codes instead).
31
+ */
32
+ const GitHubAuthGroupSchema = zod_1.z.object({
33
+ provider: zod_1.z.literal('github').describe('Registry provider type'),
34
+ clientId: zod_1.z.string().describe('OAuth client ID for device flow authentication'),
35
+ tenantId: zod_1.z.never().optional().describe('Not used for GitHub'),
36
+ registries: zod_1.z.array(RegistryEntrySchema).min(1).describe('Registries sharing this authentication'),
37
+ });
38
+ /**
39
+ * Auth group - discriminated union of provider-specific schemas
40
+ */
41
+ const AuthGroupSchema = zod_1.z.union([AzureDevOpsAuthGroupSchema, GitHubAuthGroupSchema]);
42
+ /**
43
+ * Configuration schema for npm registries
44
+ * Loaded from URL or local file
45
+ */
46
+ exports.RegistryConfigSchema = zod_1.z.object({
47
+ authGroups: zod_1.z.array(AuthGroupSchema).optional().describe('Registries requiring authentication'),
48
+ registries: zod_1.z.array(RegistryEntrySchema).optional().describe('Public registries without authentication'),
49
+ }).refine((data) => {
50
+ if (!data.authGroups)
51
+ return true;
52
+ // GitHub npm feeds: still limited to one auth group (single token for npm.pkg.github.com)
53
+ const githubGroups = data.authGroups.filter(group => group.provider === 'github');
54
+ if (githubGroups.length > 1) {
55
+ const hasNpmFeeds = githubGroups.some(group => group.registries.some(r => (r.feedType || 'npm') === 'npm'));
56
+ if (hasNpmFeeds) {
57
+ return false;
58
+ }
59
+ }
60
+ return true;
61
+ }, {
62
+ message: 'Only one GitHub auth group is allowed for npm feeds (GitHub uses a single token for npm.pkg.github.com). NuGet feeds can use separate groups.',
63
+ });