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
248 lines (222 loc) • 10.4 kB
JavaScript
console.log('samtsc: Loading SAM Framework Tools');
const { execSync } = require('child_process');
const { mkdir, existsSync, writeFileSync, readFileSync, symlinkSync, copyFileSync } = require('../file-system');
const { relative, resolve } = require('path');
const { logger } = require('../logger');
const { EventEmitter } = require('events');
const { SAMCompiledDirectory } = require('./compiled-directory');
class SAMLayerLib extends SAMCompiledDirectory {
constructor(dirPath, samconfig, buildRoot, events) {
super(dirPath, samconfig, buildRoot);
this.isLibrary = true;
this.events.on('build-complete', () => { events.emit('layer-change');});
}
}
class SAMLayer {
constructor(name, properties, metadata, stackName, buildRoot, samconfig) {
this.name = name;
this.buildRoot = buildRoot;
this.events = new EventEmitter();
this.stackName = stackName;
this.samconfig = samconfig;
this.setConfig(properties, metadata);
logger.info(`Identified Serverless Layer: ${this.path}`);
this.fileEvent(this.packagePath);
}
setConfig(properties, metadata) {
this.path = properties.ContentUri;
this.sourcePath = this.path;
if(this.path == '.') {
logger.info('Constructing stack layer');
this.path = 'src/layers/stack-layer';
properties.ContentUri = this.path;
}
this.layerName = properties.LayerName || `${this.stackName}-${this.name}`;
this.packageFolder = 'nodejs/';
if(metadata && metadata.BuildMethod && metadata.BuildMethod.startsWith('nodejs')) {
delete metadata.BuildMethod;
this.packageFolder = '';
this.copyToNodeJs = true;
}
this.packagePath = this.packageFolder + 'package.json';
}
fileEvent(filePath) {
if(filePath != this.packagePath) {
return;
}
let pckFolder = resolve(this.sourcePath, this.packageFolder);
let pathWithNodejs = false;
logger.debug(pckFolder);
if(!existsSync(this.packagePath)) {
logger.error(`${this.packagePath} does not exist`);
return;
}
const pckFilePath = resolve(pckFolder, this.packagePath);
this.pck = JSON.parse(readFileSync(pckFilePath).toString());
if(!this.pck.dependencies) {
this.pck.dependencies = {};
}
if(this.copyToNodeJs) {
pckFolder = resolve(this.sourcePath, this.packageFolder, 'nodejs');
pathWithNodejs = true;
if(this.sourcePath != '.') {
Object.keys(this.pck.dependencies).forEach(k => {
let val = this.pck.dependencies[k];
if(!val.startsWith('file:')) {
return;
}
val = val.slice(5);
this.pck.dependencies[k] = 'file:../' + val;
});
} else {
Object.keys(this.pck.dependencies).forEach(k => {
let val = this.pck.dependencies[k];
if(!val.startsWith('file:')) {
return;
}
val = val.slice(5);
val = resolve(val);
logger.debug('abPath', this.path, val, relative(resolve(this.path), val));
this.pck.dependencies[k] = 'file:../' + relative(resolve(this.path), val);
});
}
}
let lock;
// const sourceLockPath = resolve(this.sourcePath, 'package-lock.json');
// if(existsSync(sourceLockPath)) {
// lock = JSON.parse(readFileSync(sourceLockPath));
// }
if(this.name == this.samconfig.stack_reference_layer && this.sourcePath != '.') {
logger.info('Constructing combined dependencies');
const rootPck = JSON.parse(readFileSync('package.json'));
if(rootPck.dependencies && Object.keys(rootPck.dependencies).length > 0) {
lock = null;
Object.keys(rootPck.dependencies).forEach(k => {
let val = rootPck.dependencies[k];
if(val.version) {
val = val.version;
}
if(!val) {
return;
}
if(!val.startsWith('file:')) {
this.pck.dependencies[k] = val;
return;
}
val = val.slice(5);
val = resolve(val);
let abPath = relative(pckFolder, val);
this.pck.dependencies[k] = 'file:' + abPath;
});
}
logger.info('Construction complete');
}
this.libs = Object.keys(this.pck.dependencies).filter(k => {
const d = this.pck.dependencies[k];
if(!d.startsWith('file:')) {
return false;
}
const subpath = resolve(this.path, this.copyToNodeJs? 'nodejs/' : this.packageFolder, d.slice(5));
if(!subpath.startsWith(process.cwd())) {
const localLibDir = resolve(this.buildRoot, 'externals', k);
logger.debug('localLibDir', localLibDir);
mkdir(localLibDir);
if(!existsSync(resolve(localLibDir, 'package.json'))) {
logger.info('Creating local link to lib', subpath);
symlinkSync(resolve(subpath, 'package.json'), resolve(localLibDir, 'package.json'), 'file');
const tsconfig = JSON.parse(readFileSync(resolve(subpath, 'tsconfig.json')));
if(!tsconfig.compilerOptions || !tsconfig.compilerOptions.outDir) {
logger.error('External libraries are only supported with an outDir in the tsconfig. Reference', k);
throw new Error('No external library outDir');
}
mkdir(resolve(localLibDir, tsconfig.compilerOptions.outDir, '..'));
symlinkSync(resolve(subpath, tsconfig.compilerOptions.outDir), resolve(localLibDir, tsconfig.compilerOptions.outDir), 'dir');
}
this.pck.dependencies[k] = 'file:' + localLibDir;
if(lock) {
lock.dependencies[k].version = this.pck.dependencies[k]
}
} else if(pathWithNodejs) {
if(!subpath.startsWith(process.cwd())) {
return;
}
this.pck.dependencies[k] = 'file:' + d.slice(5);
if(lock) {
lock.dependencies[k].version = this.pck.dependencies[k]
}
return true;
} else {
return subpath.startsWith(process.cwd());
}
}).map(k => {
let d = this.pck.dependencies[k];
if(pathWithNodejs) {
d = d.slice(5 + 3);
} else {
d = d.slice(5);
}
logger.debug(d);
const fullPath = resolve(this.path, this.packageFolder, d);
const subpath = relative(process.cwd(), fullPath);
logger.info(subpath);
return new SAMLayerLib(subpath.replace(/\\/g, '/'), this.samconfig, this.buildRoot, this.events);
});
this.libs.forEach(x => {
x.buildIfNotPresent()
x.events.on('build-complete', () => {
this.events.emit('layer-change', this);
});
});
console.log('samtsc: constructing build directory');
const addExtraJs = lock || this.copyToNodeJs? 'nodejs/' : '';
const nodejsPath = `${this.buildRoot}/${this.path}/${this.packageFolder}${addExtraJs}`;
mkdir(nodejsPath);
const pckCopy = JSON.parse(JSON.stringify(this.pck));
if(pckCopy.dependencies) {
Object.keys(pckCopy.dependencies).forEach(k => {
const val = pckCopy.dependencies[k];
if(!val.startsWith('file:')) {
return;
}
let refPath = val.slice(5);
logger.debug('refPath', refPath);
const packFolder = this.sourcePath == '.'? this.path + '/' + (this.copyToNodeJs? 'nodejs/' : '') : pckFolder;
const abPath = resolve(packFolder, refPath);
if(abPath.startsWith(process.cwd())) {
const original = refPath;
//refPath = relative(nodejsPath, abPath);
logger.debug('Resolving file path', original, nodejsPath, abPath, refPath);
} else {
refPath = abPath;
logger.debug('Using absosolute file path', refPath);
}
pckCopy.dependencies[k] = 'file:' + refPath;
if(lock) {
logger.debug('Setting version');
lock.dependencies[k].version = 'file:' + refPath;
lock.packages[""].dependencies[k] = 'file:' + abPath;
// lock.packages[`node_modules/${k}`].resolved = refPath;
}
});
}
writeFileSync(nodejsPath + 'package.json', JSON.stringify(pckCopy, undefined, 2));
if(lock) {
const outputPath = resolve(nodejsPath, 'package-lock.json');
writeFileSync(outputPath, JSON.stringify(lock, undefined, 2));
if(pckCopy.dependencies && Object.keys(this.pck.dependencies).length > 0) {
execSync('npm i --only=prod --legacy-peer-deps', { cwd: nodejsPath, stdio: 'inherit' });
}
} else {
if(pckCopy.dependencies && Object.keys(this.pck.dependencies).length > 0) {
logger.info('samtsc: installing dependencies');
execSync('npm i --only=prod --legacy-peer-deps', { cwd: nodejsPath, stdio: 'inherit' });
}
}
console.log('samtsc: file change ', filePath);
this.events.emit('layer-change', this);
}
cleanup() {
this.libs && this.libs.forEach(x => x.cleanup());
}
}
module.exports.SAMLayer = SAMLayer;