packages/@aws-cdk/custom-resource-handlers/scripts/generate.ts (106 lines of code) (raw):
import * as fs from 'fs';
import * as path from 'path';
import * as esbuild from 'esbuild';
import { config, ComponentProps } from '../lib/custom-resources-framework/config';
import { HandlerFrameworkModule } from '../lib/custom-resources-framework/framework';
const framework: { [fqn: string]: ComponentProps[] } = {};
async function main() {
recurse(config, []);
for (const [fqn, components] of Object.entries(framework)) {
const module = new HandlerFrameworkModule(fqn);
for (const component of components) {
const outfile = calculateOutfile(component.sourceCode);
if (component.minifyAndBundle ?? true) {
await minifyAndBundle(component.sourceCode, outfile);
} else {
fs.mkdirSync(path.dirname(outfile), { recursive: true });
fs.copyFileSync(component.sourceCode, outfile);
}
const codeDirectory = path.dirname(outfile).split('/').pop() ?? '';
module.build(component, codeDirectory);
}
if (module.hasComponents) {
module.renderTo(`dist/${fqn}.generated.ts`);
}
}
function recurse(_config: any, _path: string[]) {
// base case - this is a framework component array and we will build a module with
// all defined components
if (_config instanceof Array) {
const fqn = _path.join('/');
framework[fqn] = _config;
return;
}
for (const key in _config) {
if (_config.hasOwnProperty(key) && typeof _config[key] === 'object') {
_path.push(key);
recurse(_config[key], _path);
_path.pop(); // backtrack
}
}
}
}
async function minifyAndBundle(infile: string, outfile: string) {
const result = await esbuild.build({
entryPoints: [infile],
outfile,
external: ['@aws-sdk/*', 'aws-sdk'],
format: 'cjs',
platform: 'node',
target: 'node18',
bundle: true,
minify: true,
minifyWhitespace: true,
minifySyntax: true,
minifyIdentifiers: true,
sourcemap: false,
tsconfig: 'tsconfig.json',
// These should be checked because they can lead to runtime failures. There are
// false positives, and the esbuild API does not provide a way to suppress them,
// so we need to do some postprocessing.
logOverride: {
'unsupported-dynamic-import': 'warning',
'unsupported-require-call': 'warning',
'indirect-require': 'warning',
},
logLevel: 'error',
});
const failures = [
...result.errors,
...ignoreWarnings(result),
];
if (failures.length > 0) {
const messages = esbuild.formatMessagesSync(failures, {
kind: 'error',
color: true,
});
// eslint-disable-next-line no-console
console.log(messages.join('\n'));
// eslint-disable-next-line no-console
console.log(`${messages.length} errors. For false positives, put '// esbuild-disable <code> - <motivation>' on the line before`);
process.exitCode = 1;
}
}
export function calculateOutfile(file: string) {
// turn ts extension into js extension
if (file.includes('index.ts')) {
file = path.join(path.dirname(file), path.basename(file, path.extname(file)) + '.js');
}
// replace /lib with /dist
const fileContents = file.split(path.sep);
fileContents[fileContents.lastIndexOf('lib')] = 'dist';
return fileContents.join(path.sep);
}
function ignoreWarnings(result: esbuild.BuildResult) {
const ret: esbuild.Message[] = [];
for (const warning of result.warnings) {
let suppressed = false;
if (warning.location?.file) {
const contents = fs.readFileSync(warning.location.file, { encoding: 'utf-8' });
const lines = contents.split('\n');
const lineBefore = lines[warning.location.line - 1 - 1];
if (lineBefore.includes(`esbuild-disable ${warning.id}`)) {
suppressed = true;
}
}
if (!suppressed) {
ret.push(warning);
}
}
return ret;
}
main().catch((e) => {
// eslint-disable-next-line no-console
console.error(e);
process.exitCode = 1;
});