'use strict'; var yaml = require('js-yaml'); var commander = require('commander'); var fs = require('fs'); var http = require('http'); var https = require('https'); var node_child_process = require('node:child_process'); var crossSpawn = require('cross-spawn'); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; function parseKeyValueFormat(fileContent) { return parseEnvTypeFile(fileContent); } function parseYmlFormat(fileContent) { return yaml.load(fileContent); } function parseJsonFormat(fileContent) { return JSON.parse(fileContent); } function parseEnvTypeFile(src) { var obj = {}; // 라인 별로 분리하여 처리 var lines = src.replace(/\r\n?/g, '\n').split('\n'); for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) { var line = lines_1[_i]; var match = line.match(/^(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?$/); if (match) { var key = match[1]; var value = match[2] || ''; value = value.trim(); // Detect surrounding quotes var firstChar = value[0]; if (firstChar === '"' || firstChar === "'") { // Remove the first and last character (quotes) value = value.substring(1, value.length - 1); // Handle escape sequences for double quotes if (firstChar === '"') { value = value.replace(/\\"/g, '"').replace(/\\n/g, '\n').replace(/\\r/g, '\r'); } else if (firstChar === "'") { value = value.replace(/\\'/g, "'"); } } obj[key] = value; } } return obj; } /** * Parse the command line arguments * @param args */ function parseArgs(args) { var packageJson = require('../package.json'); var program = new commander.Command(); return program .version(packageJson.version, '-v, --version') .usage('[options] [...args]') .option('-e, --environment [env]', 'Set the cloud-config environment like dev, staging, prod', '') .parse(__spreadArray(['_', '_'], args, true)); } function getFileContent(path) { return fs.readFileSync(path).toString(); } function getUrlContent(url, headers) { return new Promise(function (resolve, reject) { var lib = url.startsWith('https') ? https : http; var options = { headers: __assign({}, headers) }; var request = lib.get(url, options, function (response) { if (response.statusCode && (response.statusCode < 200 || response.statusCode > 299)) { reject(new Error('Failed to load page, status code: ' + response.statusCode)); } var body = []; response.on('data', function (chunk) { return body.push(chunk); }); response.on('end', function () { return resolve(body.join('')); }); }); request.on('error', function (err) { console.log('Error: ', err.message); reject(err); }); }); } var GitFetcher = /** @class */ (function () { function GitFetcher(param, parser) { this.param = param; this.parser = parser; } GitFetcher.prototype.fetchConfigFromRemote = function () { return __awaiter(this, void 0, void 0, function () { var owner, repo, path, branch, token, url, headers, response; return __generator(this, function (_a) { switch (_a.label) { case 0: owner = this.param.owner; repo = this.param.repo; path = this.param.path; branch = this.param.branch ? "?ref=".concat(this.param.branch) : ''; token = this.param.token; url = "https://api.github.com/repos/".concat(owner, "/").concat(repo, "/contents/").concat(path).concat(branch); headers = { Authorization: "Bearer ".concat(token), Accept: 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28', 'User-Agent': 'NodejsCloudConfigModule' }; return [4 /*yield*/, getUrlContent(url, headers)]; case 1: response = _a.sent(); this.response = JSON.parse(response); return [2 /*return*/]; } }); }); }; GitFetcher.prototype.parseToMapData = function () { if (!this.response) { throw new Error('fetchConfigFromRemote should be called before parseToMapData'); } var envData = this.decodeContent(this.response.content); return this.parser(envData); }; GitFetcher.prototype.decodeContent = function (content) { return Buffer.from(content, 'base64').toString('utf8'); }; return GitFetcher; }()); var UrlFetcher = /** @class */ (function () { function UrlFetcher(param, parser) { this.param = param; this.parser = parser; } UrlFetcher.prototype.fetchConfigFromRemote = function () { return __awaiter(this, void 0, void 0, function () { var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = this; return [4 /*yield*/, getUrlContent(this.param.url)]; case 1: _a.response = _b.sent(); return [2 /*return*/]; } }); }); }; UrlFetcher.prototype.parseToMapData = function () { if (!this.response) { throw new Error('fetchConfigFromRemote should be called before parseToMapData'); } return this.parser(this.response); }; return UrlFetcher; }()); var SpringFetcher = /** @class */ (function () { function SpringFetcher(param, parser) { this.param = param; this.parser = parser; } SpringFetcher.prototype.fetchConfigFromRemote = function () { return __awaiter(this, void 0, void 0, function () { var springCloudConfigRequestUrl, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: springCloudConfigRequestUrl = this.param.label ? "".concat(this.param.serverUrl, "/").concat(this.param.applicationName, "/").concat(this.param.profile, "/").concat(this.param.label) : "".concat(this.param.serverUrl, "/").concat(this.param.applicationName, "/").concat(this.param.profile); _a = this; return [4 /*yield*/, getUrlContent(springCloudConfigRequestUrl)]; case 1: _a.response = _b.sent(); return [2 /*return*/]; } }); }); }; SpringFetcher.prototype.parseToMapData = function () { if (!this.response) { throw new Error('fetchConfigFromRemote should be called before parseToMapData'); } var springApplicationData = this.parser(this.response); return springApplicationData.propertySources .reduce(function (acc, cur) { return __assign(__assign({}, acc), cur.source); }, {}); }; return SpringFetcher; }()); var GitCliFetcher = /** @class */ (function () { function GitCliFetcher(param, parser) { this.param = param; this.parser = parser; } GitCliFetcher.prototype.fetchConfigFromRemote = function () { return __awaiter(this, void 0, void 0, function () { var owner, repo, path, cliCommand, output; return __generator(this, function (_a) { owner = this.param.owner; repo = this.param.repo; path = this.param.path; this.param.branch ? "?ref=".concat(this.param.branch) : ''; cliCommand = "gh api -H \"Accept: application/vnd.github+json\" -H \"X-GitHub-Api-Version: 2022-11-28\" /repos/".concat(owner, "/").concat(repo, "/contents/").concat(path, "\n "); try { output = node_child_process.execSync(cliCommand); // 결과 출력 this.response = JSON.parse(output.toString()); } catch (error) { // 에러 처리 console.error("Error: ".concat(error)); } return [2 /*return*/]; }); }); }; GitCliFetcher.prototype.parseToMapData = function () { if (!this.response) { throw new Error('fetchConfigFromRemote should be called before parseToMapData'); } var envData = this.decodeContent(this.response.content); return this.parser(envData); }; GitCliFetcher.prototype.decodeContent = function (content) { return Buffer.from(content, 'base64').toString('utf8'); }; return GitCliFetcher; }()); // Fetcher 인터페이스를 정의합니다. // Fetcher 구현체를 생성하는 팩토리 함수를 만듭니다. function createFetcher(config) { var type = config.remote.type.toLocaleLowerCase(); var param = config.remote.param; var parser = getParser(config.remote.format); switch (type) { case 'git': return new GitFetcher(param, parser); case 'gitcli': return new GitCliFetcher(param, parser); case 'url': return new UrlFetcher(param, parser); case 'spring': return new SpringFetcher(param, parseJsonFormat); default: throw new Error("Unsupported fetcher type: ".concat(type)); } } /** * This function creates a parser function based on the format name. * @param formatName */ function getParser(formatName) { switch (formatName) { case 'json': return parseJsonFormat; case 'yml': return parseYmlFormat; case 'yaml': return parseYmlFormat; case 'key=value': return parseKeyValueFormat; case 'env': return parseKeyValueFormat; default: return parseJsonFormat; } } function loadEnv() { return __awaiter(this, void 0, void 0, function () { var cloudConfigFilename, cloudConfigFilePath, config, fetcher, envVariables, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: console.log('Loading env from remote...'); cloudConfigFilename = getCloudConfigFilenameByNodeEnv(); cloudConfigFilePath = getFileContent("".concat(process.cwd(), "/").concat(cloudConfigFilename)); config = parseYmlFormat(cloudConfigFilePath); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); fetcher = createFetcher(config); return [4 /*yield*/, fetcher.fetchConfigFromRemote()]; case 2: _a.sent(); envVariables = fetcher.parseToMapData(); if (config.remote.debug) { console.log('Fetched env:', envVariables); } console.log("Successfully loaded env from ".concat(config.remote.type, ":"), config.remote.param); return [2 /*return*/, envVariables]; case 3: error_1 = _a.sent(); console.error("Error fetching the env file: ".concat(error_1)); return [3 /*break*/, 4]; case 4: return [2 /*return*/]; } }); }); } function getCloudConfigFilenameByNodeEnv() { var env = process.env.CLOUD_CONFIG_ENV ? ".".concat(process.env.CLOUD_CONFIG_ENV) : process.env.NODE_ENV ? ".".concat(process.env.NODE_ENV) : ''; return ".cloud-config".concat(env, ".yml"); } var CloudConfig = /** @class */ (function () { function CloudConfig() { } CloudConfig.load = function (callback) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, new Promise(function (resolve, reject) { loadEnv().then(function (remoteConfig) { if (callback) { callback(remoteConfig); } resolve(remoteConfig); }).catch(function (error) { reject(error); }); })]; }); }); }; CloudConfig.bind = function (envVariables, target) { for (var _i = 0, _a = Object.keys(envVariables); _i < _a.length; _i++) { var key = _a[_i]; target[key] = envVariables[key]; } }; CloudConfig.CLI = function (args) { var program = parseArgs(args); var useShell = !!program.useShell; process.env.CLOUD_CONFIG_ENV = program.opts().environment; this.load(function (config) { var command = program.args[0]; if (command !== undefined) { var commandArgs = args.splice(args.indexOf(command) + 1); var env = Object.assign({}, process.env, config); // Run the command crossSpawn(command, commandArgs, { stdio: 'inherit', shell: useShell, env: env }); } }); }; return CloudConfig; }()); module.exports = CloudConfig;