UNPKG

@tywalk/pcf-helper

Version:

Command line helper for building and publishing PCF controls to Dataverse.

151 lines (150 loc) 6.67 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getGlobalConfigPath = getGlobalConfigPath; exports.getProjectConfigPath = getProjectConfigPath; exports.loadPcfHelperConfig = loadPcfHelperConfig; exports.resolveProfile = resolveProfile; exports.mergeSessionConfig = mergeSessionConfig; exports.writeProfile = writeProfile; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const os_1 = __importDefault(require("os")); const color_logger_1 = __importDefault(require("@tywalk/color-logger")); const PROJECT_CONFIG_NAME = 'pcf-helper.config.json'; const GLOBAL_CONFIG_DIR_NAME = '.pcf-helper'; const GLOBAL_CONFIG_FILE_NAME = 'config.json'; function getGlobalConfigPath() { return path_1.default.join(os_1.default.homedir(), GLOBAL_CONFIG_DIR_NAME, GLOBAL_CONFIG_FILE_NAME); } function getProjectConfigPath(cwd = process.cwd()) { return path_1.default.join(cwd, PROJECT_CONFIG_NAME); } function readJsonIfExists(filePath) { if (!fs_1.default.existsSync(filePath)) { return undefined; } try { const raw = fs_1.default.readFileSync(filePath, 'utf8'); return JSON.parse(raw); } catch (e) { const message = e instanceof Error ? e.message : String(e); color_logger_1.default.warn(`⚠️ Failed to parse pcf-helper config at ${filePath}: ${message}`); return undefined; } } /** * Loads the global then project pcf-helper configs and returns a merged view. * Project-level values override global values. Profiles from both files are * merged by name (project wins on collision). * * Missing files are treated as empty objects. Malformed JSON produces a * warning and is also treated as empty. */ function loadPcfHelperConfig(cwd = process.cwd()) { var _a, _b, _c, _d, _e, _f, _g; const globalPath = getGlobalConfigPath(); const projectPath = getProjectConfigPath(cwd); const globalCfg = (_a = readJsonIfExists(globalPath)) !== null && _a !== void 0 ? _a : {}; const projectCfg = (_b = readJsonIfExists(projectPath)) !== null && _b !== void 0 ? _b : {}; const sources = []; if (fs_1.default.existsSync(globalPath)) sources.push(globalPath); if (fs_1.default.existsSync(projectPath)) sources.push(projectPath); const mergedProfiles = Object.assign(Object.assign({}, ((_c = globalCfg.profiles) !== null && _c !== void 0 ? _c : {})), ((_d = projectCfg.profiles) !== null && _d !== void 0 ? _d : {})); const mergedSession = Object.assign(Object.assign({}, ((_e = globalCfg.session) !== null && _e !== void 0 ? _e : {})), ((_f = projectCfg.session) !== null && _f !== void 0 ? _f : {})); const merged = { defaultProfile: (_g = projectCfg.defaultProfile) !== null && _g !== void 0 ? _g : globalCfg.defaultProfile, profiles: mergedProfiles, session: mergedSession, }; return { merged, projectPath, globalPath, sources }; } /** * Resolves the profile to use: explicit --profile flag wins, else * `defaultProfile`, else undefined. * * Throws a clear error if a profile name is resolved but the config has no * matching entry. */ function resolveProfile(requestedName, merged) { var _a, _b; const name = requestedName !== null && requestedName !== void 0 ? requestedName : merged.defaultProfile; if (!name) return {}; const profile = (_a = merged.profiles) === null || _a === void 0 ? void 0 : _a[name]; if (!profile) { const available = Object.keys((_b = merged.profiles) !== null && _b !== void 0 ? _b : {}); const list = available.length > 0 ? available.join(', ') : '(none)'; throw new Error(`Profile "${name}" not found. Available profiles: ${list}. Check pcf-helper.config.json (global or project).`); } return { name, profile }; } /** * Layers session config values highest-wins. Undefined values in later layers * do not overwrite defined values in earlier layers — pass layers from * lowest-precedence to highest-precedence. */ function mergeSessionConfig(...layers) { const result = {}; for (const layer of layers) { if (!layer) continue; for (const key of Object.keys(layer)) { const value = layer[key]; if (value !== undefined) { // TS: assignment to union member is safe because we're copying the same key result[key] = value; } } } return result; } /** * Writes a profile into the target pcf-helper config file, merging with any * existing content. Creates the parent directory and the file itself if * neither exists. Writes atomically (temp + rename) so a failed mid-write * cannot corrupt the config. * * Throws if the profile name already exists and `force` is not set. */ function writeProfile(name, profile, options = {}) { var _a, _b, _c; if (!name || !name.trim()) { throw new Error('Profile name is required.'); } const targetPath = options.global ? getGlobalConfigPath() : getProjectConfigPath(options.cwd); const fileExisted = fs_1.default.existsSync(targetPath); const existing = fileExisted ? ((_a = readJsonIfExists(targetPath)) !== null && _a !== void 0 ? _a : {}) : {}; const existingProfile = (_b = existing.profiles) === null || _b === void 0 ? void 0 : _b[name]; if (existingProfile && !options.force) { throw new Error(`Profile "${name}" already exists in ${targetPath}. Pass --force to overwrite.`); } const next = Object.assign(Object.assign({}, existing), { profiles: Object.assign(Object.assign({}, ((_c = existing.profiles) !== null && _c !== void 0 ? _c : {})), { [name]: profile }) }); if (options.setDefault) { next.defaultProfile = name; } // Ensure parent directory exists (matters for ~/.pcf-helper/ on first use). const parentDir = path_1.default.dirname(targetPath); if (!fs_1.default.existsSync(parentDir)) { fs_1.default.mkdirSync(parentDir, { recursive: true }); } // Atomic write: write to a temp file in the same directory, then rename. const tmpPath = `${targetPath}.tmp-${process.pid}-${Date.now()}`; const serialized = JSON.stringify(next, null, 2) + '\n'; fs_1.default.writeFileSync(tmpPath, serialized, 'utf8'); fs_1.default.renameSync(tmpPath, targetPath); return { filePath: targetPath, createdFile: !fileExisted, replacedProfile: !!existingProfile, }; }