-
Notifications
You must be signed in to change notification settings - Fork 113
Expand file tree
/
Copy pathconfig.ts
More file actions
131 lines (119 loc) · 4.58 KB
/
Copy pathconfig.ts
File metadata and controls
131 lines (119 loc) · 4.58 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
import { existsSync, readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';
import {
DEFAULT_PROFILE,
defaultCredentialsPath,
parseIniFile,
readProfile,
} from './credentials.js';
export interface Config {
apiUrl: string;
apiKey?: string;
profile: string;
}
export interface LoadConfigOptions {
profile?: string;
endpointUrl?: string;
env?: NodeJS.ProcessEnv;
credentialsPath?: string;
/** Settings file override; defaults to `TESTSPRITE_CONFIG_FILE` or `~/.testsprite/config`. */
configPath?: string;
}
const DEFAULT_API_URL = 'https://api.testsprite.com';
/** Treat empty / whitespace-only env values as unset for `??` resolution chains. */
export function normalizeEnvVar(value: string | undefined): string | undefined {
return value?.trim() || undefined;
}
export function defaultConfigPath(): string {
return join(homedir(), '.testsprite', 'config');
}
/**
* Non-credential defaults a user can persist in `~/.testsprite/config`
* (aws-cli's credentials-vs-config split: the key lives in `credentials`,
* settings live here). INI sections are profile names, mirroring the
* credentials file.
*/
export interface ConfigFileSettings {
endpointUrl?: string;
output?: string;
projectId?: string;
}
/** INI key -> settings field. sourceRef: DOCUMENTATION.md configuration table. */
const CONFIG_KEY_TO_FIELD: Record<string, keyof ConfigFileSettings> = {
endpoint_url: 'endpointUrl',
output: 'output',
project_id: 'projectId',
};
export interface ReadConfigFileOptions {
env?: NodeJS.ProcessEnv;
path?: string;
}
/**
* Read the `[profile]` section of the settings config file. Missing or
* unreadable file (or section) yields `{}` so the cascade falls through to
* built-in defaults; the file is optional by design. Reuses the hardened INI
* parser the credentials file uses (null-prototype + proto-key guards).
*/
export function readConfigFileSettings(
profile: string,
options: ReadConfigFileOptions = {},
): ConfigFileSettings {
const env = options.env ?? process.env;
const path = options.path ?? env.TESTSPRITE_CONFIG_FILE ?? defaultConfigPath();
let content: string;
try {
if (!existsSync(path)) return {};
content = readFileSync(path, 'utf-8');
} catch {
// Unreadable settings file: settings are optional, never fatal.
return {};
}
const section = parseIniFile(content)[profile];
if (!section) return {};
const settings: ConfigFileSettings = {};
for (const [rawKey, field] of Object.entries(CONFIG_KEY_TO_FIELD)) {
const value = section[rawKey];
if (typeof value === 'string' && value.length > 0) settings[field] = value;
}
return settings;
}
/**
* Resolves the active profile name and its (apiUrl, apiKey) pair.
*
* Resolution order, highest precedence first:
* profile name: options.profile > env.TESTSPRITE_PROFILE > "default"
* apiKey: env.TESTSPRITE_API_KEY > credentials file profile entry
* apiUrl: options.endpointUrl > env.TESTSPRITE_API_URL > credentials file
* > config file `endpoint_url` > built-in default
*
* Env wins over the credentials file so CI / scripted callers can run without touching
* the user's ~/.testsprite/credentials. The config file sits just above the built-in
* default: it is where a user persists "this machine talks to this endpoint" once.
*/
export function loadConfig(options: LoadConfigOptions = {}): Config {
const env = options.env ?? process.env;
const profile = options.profile ?? normalizeEnvVar(env.TESTSPRITE_PROFILE) ?? DEFAULT_PROFILE;
const credentialsPath = options.credentialsPath ?? defaultCredentialsPath();
const fileEntry = readProfile(profile, { path: credentialsPath });
const settings = readConfigFileSettings(profile, { env, path: options.configPath });
// Empty / whitespace-only env vars are treated as unset so they do not
// short-circuit the `??` chain (e.g. `export TESTSPRITE_API_URL=` or
// `export TESTSPRITE_PROFILE=` in a shell profile). For the profile this
// also avoids a confusing VALIDATION_ERROR: an empty name fails the INI
// section-name guard, so without normalization a blank env var would break
// every command instead of falling back to the default profile. Matches the
// normalization in auth configure and init/setup.
const envApiUrl = normalizeEnvVar(env.TESTSPRITE_API_URL);
const envApiKey = normalizeEnvVar(env.TESTSPRITE_API_KEY);
return {
apiUrl:
options.endpointUrl ??
envApiUrl ??
fileEntry?.apiUrl ??
settings.endpointUrl ??
DEFAULT_API_URL,
apiKey: envApiKey ?? fileEntry?.apiKey,
profile,
};
}