@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.
- package/README.md +271 -0
- package/dist/auth/constants.d.ts +1 -0
- package/dist/auth/constants.js +4 -0
- package/dist/auth/device-flow.d.ts +41 -0
- package/dist/auth/device-flow.js +122 -0
- package/dist/auth/orchestrator.d.ts +6 -0
- package/dist/auth/orchestrator.js +76 -0
- package/dist/auth/pkce-flow.d.ts +9 -0
- package/dist/auth/pkce-flow.js +40 -0
- package/dist/auth/pkce.d.ts +36 -0
- package/dist/auth/pkce.js +286 -0
- package/dist/auth/providers/azure-devops.d.ts +15 -0
- package/dist/auth/providers/azure-devops.js +130 -0
- package/dist/auth/providers/github.d.ts +6 -0
- package/dist/auth/providers/github.js +38 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.js +214 -0
- package/dist/commands/update.d.ts +5 -0
- package/dist/commands/update.js +199 -0
- package/dist/config/auth-token-cache.d.ts +10 -0
- package/dist/config/auth-token-cache.js +11 -0
- package/dist/config/loader.d.ts +9 -0
- package/dist/config/loader.js +214 -0
- package/dist/feed/router.d.ts +13 -0
- package/dist/feed/router.js +58 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +49 -0
- package/dist/npmrc/manager.d.ts +19 -0
- package/dist/npmrc/manager.js +142 -0
- package/dist/nuget/manager.d.ts +40 -0
- package/dist/nuget/manager.js +145 -0
- package/dist/types/config.d.ts +150 -0
- package/dist/types/config.js +63 -0
- package/package.json +41 -0
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
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
|
+
});
|