Skip to content

Commit 3e0dd6c

Browse files
tvanhensTyler van Hensbergen
andauthored
feat: compose templates and new devx template (#9)
- Adds the ability to compose templates via `extend` keyword in manifest files. - Composed templates compose by merging their files and dependencies. - Broke out shared parts of template into `base` template. - Refactored `default` to extend `base`. - Added new `devx-experimental` template which extends `base`. Co-authored-by: Tyler van Hensbergen <tyler@functionless.org>
1 parent 2af5726 commit 3e0dd6c

File tree

11 files changed

+130
-37
lines changed

11 files changed

+130
-37
lines changed

src/index.ts

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import mustache from "mustache";
77
import path from "path";
88

99
import packageJson from "../package.json";
10+
import { loadTemplatePlan } from "./plan";
1011
import {
1112
askProjectName,
1213
failOnError,
@@ -17,15 +18,23 @@ import {
1718
const program = new Command();
1819

1920
let projectName: string | undefined;
21+
let templateName = "default";
2022

2123
program
2224
.name(packageJson.name)
2325
.version(packageJson.version)
2426
.arguments("[projectName]")
25-
.action((name) => {
27+
.option(
28+
"-t --template [templateName]",
29+
"name of the template to use [default]"
30+
)
31+
.action((name, options) => {
2632
if (typeof name === "string") {
2733
projectName = name;
2834
}
35+
if (typeof options.template === "string") {
36+
templateName = options.template;
37+
}
2938
})
3039
.parse(process.argv);
3140

@@ -37,12 +46,7 @@ run().catch((err) => {
3746
async function run() {
3847
const packageMangager = getPackageManager();
3948

40-
const templateName = "default";
41-
const templateRoot = path.join(__dirname, "..", "templates", templateName);
42-
const templateManifestPath = path.join(templateRoot, "manifest.json");
43-
const templateManifest: string[] = JSON.parse(
44-
await fs.promises.readFile(templateManifestPath, "utf-8")
45-
);
49+
const templatePlan = await loadTemplatePlan(templateName);
4650

4751
if (!projectName) {
4852
projectName = await askProjectName();
@@ -63,12 +67,12 @@ async function run() {
6367
}
6468

6569
const renderTemplate = async (
70+
sourcePath: string,
6671
localPath: string,
6772
data: Record<string, unknown>
6873
) => {
69-
const templateFilePath = path.join(templateRoot, localPath);
7074
const templateContent = mustache.render(
71-
await fs.promises.readFile(templateFilePath, "utf-8"),
75+
await fs.promises.readFile(sourcePath, "utf-8"),
7276
data
7377
);
7478
// Npm won't include `.gitignore` files in a package.
@@ -86,34 +90,19 @@ async function run() {
8690
projectName,
8791
};
8892

89-
await Promise.all(
90-
templateManifest.map((path) => renderTemplate(path, templateData))
91-
);
93+
for (const mapping of templatePlan.fileMappings) {
94+
// Sequential because order of file rendering matters
95+
// for templates that extend other templates.
96+
await renderTemplate(mapping.sourcePath, mapping.localPath, templateData);
97+
}
9298

9399
process.chdir(root);
94100

95101
console.log();
96102
console.log("Installing packages...");
97103
console.log();
98104

99-
const dependencies = [
100-
"@aws-cdk/aws-appsync-alpha",
101-
"@functionless/ast-reflection",
102-
"@functionless/language-service",
103-
"@types/jest",
104-
"@types/node",
105-
"aws-cdk",
106-
"aws-cdk-lib",
107-
"aws-sdk",
108-
"constructs",
109-
"esbuild",
110-
"functionless",
111-
"jest",
112-
"typesafe-dynamodb",
113-
"typescript",
114-
];
115-
116-
installPackages(packageMangager, dependencies);
105+
installPackages(packageMangager, templatePlan.devDependencies);
117106

118107
console.log();
119108
console.log("Initializing git repository...");

src/plan.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import path from "path";
2+
import fs from "fs";
3+
4+
interface TemplateManifest {
5+
root: string;
6+
extend?: string;
7+
devDependencies?: string[];
8+
files?: string[];
9+
}
10+
11+
interface FileMapping {
12+
sourcePath: string;
13+
localPath: string;
14+
}
15+
16+
interface TemplatePlan {
17+
devDependencies: string[];
18+
fileMappings: FileMapping[];
19+
}
20+
21+
async function loadTemplateManifest(templateName: string) {
22+
const templateRoot = path.join(__dirname, "..", "templates", templateName);
23+
const templateManifestPath = path.join(templateRoot, "manifest.json");
24+
try {
25+
const templateManifest: TemplateManifest = {
26+
...JSON.parse(await fs.promises.readFile(templateManifestPath, "utf-8")),
27+
root: templateRoot,
28+
};
29+
return templateManifest;
30+
} catch (err) {
31+
throw new Error(`Unable to load template: ${templateName}`);
32+
}
33+
}
34+
35+
function applyManifest(
36+
plan: TemplatePlan,
37+
manifest: TemplateManifest
38+
): TemplatePlan {
39+
return {
40+
devDependencies: [
41+
...plan.devDependencies,
42+
...(manifest.devDependencies ?? []),
43+
],
44+
fileMappings: [
45+
...plan.fileMappings,
46+
...(manifest.files?.map((filePath) => {
47+
return {
48+
sourcePath: path.join(manifest.root, filePath),
49+
localPath: filePath,
50+
};
51+
}) ?? []),
52+
],
53+
};
54+
}
55+
56+
export async function loadTemplatePlan(templateName: string) {
57+
let generatedPlan: TemplatePlan = {
58+
devDependencies: [],
59+
fileMappings: [],
60+
};
61+
const targetManifest = await loadTemplateManifest(templateName);
62+
63+
if (targetManifest.extend) {
64+
const baseManifest = await loadTemplateManifest(targetManifest.extend);
65+
66+
generatedPlan = applyManifest(generatedPlan, baseManifest);
67+
}
68+
69+
return applyManifest(generatedPlan, targetManifest);
70+
}
File renamed without changes.
File renamed without changes.

templates/base/manifest.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"devDependencies": [
3+
"@aws-cdk/aws-appsync-alpha",
4+
"@functionless/ast-reflection",
5+
"@functionless/language-service",
6+
"@types/jest",
7+
"@types/node",
8+
"aws-cdk",
9+
"aws-cdk-lib",
10+
"aws-sdk",
11+
"constructs",
12+
"esbuild",
13+
"functionless",
14+
"jest",
15+
"typesafe-dynamodb",
16+
"typescript"
17+
],
18+
"files": [
19+
"package.json",
20+
"cdk.json",
21+
"tsconfig.json",
22+
".gitignore.template"
23+
]
24+
}
File renamed without changes.
File renamed without changes.

templates/default/manifest.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
[
2-
"package.json",
3-
"cdk.json",
4-
"tsconfig.json",
5-
".gitignore.template",
6-
"src/app.ts"
7-
]
1+
{
2+
"extend": "base",
3+
"files": [
4+
"src/app.ts"
5+
]
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"app": "fl-exp cdk-synth"
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"extend": "base",
3+
"devDependencies": ["fl-exp"],
4+
"files": ["cdk.json", "src/my-stack/_stack.ts"]
5+
}

0 commit comments

Comments
 (0)