samtsc
Version:
This project was put together to make working with the AWS SAM framework easier for developers. It simplifies working with the SAM framework, using real-time updates to lambda functions, layers and resources. This is done by **samtsc** connecting to the
217 lines (189 loc) • 8.52 kB
JavaScript
const { watch, readFileSync, writeFileSync, existsSync, archiveDirectory, mkdir, syncFolder } = require('../file-system');
const { execOnlyShowErrors, folderUpdated, compileTypescript, findTsConfigDir, writeCacheFile, getFileSmash } = require('../tsc-tools');
const { logger } = require('../logger');
const { EventEmitter } = require('events');
const { resolve, relative } = require('path');
function buildPackageJson(source, buildRoot) {
logger.info('Building package.json', source);
const pck = JSON.parse(readFileSync(`${source}/package.json`).toString());
if(pck.dependencies) {
Object.keys(pck.dependencies).forEach(key => {
if(pck.dependencies[key].startsWith('file:')) {
const subPrefix = pck.dependencies[key].slice(5);
const res = resolve(source, subPrefix);
pck.dependencies[key] = `file:${res}`;
} else if(pck.dependencies[key].startsWith('^') || pck.dependencies[key].startsWith('~')) {
pck.dependencies[key] = pck.dependencies[key].slice(1);
}
});
}
mkdir(`${buildRoot}/${source}`);
writeFileSync(`${buildRoot}/${source}/package.json`, JSON.stringify(pck, undefined, 2));
if(pck.dependencies && Object.keys(pck.dependencies).length > 0) {
execOnlyShowErrors('npm i --only=prod', { cwd: `${buildRoot}/${source}`});
}
logger.info('Completed package.json', source);
}
class SAMCompiledDirectory {
constructor(dirPath, samconfig, buildRoot, tempDir = '.build/tmp', moduleType) {
this.path = dirPath;
this.samconfig = samconfig;
this.moduleType = moduleType;
this.buildRoot = buildRoot;
this.tempDir = tempDir;
this.neverPackage = false;
this.events = new EventEmitter();
logger.success('Deployment Library ', dirPath);
const parent = findTsConfigDir(dirPath);
const path = resolve(this.path);
if(!existsSync(this.path)) {
logger.error('CodeUri directory does not exist', dirPath);
throw new Error('Directory does not exist');
}
if(parent != this.path) {
if(parent == null) {
logger.error('No parent tsconfig.json found');
throw new Error('No parent tsconfig.json found')
}
logger.warn('Building tsconfig.json for', dirPath);
writeFileSync(`${this.path}/tsconfig.json`, JSON.stringify({
extends: relative(dirPath, parent || '.') + '/tsconfig.json',
compilerOptions: this.moduleType ? { module: this.moduleType } : undefined
}, undefined, 2));
}
if(!existsSync(`${dirPath}/package.json`)) {
logger.warn('Building package.json for', dirPath);
writeFileSync(`${this.path}/package.json`, JSON.stringify({ name: 'lambda-function', version: '1.0.0' }, undefined, 2));
}
this.tsconfigDir = this.path;
this.loadOutDir();
}
cleanup() {
}
fileEvent(filePath) {
if(filePath.startsWith('package.json.')) {
return;
}
logger.warn('File event occurred', filePath);
this.build(filePath);
}
loadOutDir() {
logger.info('Loading tsconfig in', this.tsconfigDir);
const tsconfigPath = `${this.tsconfigDir}/tsconfig.json`;
const tsconfig = JSON.parse(readFileSync(tsconfigPath).toString());
if(tsconfig) {
if(tsconfig && tsconfig.compilerOptions && tsconfig.compilerOptions.outDir) {
this.outDir = tsconfig.compilerOptions.outDir;
}
if(this.outDir) {
if(!tsconfig.exclude) {
tsconfig.exclude = [];
}
let needsSaving = false;
if(!tsconfig.exclude.find(x => x == `${this.outDir}/**/*`)) {
tsconfig.exclude.push(`${this.outDir}/**/*`);
needsSaving = true;
}
if(!tsconfig.exclude.find(x => x == `node_modules/**/*`)) {
tsconfig.exclude.push(`node_modules/**/*`);
needsSaving = true;
}
if(needsSaving) {
writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
}
}
}
if(!this.outDir) {
this.outDir = '';
}
}
installAtLeastOnce() {
if(this.alreadyInstalled) {
return;
}
this.installDependencies();
}
installDependencies() {
logger.info('installing dependencies', this.path);
const content = JSON.parse(readFileSync(this.path + '/package.json'));
if(content.dependencies && Object.keys(content.dependencies).length > 0) {
if(existsSync(resolve(this.path, 'package-lock.json'))) {
execOnlyShowErrors(`npm ci`, { cwd: this.path });
} else {
execOnlyShowErrors(`npm i`, { cwd: this.path });
}
}
}
buildIfNotPresent() {
const outDir = `${this.buildRoot}/${this.tsconfigDir}/${this.outDir}`;
if(!existsSync(outDir)) {
this.build(undefined, true);
}
}
build(filePath, skipDeploy) {
logger.debug('Starting build');
if(filePath && this.outDir && (filePath.startsWith(this.outDir) || filePath.indexOf('node_modules') >= 0)) {
return;
}
if(!folderUpdated(this.path) &&
existsSync(resolve(this.buildRoot, this.path, 'package.json')) &&
(!this.outDir || existsSync(resolve(this.path, this.outDir)))) {
// console.log('samtsc: No build needed');
// TODO: Figure out if this scenario is a second call of the same compile or a separate function
// needing to be deployed
// if(this.deploy && filePath) {
// this.deploy(filePath);
// }
return;
}
try {
filePath && logger.info('File changed ', filePath);
if((!filePath && !existsSync(this.path + '/node_modules')) || (filePath && filePath.indexOf('package.json') >= 0)) {
this.installDependencies();
}
syncFolder(this.path, resolve(this.buildRoot, this.path), ['node_modules', '.ts', 'package.json', 'tsconfig.json']);
if(this.tsconfigDir) {
logger.info('building path ', this.path);
compileTypescript(this.tsconfigDir, this.buildRoot, { library: this.isLibrary, outDir: this.outDir }, this.samconfig);
logger.success('build complete', this.path);
}
if(!filePath || filePath.indexOf('package.json') >= 0) {
buildPackageJson(this.path, this.buildRoot);
}
writeCacheFile(this.path);
const buildDir = `${this.buildRoot}/${this.path}`;
this.events.emit('build-complete', buildDir);
if(!skipDeploy && !this.neverPackage) {
if(filePath) {
this.package(filePath);
}
}
} catch (err) {
console.debug(err);
throw err;
}
}
async package(filePath) {
try {
logger.info('preparing packaging function', this.name, this.tempDir, this.path);
const zipFile = resolve(`${this.tempDir}/${getFileSmash(this.path)}.zip`);
const buildDir = `${this.buildRoot}/${this.path}`;
if(filePath == 'package.json' || !existsSync(`${buildDir}/node_modules`)) {
const content = JSON.parse(readFileSync(resolve(this.path, 'package.json')));
if(content.dependencies && Object.keys(content.dependencies)) {
logger.info('Updating dependencies');
execOnlyShowErrors('npm i --only=prod', { cwd: `${buildDir}`})
}
}
logger.info('packaging up function');
logger.debug(buildDir);
await archiveDirectory(zipFile, buildDir);
const zipContents = readFileSync(zipFile);
this.events.emit('package', zipContents);
logger.success('packaging complete', this.name);
} catch (err) {
logger.error('packaging FAILED', err);
}
}
}
module.exports.SAMCompiledDirectory = SAMCompiledDirectory;