@@ -25,8 +25,55 @@ import { getConfigFile } from './file';
2525import { validateConfig } from './validators' ;
2626import { handleErrorAndLog , handleErrorAndThrow } from '../utils/errors' ;
2727
28+ // Deprecated compatibility fields are still optional because the defaults do not set them.
29+ type OptionalTopLevelConfigKey = 'proxyUrl' | 'sslCertPemPath' | 'sslKeyPemPath' ;
30+ type RequiredTopLevelConfigKey = Exclude < keyof GitProxyConfig , OptionalTopLevelConfigKey > ;
31+
32+ export type FullGitProxyConfig = Required < Omit < GitProxyConfig , OptionalTopLevelConfigKey > > &
33+ Pick < GitProxyConfig , OptionalTopLevelConfigKey > ;
34+
35+ const REQUIRED_TOP_LEVEL_CONFIG_KEYS = [
36+ 'api' ,
37+ 'apiAuthentication' ,
38+ 'attestationConfig' ,
39+ 'authentication' ,
40+ 'authorisedList' ,
41+ 'commitConfig' ,
42+ 'configurationSources' ,
43+ 'contactEmail' ,
44+ 'cookieSecret' ,
45+ 'csrfProtection' ,
46+ 'domains' ,
47+ 'plugins' ,
48+ 'privateOrganizations' ,
49+ 'rateLimit' ,
50+ 'sessionMaxAgeHours' ,
51+ 'sink' ,
52+ 'tempPassword' ,
53+ 'tls' ,
54+ 'uiRouteAuth' ,
55+ 'urlShortener' ,
56+ ] as const satisfies readonly RequiredTopLevelConfigKey [ ] ;
57+
58+ type MissingRequiredTopLevelConfigKeys = Exclude <
59+ RequiredTopLevelConfigKey ,
60+ ( typeof REQUIRED_TOP_LEVEL_CONFIG_KEYS ) [ number ]
61+ > ;
62+ type AssertNever < T extends never > = T ;
63+ type _RequiredTopLevelConfigKeysAreExhaustive = AssertNever < MissingRequiredTopLevelConfigKeys > ;
64+
65+ function assertHasRequiredTopLevelConfig (
66+ config : GitProxyConfig ,
67+ ) : asserts config is FullGitProxyConfig {
68+ const missingKeys = REQUIRED_TOP_LEVEL_CONFIG_KEYS . filter ( ( key ) => config [ key ] === undefined ) ;
69+
70+ if ( missingKeys . length > 0 ) {
71+ throw new Error ( `Missing required top-level configuration values: ${ missingKeys . join ( ', ' ) } ` ) ;
72+ }
73+ }
74+
2875// Cache for current configuration
29- let _currentConfig : GitProxyConfig | null = null ;
76+ let _currentConfig : FullGitProxyConfig | null = null ;
3077let _configLoader : ConfigLoader | null = null ;
3178
3279// Function to invalidate cache - useful for testing
@@ -57,17 +104,17 @@ function cleanUndefinedValues(obj: any): any {
57104
58105/**
59106 * Load and merge default + user configuration with QuickType validation
60- * @return {GitProxyConfig } The merged and validated configuration
107+ * @return {FullGitProxyConfig } The merged and validated configuration
61108 */
62- function loadFullConfiguration ( ) : GitProxyConfig {
109+ function loadFullConfiguration ( ) : FullGitProxyConfig {
63110 if ( _currentConfig ) {
64111 return _currentConfig ;
65112 }
66113
67114 const rawDefaultConfig = Convert . toGitProxyConfig ( JSON . stringify ( defaultSettings ) ) ;
68115
69116 // Clean undefined values from defaultConfig
70- const defaultConfig = cleanUndefinedValues ( rawDefaultConfig ) ;
117+ const defaultConfig = cleanUndefinedValues ( rawDefaultConfig ) as GitProxyConfig ;
71118
72119 let userSettings : Partial < GitProxyConfig > = { } ;
73120 const userConfigFile = process . env . CONFIG_FILE || getConfigFile ( ) ;
@@ -102,12 +149,12 @@ function loadFullConfiguration(): GitProxyConfig {
102149 * Merge configurations with environment variable overrides
103150 * @param {GitProxyConfig } defaultConfig - The default configuration
104151 * @param {Partial<GitProxyConfig> } userSettings - User-provided configuration overrides
105- * @return {GitProxyConfig } The merged configuration
152+ * @return {FullGitProxyConfig } The merged configuration
106153 */
107154function mergeConfigurations (
108155 defaultConfig : GitProxyConfig ,
109156 userSettings : Partial < GitProxyConfig > ,
110- ) : GitProxyConfig {
157+ ) : FullGitProxyConfig {
111158 // Special handling for TLS configuration when legacy fields are used
112159 let tlsConfig = userSettings . tls || defaultConfig . tls ;
113160
@@ -121,7 +168,7 @@ function mergeConfigurations(
121168 } ;
122169 }
123170
124- return {
171+ const config = {
125172 ...defaultConfig ,
126173 ...userSettings ,
127174 // Deep merge for specific objects
@@ -141,6 +188,9 @@ function mergeConfigurations(
141188 userSettings . cookieSecret ||
142189 defaultConfig . cookieSecret ,
143190 } ;
191+
192+ assertHasRequiredTopLevelConfig ( config ) ;
193+ return config ;
144194}
145195
146196// Get configured proxy URL
@@ -152,7 +202,7 @@ export const getProxyUrl = (): string | undefined => {
152202// Gets a list of authorised repositories
153203export const getAuthorisedList = ( ) => {
154204 const config = loadFullConfiguration ( ) ;
155- return config . authorisedList || [ ] ;
205+ return config . authorisedList ;
156206} ;
157207
158208// Gets a list of authorised repositories
@@ -164,7 +214,7 @@ export const getTempPasswordConfig = () => {
164214// Gets the configured data sink, defaults to filesystem
165215export const getDatabase = ( ) => {
166216 const config = loadFullConfiguration ( ) ;
167- const databases = config . sink || [ ] ;
217+ const databases = config . sink ;
168218
169219 for ( const db of databases ) {
170220 if ( db . enabled ) {
@@ -187,7 +237,7 @@ export const getDatabase = () => {
187237 */
188238export const getAuthMethods = ( ) => {
189239 const config = loadFullConfiguration ( ) ;
190- const authSources = config . authentication || [ ] ;
240+ const authSources = config . authentication ;
191241
192242 const enabledAuthMethods = authSources . filter ( ( auth ) => auth . enabled ) ;
193243
@@ -206,7 +256,7 @@ export const getAuthMethods = () => {
206256 */
207257export const getAPIAuthMethods = ( ) => {
208258 const config = loadFullConfiguration ( ) ;
209- const apiAuthSources = config . apiAuthentication || [ ] ;
259+ const apiAuthSources = config . apiAuthentication ;
210260
211261 return apiAuthSources . filter ( ( auth : { enabled : any } ) => auth . enabled ) ;
212262} ;
@@ -227,7 +277,7 @@ export const logConfiguration = () => {
227277
228278export const getAPIs = ( ) => {
229279 const config = loadFullConfiguration ( ) ;
230- return config . api || { } ;
280+ return config . api ;
231281} ;
232282
233283export const getCookieSecret = ( ) : string => {
@@ -242,25 +292,25 @@ export const getCookieSecret = (): string => {
242292
243293export const getSessionMaxAgeHours = ( ) : number => {
244294 const config = loadFullConfiguration ( ) ;
245- return config . sessionMaxAgeHours || 24 ;
295+ return config . sessionMaxAgeHours ;
246296} ;
247297
248298// Get commit related configuration
249299export const getCommitConfig = ( ) => {
250300 const config = loadFullConfiguration ( ) ;
251- return config . commitConfig || { } ;
301+ return config . commitConfig ;
252302} ;
253303
254304// Get attestation related configuration
255305export const getAttestationConfig = ( ) => {
256306 const config = loadFullConfiguration ( ) ;
257- return config . attestationConfig || { } ;
307+ return config . attestationConfig ;
258308} ;
259309
260310// Get private organizations related configuration
261311export const getPrivateOrganizations = ( ) => {
262312 const config = loadFullConfiguration ( ) ;
263- return config . privateOrganizations || [ ] ;
313+ return config . privateOrganizations ;
264314} ;
265315
266316// Get URL shortener
@@ -284,7 +334,7 @@ export const getCSRFProtection = (): boolean | undefined => {
284334// Get loadable push plugins
285335export const getPlugins = ( ) => {
286336 const config = loadFullConfiguration ( ) ;
287- return config . plugins || [ ] ;
337+ return config . plugins ;
288338} ;
289339
290340export const getTLSKeyPemPath = ( ) : string | undefined => {
@@ -304,12 +354,12 @@ export const getTLSEnabled = (): boolean => {
304354
305355export const getDomains = ( ) => {
306356 const config = loadFullConfiguration ( ) ;
307- return config . domains || { } ;
357+ return config . domains ;
308358} ;
309359
310360export const getUIRouteAuth = ( ) => {
311361 const config = loadFullConfiguration ( ) ;
312- return config . uiRouteAuth || { } ;
362+ return config . uiRouteAuth ;
313363} ;
314364
315365export const getRateLimit = ( ) => {
@@ -331,6 +381,7 @@ const handleConfigUpdate = async (newConfig: Configuration) => {
331381 await proxy . stop ( ) ;
332382
333383 // 4. Update config
384+ assertHasRequiredTopLevelConfig ( validatedConfig ) ;
334385 _currentConfig = validatedConfig ;
335386
336387 // 5. Restart services with new config
0 commit comments