-
-
Notifications
You must be signed in to change notification settings - Fork 591
/
addon-generator.ts
146 lines (117 loc) · 5.08 KB
/
addon-generator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import fs from "fs";
import path from "path";
import Generator from "yeoman-generator";
import { CustomGenerator } from "./types";
import type { CustomGeneratorOptions, BaseCustomGeneratorOptions } from "./types";
import { getInstaller, getTemplate } from "./utils/helpers";
// eslint-disable-next-line @typescript-eslint/no-var-requires
Object.assign(Generator.prototype, require("yeoman-generator/lib/actions/install"));
// Helper to get the template-directory content
const getFiles = (dir: string): string[] => {
return fs.readdirSync(dir).reduce((list, file) => {
const filePath = path.join(dir, file);
const isDir = fs.statSync(filePath).isDirectory();
return list.concat(isDir ? getFiles(filePath) : filePath);
}, [] as string[]);
};
abstract class AddonGenerator<
T extends BaseCustomGeneratorOptions = BaseCustomGeneratorOptions,
Z extends CustomGeneratorOptions<T> = CustomGeneratorOptions<T>,
> extends CustomGenerator<T, Z> {
public props: Generator.Question | undefined;
public resolvedTemplatePath: string | undefined;
}
export interface AddonGeneratorConstructor<
T extends BaseCustomGeneratorOptions = BaseCustomGeneratorOptions,
Z extends CustomGeneratorOptions<T> = CustomGeneratorOptions<T>,
> {
new (args: string | string[], opts: Z): AddonGenerator<T, Z>;
}
const addonGenerator = <
T extends BaseCustomGeneratorOptions = BaseCustomGeneratorOptions,
Z extends CustomGeneratorOptions<T> = CustomGeneratorOptions<T>,
>(
prompts: Generator.Questions,
templateDir: string,
templateFn: (instance: CustomGenerator<T, Z> & AddonGenerator) => Record<string, unknown>,
): AddonGeneratorConstructor<T, Z> => {
return class extends CustomGenerator<T, Z> {
public resolvedTemplatePath: string | undefined;
public props: Generator.Question | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public constructor(args: string | string[], opts: any) {
super(args, opts);
this.supportedTemplates = fs.readdirSync(templateDir);
}
public async prompting(): Promise<void> {
this.template = await getTemplate.call(this);
this.resolvedTemplatePath = path.join(templateDir, this.template);
this.props = await this.prompt(prompts);
this.packageManager = await getInstaller.call(this);
}
public default(): void {
const name = (this.props as Generator.Question).name as string;
const currentDirName = path.basename(this.destinationPath());
if (currentDirName !== name) {
this.log(`
Your project must be inside a folder named ${name}
I will create this folder for you.
`);
const pathToProjectDir: string = this.destinationPath(name);
try {
fs.mkdirSync(pathToProjectDir, { recursive: true });
} catch (error) {
this.cli.logger.error("Failed to create directory");
this.cli.logger.error(error);
}
this.destinationRoot(pathToProjectDir);
}
}
public writing(): void {
const name = (this.props as Generator.Question).name as string;
const resolvedTemplatePath = this.resolvedTemplatePath as string;
const packageJsonTemplatePath = "../addon-template/package.json.js";
this.fs.extendJSON(
this.destinationPath("package.json"),
// eslint-disable-next-line @typescript-eslint/no-var-requires
require(packageJsonTemplatePath)(name),
);
let files = [];
try {
// An array of file paths (relative to `./templates`) of files to be copied to the generated project
files = getFiles(resolvedTemplatePath);
} catch (error) {
this.cli.logger.error(`Failed to generate starter template.\n ${error}`);
process.exit(2);
}
// Template file paths should be of the form `path/to/_file.js.tpl`
const copyTemplateFiles = files.filter((filePath: string) =>
path.basename(filePath).startsWith("_"),
);
// File paths should be of the form `path/to/file.js.tpl`
const copyFiles = files.filter((filePath: string) => !copyTemplateFiles.includes(filePath));
copyFiles.forEach((filePath: string) => {
// `absolute-path/to/file.js.tpl` -> `destination-path/file.js`
const destFilePath = path.relative(resolvedTemplatePath, filePath).replace(".tpl", "");
this.fs.copyTpl(filePath, this.destinationPath(destFilePath));
});
copyTemplateFiles.forEach((filePath: string) => {
// `absolute-path/to/_file.js.tpl` -> `destination-path/file.js`
const destFilePath = path
.relative(resolvedTemplatePath, filePath)
.replace("_", "")
.replace(".tpl", "");
this.fs.copyTpl(filePath, this.destinationPath(destFilePath), templateFn(this));
});
}
public install(): void {
const packageManager = this.packageManager as string;
const opts: {
dev?: boolean;
"save-dev"?: boolean;
} = this.packageManager === "yarn" ? { dev: true } : { "save-dev": true };
this.scheduleInstallTask(packageManager, ["webpack-defaults"], opts);
}
};
};
export default addonGenerator;