Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1/**
2 * Loads and validates the adopters' integrations config file.
3 * Path: INTEGRATIONS_CONFIG_PATH env or cwd/integrations.config.json.
4 */
5
6import { existsSync, readFileSync } from 'fs';
7import path from 'path';
8
9import type { CoopIntegrationsConfig } from '@roostorg/types';
10
11import { jsonParse } from '../../utils/encoding.js';
12import type { JsonOf } from '../../utils/encoding.js';
13
14function getConfigPath(): string {
15 const envPath = process.env.INTEGRATIONS_CONFIG_PATH;
16 if (envPath != null && envPath !== '') {
17 return path.isAbsolute(envPath) ? envPath : path.join(process.cwd(), envPath);
18 }
19 const cwdPath = path.join(process.cwd(), 'integrations.config.json');
20 // eslint-disable-next-line security/detect-non-literal-fs-filename -- path from cwd/env, not user input
21 if (existsSync(cwdPath)) return cwdPath;
22 // When started from repo root (e.g. npm run start), cwd has no integrations.config.json; try server/
23 const serverPath = path.join(process.cwd(), 'server', 'integrations.config.json');
24 // eslint-disable-next-line security/detect-non-literal-fs-filename -- path from cwd, not user input
25 if (existsSync(serverPath)) return serverPath;
26 return cwdPath;
27}
28
29/**
30 * Returns the path to the integrations config file (for resolving relative package specs).
31 */
32export function getIntegrationsConfigPath(): string {
33 return getConfigPath();
34}
35
36/**
37 * Loads CoopIntegrationsConfig from the configured path.
38 * Returns { integrations: [] } if the file is missing (built-ins only).
39 */
40export function loadIntegrationsConfig(): CoopIntegrationsConfig {
41 const configPath = getConfigPath();
42 try {
43 // eslint-disable-next-line security/detect-non-literal-fs-filename -- path from getConfigPath (env/cwd), not user input
44 const raw = readFileSync(configPath, 'utf-8');
45 const parsed = jsonParse(raw as JsonOf<Record<string, unknown>>);
46 if (typeof parsed !== 'object') {
47 return { integrations: [] };
48 }
49 const o = parsed;
50 const integrations = Array.isArray(o.integrations) ? o.integrations : [];
51 const entries = integrations.filter(
52 (e): e is { package: string; enabled?: boolean; config?: Record<string, unknown> } =>
53 e != null && typeof e === 'object' && typeof (e as Record<string, unknown>).package === 'string',
54 );
55 return { integrations: entries };
56 } catch (err: unknown) {
57 if (err != null && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {
58 return { integrations: [] };
59 }
60 throw err;
61 }
62}