101 lines
3.9 KiB
JavaScript
101 lines
3.9 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const CONFIG_PATH = path.resolve(__dirname, '..', 'config.json');
|
|
|
|
function loadConfig() {
|
|
if (!fs.existsSync(CONFIG_PATH)) {
|
|
throw new Error(`Missing config file at ${CONFIG_PATH}`);
|
|
}
|
|
const json = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
return JSON.parse(json);
|
|
}
|
|
|
|
function resolveOutputPath(outputDir, filename) {
|
|
if (!path.isAbsolute(outputDir)) {
|
|
outputDir = path.resolve(process.cwd(), outputDir);
|
|
}
|
|
if (!fs.existsSync(outputDir)) {
|
|
fs.mkdirSync(outputDir, {recursive: true});
|
|
}
|
|
return path.join(outputDir, filename);
|
|
}
|
|
|
|
function validateConfig(config) {
|
|
if (!config || typeof config !== 'object') {
|
|
throw new Error('Missing or invalid configuration object');
|
|
}
|
|
|
|
if (!Array.isArray(config.documents) || config.documents.length === 0) {
|
|
throw new Error('`documents` must be a non-empty array in config.json');
|
|
}
|
|
|
|
const hasApiKeys = Array.isArray(config.apiKeys) && config.apiKeys.length > 0;
|
|
const hasSvcAccounts = Array.isArray(config.serviceAccounts) && config.serviceAccounts.length > 0;
|
|
|
|
// Do not allow both API keys and service accounts to be present simultaneously
|
|
if (hasApiKeys && hasSvcAccounts) {
|
|
throw new Error('Configuration must not include both `apiKeys` and `serviceAccounts`; choose one authentication method');
|
|
}
|
|
|
|
if (!hasApiKeys && !hasSvcAccounts) {
|
|
throw new Error('Configuration must include at least one `apiKeys` or `serviceAccounts`');
|
|
}
|
|
|
|
// Ensure API key values are unique when using apiKeys
|
|
if (hasApiKeys) {
|
|
const extracted = config.apiKeys.map(item => {
|
|
if (typeof item === 'string') return item.trim();
|
|
if (item && typeof item === 'object') return (item.key || item.apiKey || '').toString().trim();
|
|
return '';
|
|
});
|
|
const nonEmpty = extracted.filter(Boolean);
|
|
const unique = new Set(nonEmpty);
|
|
if (unique.size !== nonEmpty.length) {
|
|
throw new Error('`apiKeys` contains duplicate `key` values; ensure all API keys are unique');
|
|
}
|
|
}
|
|
|
|
// Ensure service account credential paths are unique when using serviceAccounts
|
|
if (hasSvcAccounts) {
|
|
// Read and canonicalize the contents of each service account credential
|
|
const svcContents = config.serviceAccounts.map(item => {
|
|
let p = '';
|
|
if (typeof item === 'string') p = item.trim();
|
|
else if (item && typeof item === 'object') p = (item.path || item.file || item.credentialsPath || '').toString().trim();
|
|
if (!p) return '';
|
|
const full = path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
|
|
if (!fs.existsSync(full)) {
|
|
throw new Error(`Service account file not found: ${p}`);
|
|
}
|
|
const raw = fs.readFileSync(full, 'utf8').trim();
|
|
try {
|
|
const parsed = JSON.parse(raw);
|
|
const canonicalize = (obj) => {
|
|
if (obj === null || typeof obj !== 'object') return JSON.stringify(obj);
|
|
if (Array.isArray(obj)) return '[' + obj.map(canonicalize).join(',') + ']';
|
|
const keys = Object.keys(obj).sort();
|
|
return '{' + keys.map(k => JSON.stringify(k) + ':' + canonicalize(obj[k])).join(',') + '}';
|
|
};
|
|
return canonicalize(parsed);
|
|
} catch (e) {
|
|
// Non-JSON files: use raw contents
|
|
return raw;
|
|
}
|
|
}).filter(Boolean);
|
|
|
|
const uniqueSvc = new Set(svcContents);
|
|
if (uniqueSvc.size !== svcContents.length) {
|
|
throw new Error('`serviceAccounts` contains duplicate credential contents; ensure all service account credentials are unique');
|
|
}
|
|
}
|
|
|
|
// Basic validation of documents/sheets structure
|
|
for (const doc of config.documents) {
|
|
if (!doc.documentId) throw new Error('Each document must include a `documentId`');
|
|
if (!Array.isArray(doc.sheets) || doc.sheets.length === 0) throw new Error(`Document ${doc.documentId} must include a non-empty 'sheets' array`);
|
|
}
|
|
}
|
|
|
|
module.exports = {loadConfig, resolveOutputPath, validateConfig};
|