-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathconfig.ts
More file actions
280 lines (253 loc) · 8.29 KB
/
config.ts
File metadata and controls
280 lines (253 loc) · 8.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import type {AuthMethod} from '../auth/types.js';
import {ALL_AUTH_METHODS} from '../auth/types.js';
import {getLogger} from '../logging/logger.js';
// Re-export for convenience
export type {AuthMethod};
export {ALL_AUTH_METHODS};
export interface ResolvedConfig {
hostname?: string;
webdavHostname?: string;
codeVersion?: string;
username?: string;
password?: string;
clientId?: string;
clientSecret?: string;
scopes?: string[];
shortCode?: string;
mrtApiKey?: string;
/** MRT project slug */
mrtProject?: string;
/** MRT environment name (e.g., staging, production) */
mrtEnvironment?: string;
/** MRT API origin URL override */
mrtOrigin?: string;
instanceName?: string;
/** Allowed authentication methods (in priority order). If not set, all methods are allowed. */
authMethods?: AuthMethod[];
}
/**
* dw.json single config structure
*/
interface DwJsonConfig {
name?: string;
active?: boolean;
hostname?: string;
'code-version'?: string;
username?: string;
password?: string;
'client-id'?: string;
'client-secret'?: string;
'oauth-scopes'?: string[];
/** SCAPI short code (multiple key formats supported) */
shortCode?: string;
'short-code'?: string;
'scapi-shortcode'?: string;
secureHostname?: string;
'secure-server'?: string;
/** Allowed authentication methods (in priority order) */
'auth-methods'?: AuthMethod[];
/** MRT project slug */
mrtProject?: string;
/** MRT environment name (e.g., staging, production) */
mrtEnvironment?: string;
}
/**
* dw.json with multi-config support
*/
interface DwJsonMultiConfig extends DwJsonConfig {
configs?: DwJsonConfig[];
}
export interface LoadConfigOptions {
instance?: string;
configPath?: string;
}
/**
* Finds dw.json by walking up from current directory.
*/
export function findDwJson(startDir: string = process.cwd()): string | null {
const logger = getLogger();
let dir = startDir;
const root = path.parse(dir).root;
logger.trace({startDir}, '[Config] Searching for dw.json');
while (dir !== root) {
const dwJsonPath = path.join(dir, 'dw.json');
if (fs.existsSync(dwJsonPath)) {
logger.trace({path: dwJsonPath}, '[Config] Found dw.json');
return dwJsonPath;
}
dir = path.dirname(dir);
}
logger.trace('[Config] No dw.json found');
return null;
}
/**
* Maps dw.json fields to ResolvedConfig
*/
function mapDwJsonToConfig(json: DwJsonConfig): ResolvedConfig {
return {
hostname: json.hostname,
webdavHostname: json.secureHostname || json['secure-server'],
codeVersion: json['code-version'],
username: json.username,
password: json.password,
clientId: json['client-id'],
clientSecret: json['client-secret'],
scopes: json['oauth-scopes'],
shortCode: json.shortCode || json['short-code'] || json['scapi-shortcode'],
instanceName: json.name,
authMethods: json['auth-methods'],
mrtProject: json.mrtProject,
mrtEnvironment: json.mrtEnvironment,
};
}
/**
* Loads configuration from dw.json file.
* Supports multi-config format with 'configs' array.
*/
function loadDwJson(instanceName?: string, configPath?: string): ResolvedConfig {
const logger = getLogger();
const dwJsonPath = configPath || findDwJson();
if (!dwJsonPath || !fs.existsSync(dwJsonPath)) {
logger.trace('[Config] No dw.json to load');
return {};
}
try {
const content = fs.readFileSync(dwJsonPath, 'utf8');
const json = JSON.parse(content) as DwJsonMultiConfig;
let selectedConfig: DwJsonConfig = json;
let selectedName = json.name || 'root';
// Handle multi-config format
if (Array.isArray(json.configs)) {
if (instanceName) {
// Find by instance name
const found = json.name === instanceName ? json : json.configs.find((c) => c.name === instanceName);
if (found) {
selectedConfig = found;
selectedName = found.name || instanceName;
}
} else if (json.active === false) {
// Root config is inactive, find active one in configs
const activeConfig = json.configs.find((c) => c.active === true);
if (activeConfig) {
selectedConfig = activeConfig;
selectedName = activeConfig.name || 'active';
}
}
// Otherwise use root config
}
logger.trace({path: dwJsonPath, instance: selectedName}, '[Config] Loaded dw.json');
return mapDwJsonToConfig(selectedConfig);
} catch (error) {
logger.trace({path: dwJsonPath, error}, '[Config] Failed to parse dw.json');
return {};
}
}
/**
* Merges config sources with precedence: flags (includes env via OCLIF) > dw.json
*
* Note: Environment variables are handled by OCLIF's flag parsing with the `env`
* property on each flag definition. By the time flags reach this function, they
* already contain env var values where applicable.
*/
function mergeConfigs(
flags: Partial<ResolvedConfig>,
dwJson: ResolvedConfig,
options: LoadConfigOptions,
): ResolvedConfig {
return {
hostname: flags.hostname || dwJson.hostname,
webdavHostname: flags.webdavHostname || dwJson.webdavHostname,
codeVersion: flags.codeVersion || dwJson.codeVersion,
username: flags.username || dwJson.username,
password: flags.password || dwJson.password,
clientId: flags.clientId || dwJson.clientId,
clientSecret: flags.clientSecret || dwJson.clientSecret,
scopes: flags.scopes || dwJson.scopes,
shortCode: flags.shortCode || dwJson.shortCode,
mrtApiKey: flags.mrtApiKey,
mrtProject: flags.mrtProject || dwJson.mrtProject,
mrtEnvironment: flags.mrtEnvironment || dwJson.mrtEnvironment,
mrtOrigin: flags.mrtOrigin,
instanceName: dwJson.instanceName || options.instance,
authMethods: flags.authMethods || dwJson.authMethods,
};
}
/**
* Loads configuration with precedence: CLI flags/env vars > dw.json
*
* OCLIF handles environment variables automatically via flag `env` properties.
* The flags parameter already contains resolved env var values.
*/
export function loadConfig(flags: Partial<ResolvedConfig> = {}, options: LoadConfigOptions = {}): ResolvedConfig {
const dwJsonConfig = loadDwJson(options.instance, options.configPath);
return mergeConfigs(flags, dwJsonConfig, options);
}
/**
* Mobify config file structure (~/.mobify)
*/
interface MobifyConfig {
username?: string;
api_key?: string;
}
/**
* Result from loading mobify config
*/
export interface MobifyConfigResult {
apiKey?: string;
username?: string;
}
/**
* Loads MRT API key from ~/.mobify config file.
*
* The mobify config file is a JSON file located at ~/.mobify containing:
* ```json
* {
* "username": "user@example.com",
* "api_key": "your-api-key"
* }
* ```
*
* When a cloudOrigin is provided, looks for ~/.mobify--[cloudOrigin] instead.
* For example, if cloudOrigin is "https://cloud-staging.mobify.com", the file
* would be ~/.mobify--cloud-staging.mobify.com
*
* @param cloudOrigin - Optional cloud origin URL to determine which config file to read
* @returns The API key and username if found, undefined otherwise
*/
export function loadMobifyConfig(cloudOrigin?: string): MobifyConfigResult {
const logger = getLogger();
let mobifyPath: string;
if (cloudOrigin) {
// Extract hostname from origin URL for the config file suffix
try {
const url = new URL(cloudOrigin);
mobifyPath = path.join(os.homedir(), `.mobify--${url.hostname}`);
} catch {
// If URL parsing fails, use the origin as-is
mobifyPath = path.join(os.homedir(), `.mobify--${cloudOrigin}`);
}
} else {
mobifyPath = path.join(os.homedir(), '.mobify');
}
logger.trace({path: mobifyPath}, '[Config] Checking for mobify config');
if (!fs.existsSync(mobifyPath)) {
logger.trace({path: mobifyPath}, '[Config] No mobify config found');
return {};
}
try {
const content = fs.readFileSync(mobifyPath, 'utf8');
const config = JSON.parse(content) as MobifyConfig;
const hasApiKey = Boolean(config.api_key);
logger.trace({path: mobifyPath, hasApiKey, username: config.username}, '[Config] Loaded mobify config');
return {
apiKey: config.api_key,
username: config.username,
};
} catch (error) {
logger.trace({path: mobifyPath, error}, '[Config] Failed to parse mobify config');
return {};
}
}