@sprucelabs/spruce-cli
Version:
Command line interface for building Spruce skills.
225 lines • 8.48 kB
JavaScript
;
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