projenrc/projects/pdk-project.ts (321 lines of code) (raw):

/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ import * as path from "path"; import { Dependency, DependencyType, Project } from "projen"; import { JsiiProject, Stability } from "projen/lib/cdk"; import { NodePackageManager } from "projen/lib/javascript"; import { Release } from "projen/lib/release"; import { PDKInternalProjectUtils } from "./internal/internal-project"; import { NodePackageUtils, NxProject } from "../../packages/monorepo/src"; import { PDKProject, PDK_NAMESPACE, PublishConfig, } from "../abstract/pdk-project"; const PACK_COMMAND = [ "rm -rf build", "pnpm --config.shamefully-hoist=true --config.hoist=true --config.symlinks=false --config.shared-workspace-lockfile=false --filter=@aws/pdk deploy build", "cd build", "pnpm pack", "mv *.tgz ..", "cd ..", "rm -rf build", ].join(" && "); /** * Contains configuration for the UberProject. */ export class PdkProject extends PDKProject { static getJsiiProjects(parent?: Project): JsiiProject[] | undefined { return parent?.subprojects .filter((subProject) => !PDKInternalProjectUtils.isInternal(subProject)) .filter((subProject) => subProject instanceof JsiiProject) .filter((subProject) => subProject.name.startsWith(PDK_NAMESPACE)) .map((subProject) => subProject as JsiiProject); } static getProjectName(project: JsiiProject | string, snakeCase?: boolean) { const pathSegments = ( project instanceof JsiiProject ? project.outdir : project ).split("/"); const projectName = pathSegments[pathSegments.length - 1]; return snakeCase ? projectName.replace(/-/g, "_") : projectName; } constructor(parent: Project) { const jsiiProjects: JsiiProject[] | undefined = PdkProject.getJsiiProjects(parent); const paths = jsiiProjects?.reduce( (p, c) => ({ ...p, ...{ [c.name]: [`./${PdkProject.getProjectName(c)}`], }, }), {} ); super({ parent, author: "AWS APJ COPE", authorAddress: "apj-cope@amazon.com", defaultReleaseBranch: "mainline", name: "pdk", eslint: false, jest: false, docgen: false, sampleCode: false, releaseToNpm: true, keywords: ["aws", "pdk", "jsii", "projen"], repositoryUrl: "https://github.com/aws/aws-pdk", stability: Stability.STABLE, excludeTypescript: ["**/samples/**/*.ts", "**/scripts/**/*.ts"], libdir: ".", srcdir: ".", rootdir: ".", tsconfigDev: { compilerOptions: { paths, outDir: ".", }, }, publishConfig: { executableFiles: ["./_scripts/pdk.sh", "./_scripts/exec-command.js"], }, }); this.manifest.main = "index.js"; this.manifest.jsii.tsc.paths = paths; this.manifest.jsii.tsc.rootDir = "."; this.addBundledDeps("findup"); [ "scripts", "assets", "samples", "index.*", ...(jsiiProjects || []).map((p) => PdkProject.getProjectName(p)), ].forEach((s) => this.addGitIgnore(s)); // Rewrite imports to use tsconfig paths this.compileTask.exec( NodePackageUtils.command.downloadExec( NodePackageManager.PNPM, "tsc-alias", "-p", "tsconfig.dev.json", "--dir", "." ) ); this.generateSource(jsiiProjects); jsiiProjects?.forEach((p) => NxProject.ensure(this).addImplicitDependency(p) ); this.package.addField("bin", () => jsiiProjects ?.map((p) => Object.fromEntries( Object.entries(p.manifest.bin() || {}).map(([k, v]) => [ k, `./${v}`, ]) ) ) .reduce((p, c) => ({ ...p, ...c }), { pdk: "./_scripts/pdk.sh" }) ); // Make sure this is after NxProject so targets can be updated after inference new PDKRelease(this, { executableFiles: [ ...this.manifest.publishConfig.executableFiles, ...(jsiiProjects ?.map((p) => (p.manifest.publishConfig?.executableFiles || []).map( (_p: string) => `./${_p}` ) ) .flatMap((x) => x) || []), ], }); } private generateSource(jsiiProjects?: JsiiProject[]): void { this.preCompileTask.exec( `rm -rf index.* samples scripts assets ${jsiiProjects ?.map((p) => PdkProject.getProjectName(p)) .join(" ")}` ); jsiiProjects?.forEach((subProject) => { this.copyProjectSource(subProject); }); this.addProjectDeps((jsiiProjects || []).flatMap((p) => p.deps.all)); this.emitIndexFile(jsiiProjects); } private emitIndexFile(projects?: JsiiProject[]) { if (!projects) { return; } this.preCompileTask.exec( `echo '${projects .map( (p) => `export * as ${PdkProject.getProjectName( p, true )} from "./${PdkProject.getProjectName(p)}";` ) .join("\n")}' > ./index.ts` ); } private conditionallyCopyFiles( project: JsiiProject, dir: string, targetDir: string = dir, copyRoot: string = "." ) { this.preCompileTask.exec( `if [ -d "${path.relative( this.outdir, project.outdir )}/${dir}/" ]; then mkdir -p ./${copyRoot}/${PdkProject.getProjectName( project )}/${targetDir} && rsync -a ${path.relative( this.outdir, project.outdir )}/${dir}/ ./${copyRoot}/${PdkProject.getProjectName( project )}/${targetDir} --prune-empty-dirs; fi;` ); } private copyProjectSource(project: JsiiProject) { this.conditionallyCopyFiles(project, project.srcdir, "."); this.conditionallyCopyFiles(project, "samples", "../samples"); this.conditionallyCopyFiles(project, "scripts", "../scripts"); this.conditionallyCopyFiles(project, "assets", "../assets"); this.preCompileTask.exec( `mkdir -p ./${PdkProject.getProjectName( project )} && rsync --exclude=**/*.ts -a ${path.relative( this.outdir, project.outdir )}/${project.srcdir}/ ./${PdkProject.getProjectName( project )} --prune-empty-dirs` ); } private addProjectDeps(deps: Dependency[]) { const bundledDeps = new Set( deps .filter((dep) => dep.type === DependencyType.BUNDLED) .map((dep) => dep.name) ); deps .filter((dep) => !dep.name.startsWith(PDK_NAMESPACE)) .forEach((dep) => { switch (dep.type) { case DependencyType.BUILD: case DependencyType.TEST: !bundledDeps.has(dep.name) && this.addDevDeps(this.renderDependency(dep)); break; case DependencyType.BUNDLED: this.addBundledDeps(this.renderDependency(dep)); break; case DependencyType.PEER: this.addPeerDeps(this.renderDependency(dep)); break; case DependencyType.RUNTIME: this.addDeps(this.renderDependency(dep)); break; default: break; } }); } private renderDependency(dep: Dependency) { return `${dep.name}${!!dep.version ? `@${dep.version}` : ""}`; } } /** * Enforces licenses and attribution are checked and included as part of the release distributable. Sets up a release:mainline task which * bumps package versions using semantic versioning. */ class PDKRelease extends Release { constructor(project: PDKProject, publishConfig?: PublishConfig) { super(project, { versionFile: "package.json", task: project.buildTask, branch: "mainline", artifactsDirectory: project.artifactsDirectory, }); project.addDevDeps("license-checker", "generate-license-file"); project.packageTask.reset(); project.packageTask.exec( NodePackageUtils.command.exec( project.package.packageManager, "license-checker", "--summary", "--production", "--onlyAllow", "'MIT;Apache-2.0;Unlicense;BSD;BSD-2-Clause;BSD-3-Clause;ISC;'" ) ); project.packageTask.exec( NodePackageUtils.command.exec( project.package.packageManager, "generate-license-file", "--input package.json", "--output LICENSE_THIRD_PARTY", "--overwrite" ) ); project.packageTask.spawn(project.tasks.tryFind("package-all")!); this.updateJsPackageTask(project); this.updateJavaPackageTask(project); this.updatePythonPackageTask(project); const releaseTask = project.tasks.tryFind("release:mainline")!; releaseTask.reset(); releaseTask.env("RELEASE", "true"); releaseTask.exec("rm -rf dist"); releaseTask.spawn(project.tasks.tryFind("bump")!); releaseTask.spawn(project.preCompileTask); releaseTask.spawn(project.compileTask); releaseTask.spawn(project.postCompileTask); releaseTask.spawn(project.packageTask); releaseTask.spawn(project.tasks.tryFind("unbump")!); [ ".gitattributes", ".prettier*", "index.ts", "project.json", "!/assets", "!/samples", "!/scripts", "!.jsii", "!README.md", "!LICENSE", "!LICENSE_THIRD_PARTY", "!scripts", "!_scripts", "!assets", "!samples", "!index.js", "!index.d.ts", ...(PdkProject.getJsiiProjects(project.parent) || []).map( (p) => `!/${PdkProject.getProjectName(p)}` ), ...(PdkProject.getJsiiProjects(project.parent) || []).map( (p) => `/${PdkProject.getProjectName(p)}/**/*.ts` ), ...(PdkProject.getJsiiProjects(project.parent) || []).map( (p) => `!/${PdkProject.getProjectName(p)}/**/*.d.ts` ), ].forEach((p) => project.addPackageIgnore(p)); project.package.addField("publishConfig", { access: "public", ...publishConfig, }); NxProject.of(project)?.addBuildTargetFiles( ["!{projectRoot}/LICENSE_THIRD_PARTY"], ["{projectRoot}/LICENSE_THIRD_PARTY"] ); } /** * Updates the java package task to use the pack command. * * @param project project to update. */ private updateJavaPackageTask = (project: Project): void => { project.tasks .tryFind("package:java") ?.reset(`jsii-pacmak -v --target java --pack-command='${PACK_COMMAND}'`); }; /** * Changes the pack command to use pnpm. * * @param project project to update. */ private updateJsPackageTask = (project: Project): void => { project.tasks .tryFind("package:js") ?.reset(`jsii-pacmak -v --target js --pack-command='${PACK_COMMAND}'`); }; /** * Changes the pack command to use pnpm. * * @param project project to update. */ private updatePythonPackageTask = (project: Project): void => { project.tasks .tryFind("package:python") ?.reset( `jsii-pacmak -v --target python --pack-command='${PACK_COMMAND}'` ); }; }