UNPKG

@sprucelabs/spruce-cli

Version:

Command line interface for building Spruce skills.

225 lines • 8.48 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const spruce_skill_utils_1 = require("@sprucelabs/spruce-skill-utils"); const md5_1 = __importDefault(require("md5")); const SpruceError_1 = __importDefault(require("../errors/SpruceError")); class ImportService { cwd; divider = '## SPRUCE-CLI DIVIDER ##'; errorDivider = '## SPRUCE-CLI ERROR DIVIDER ##'; static cachedImports = {}; static importCacheDir = spruce_skill_utils_1.diskUtil.createTempDir('import-service'); command; static isCachingEnabled; constructor(options) { this.cwd = options.cwd; this.command = options.command; } importAll = async (file) => { if (!ImportService.isCachingEnabled) { return this.importAllUncached(file); } const { hash, fileContents } = this.pullHashAndContents(file); if (!this.hasFileChanged(hash)) { const isDirty = this.haveImportsChanged(file, fileContents); if (!isDirty && ImportService.cachedImports[hash]) { return ImportService.cachedImports[hash]; } else if (!isDirty) { ImportService.cachedImports[hash] = this.importAllCached(file); return ImportService.cachedImports[hash]; } } ImportService.cachedImports[hash] = this.importAllUncached(file); const response = (await ImportService.cachedImports[hash]); this.writeCacheFile(hash, response); return response; }; haveImportsChanged(originalFilepath, contents) { let changed = false; let importMatches; // only check files that start with dot because they are local and we can actually only check local files const regex = originalFilepath.search(/\.js$/i) > -1 ? /require\(['"](\..*?)['"]\)/gis : /import.*["'](\..*?)["']/gis; while ((importMatches = regex.exec(contents)) !== null) { try { const match = importMatches[1]; const dir = path_1.default.dirname(originalFilepath); const nodeResolved = require.resolve(path_1.default.join(dir, match)); const { hash } = this.pullHashAndContents(nodeResolved); if (this.hasFileChanged(hash)) { changed = true; } } catch { changed = true; } } return changed; } writeCacheFile(hash, contents) { const destination = this.resolveCacheFile(hash); spruce_skill_utils_1.diskUtil.writeFile(destination, JSON.stringify(contents)); } importAllCached(file) { const { hash } = this.pullHashAndContents(file); const cacheFile = this.resolveCacheFile(hash); const contents = spruce_skill_utils_1.diskUtil.readFile(cacheFile); return JSON.parse(contents); } importAllUncached = async (file) => { let defaultImported; if (!spruce_skill_utils_1.diskUtil.doesFileExist(file)) { throw new SpruceError_1.default({ code: 'FAILED_TO_IMPORT', file, friendlyMessage: `I couldn't import ${file} because I couldn't find it!`, }); } let args = [ '-e', `"try { const imported = require('${file}');console.log('${this.divider}');console.log(JSON.stringify(imported)); } catch(err) { console.log('${this.errorDivider}');console.log(err.options ? err.toString() : err.stack); }"`, ]; if (file.search(/\.ts$/i) > -1) { args = [ '-r', 'ts-node/register', '-r', 'tsconfig-paths/register', ...args, ]; } try { const { stdout } = await this.command.execute('node', { args, }); const successParts = stdout.split(this.divider); const errParts = stdout.split(this.errorDivider); if (errParts.length > 1) { const proxyError = this.buildErrorFromExecuteResponse(errParts, file, stdout); throw proxyError; } else { defaultImported = JSON.parse(successParts[1]); } } catch (err) { if (err instanceof SpruceError_1.default) { throw err; } else { throw new SpruceError_1.default({ code: 'FAILED_TO_IMPORT', file, originalError: err, }); } } return defaultImported; }; importDefault = async (file) => { const imported = await this.importAll(file); return imported.default; }; buildErrorFromExecuteResponse(errParts, file, stdout) { let err = {}; try { err = JSON.parse(errParts[1]); if (!err.options) { throw Error('Capture and reported below'); } } catch { err = { options: { code: 'FAILED_TO_IMPORT', file, friendlyMessage: `Unknown error from import, output was: \n\n"${errParts[1] ?? stdout}"`, }, }; } const proxyError = new SpruceError_1.default(err.options); if (err.stack) { proxyError.stack = err.stack; } return proxyError; } async bulkImport(files) { if (files.length === 0) { return []; } const filepath = spruce_skill_utils_1.diskUtil.resolvePath(this.cwd, '.tmp'); const filename = 'bulk-import-' + new Date().getTime() + '.ts'; let imports = ``; let exports = `export default [\n`; let idx = 0; for (const file of files) { const relative = path_1.default .relative(filepath, file) .replace(path_1.default.extname(file), ''); imports += `import { default as import${idx}} from '${relative}'\n`; exports += `import${idx},\n`; idx++; } exports += ']'; const contents = imports + `\n\n` + exports; const fullPath = path_1.default.join(filepath, filename); spruce_skill_utils_1.diskUtil.writeFile(fullPath, contents); try { const results = (await this.importDefault(fullPath)); return results; } catch (err) { // if something failed, lets load one at a time until we find the one that failes for (const file of files) { const imported = await this.importDefault(file); if (!imported) { throw new SpruceError_1.default({ code: 'FAILED_TO_IMPORT', file, friendlyMessage: `Looks like ${file} does not export default.`, }); } } throw err; } finally { spruce_skill_utils_1.diskUtil.deleteDir(filepath); } } static clearCache() { ImportService.cachedImports = {}; spruce_skill_utils_1.diskUtil.deleteDir(this.cacheDir()); } static setCacheDir(cacheDir) { this.importCacheDir = cacheDir; } static disableCache() { this.isCachingEnabled = false; } static enableCaching() { this.isCachingEnabled = true; } hasFileChanged(hash) { const resolvedFilePath = this.resolveCacheFile(hash); return !spruce_skill_utils_1.diskUtil.doesFileExist(resolvedFilePath); } resolveCacheFile(hash) { return spruce_skill_utils_1.diskUtil.resolvePath(ImportService.cacheDir(), hash + '.json'); } static cacheDir() { return this.importCacheDir; } pullHashAndContents(file) { const fileContents = spruce_skill_utils_1.diskUtil.readFile(file); const hash = (0, md5_1.default)(fileContents); return { hash, fileContents }; } } exports.default = ImportService; //# sourceMappingURL=ImportService.js.map