boilerforge 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/cli.js +35856 -21740
  4. package/{src/templates/create-node-app/resource/gitignore.ts → dist/templates/create-node-app/app/.gitignore.hbs} +1 -14
  5. package/dist/templates/create-node-app/app/.prettierrc.json.hbs +15 -0
  6. package/dist/templates/create-node-app/app/README.md.hbs +54 -0
  7. package/dist/templates/create-node-app/app/package.json.hbs +30 -0
  8. package/dist/templates/create-node-app/app/src/index.js.hbs +5 -0
  9. package/dist/templates/create-node-app/app/src/index.ts.hbs +5 -0
  10. package/dist/templates/create-node-app/app/tsconfig.json.hbs +15 -0
  11. package/dist/templates/create-node-app/forge.config.json +14 -0
  12. package/dist/templates/create-node-app/forge.prompt.json +105 -0
  13. package/dist/templates/create-node-app/schema/prompt.schema.json +240 -0
  14. package/package.json +17 -2
  15. package/.commitlintrc.json +0 -3
  16. package/.github/workflows/release-pipeline.yml +0 -79
  17. package/.husky/commit-msg +0 -4
  18. package/.husky/pre-push +0 -4
  19. package/.release/latest/config.json +0 -16
  20. package/.release/latest/manifest.json +0 -3
  21. package/.release/rc/config.json +0 -19
  22. package/.release/rc/manifest.json +0 -3
  23. package/CHANGELOG.md +0 -68
  24. package/bin/cli.ts +0 -21
  25. package/esbuild.config.ts +0 -17
  26. package/prettier.config.mjs +0 -20
  27. package/src/config/cli-config.ts +0 -25
  28. package/src/lib/file.ts +0 -159
  29. package/src/lib/project.ts +0 -211
  30. package/src/lib/prompter.ts +0 -201
  31. package/src/lib/task.ts +0 -41
  32. package/src/templates/create-node-app/index.ts +0 -306
  33. package/src/templates/create-node-app/resource/editor-config.ts +0 -24
  34. package/src/templates/create-node-app/resource/eslint.ts +0 -62
  35. package/src/templates/create-node-app/resource/package-json.ts +0 -57
  36. package/src/templates/create-node-app/resource/prettier.ts +0 -45
  37. package/src/templates/create-node-app/resource/project-deps.ts +0 -30
  38. package/src/templates/create-node-app/resource/project-files.ts +0 -17
  39. package/src/templates/create-node-app/resource/tsconfig.ts +0 -20
  40. package/src/templates/index.ts +0 -1
  41. package/src/templates/utils/badges.ts +0 -13
  42. package/src/templates/utils/git.ts +0 -64
  43. package/src/templates/utils/package-manager.ts +0 -78
  44. package/src/templates/utils/readme.ts +0 -115
  45. package/src/utils/exec-cmd.ts +0 -80
  46. package/src/utils/index.ts +0 -60
  47. package/src/utils/logger.ts +0 -24
  48. package/tsconfig.json +0 -17
  49. /package/{.editorconfig → dist/templates/create-node-app/app/.editorconfig.hbs} +0 -0
  50. /package/{.prettierignore → dist/templates/create-node-app/app/.prettierignore.hbs} +0 -0
  51. /package/{eslint.config.mjs → dist/templates/create-node-app/app/eslint.config.mjs.hbs} +0 -0
@@ -1,201 +0,0 @@
1
- import chalk from 'chalk';
2
- import { prompt } from 'enquirer';
3
-
4
- type QuestionResponse<Q extends Question> = Q extends InputQuestion
5
- ? string
6
- : Q extends SelectQuestion
7
- ? string
8
- : Q extends RadioQuestion
9
- ? boolean
10
- : Q extends MultiselectQuestion
11
- ? string[]
12
- : never;
13
-
14
- type ResponseShape<T extends readonly Question[]> = {
15
- [K in T[number] as K['name']]: QuestionResponse<K>;
16
- };
17
-
18
- type QuestionType = 'input' | 'radio' | 'select' | 'multiselect';
19
-
20
- type QuestionDescription = {
21
- message: string;
22
- default?: string;
23
- };
24
-
25
- type MultiselectChoice = {
26
- name: string;
27
- message: string;
28
- };
29
-
30
- type QuestionMap = {
31
- input: InputQuestion;
32
- select: SelectQuestion;
33
- multiselect: MultiselectQuestion;
34
- radio: RadioQuestion;
35
- };
36
-
37
- type PromptMap = {
38
- input: InputPrompt;
39
- select: SelectPrompt;
40
- multiselect: MultiselectPrompt;
41
- radio: RadioPrompt;
42
- };
43
-
44
- type QuestionHandler = {
45
- [K in keyof QuestionMap]: (question: QuestionMap[K]) => PromptMap[K];
46
- };
47
-
48
- interface BaseQuestion {
49
- type: QuestionType;
50
- name: string;
51
- description?: QuestionDescription;
52
- skip?: boolean;
53
- validate?: (value: string) => boolean | string;
54
- }
55
-
56
- interface SelectQuestion extends BaseQuestion {
57
- type: 'select';
58
- choices: readonly string[];
59
- default: number;
60
- }
61
-
62
- interface MultiselectQuestion extends BaseQuestion {
63
- type: 'multiselect';
64
- choices: readonly MultiselectChoice[];
65
- default: number;
66
- }
67
-
68
- interface InputQuestion extends BaseQuestion {
69
- type: 'input';
70
- default: string;
71
- }
72
-
73
- interface RadioQuestion extends BaseQuestion {
74
- type: 'radio';
75
- enabled: string;
76
- disabled: string;
77
- default: boolean;
78
- }
79
-
80
- interface InputPrompt {
81
- type: 'input';
82
- name: string;
83
- message: string;
84
- initial: string;
85
- }
86
-
87
- interface SelectPrompt {
88
- type: 'select';
89
- name: string;
90
- message: string;
91
- choices: readonly string[];
92
- initial: number;
93
- }
94
-
95
- interface MultiselectPrompt {
96
- type: 'multiselect';
97
- name: string;
98
- message: string;
99
- choices: readonly MultiselectChoice[];
100
- initial: number;
101
- }
102
-
103
- interface RadioPrompt {
104
- type: 'toggle';
105
- name: string;
106
- message: string;
107
- enabled: string;
108
- disabled: string;
109
- initial: boolean;
110
- }
111
-
112
- export type Question =
113
- | InputQuestion
114
- | SelectQuestion
115
- | RadioQuestion
116
- | MultiselectQuestion;
117
-
118
- export type PromptObject =
119
- | InputPrompt
120
- | SelectPrompt
121
- | RadioPrompt
122
- | MultiselectPrompt;
123
-
124
- export class Prompter<T extends readonly Question[] = Question[]> {
125
- questions: T;
126
-
127
- constructor(questions?: T) {
128
- this.questions = (questions ?? []) as T;
129
- }
130
-
131
- addQuestion<Q extends Question>(question: Q): Prompter<[...T, Q]> {
132
- return new Prompter<[...T, Q]>([...this.questions, question]);
133
- }
134
-
135
- buildPrompt(): PromptObject[] {
136
- const baseBuilder = <T extends Question>(
137
- question: T,
138
- ): {
139
- type: T['type'];
140
- name: string;
141
- message: string;
142
- validate?: (value: string) => boolean | string;
143
- } => ({
144
- type: question.type,
145
- name: question.name,
146
- message:
147
- chalk.cyan(question.description?.message) +
148
- (question.description?.default
149
- ? chalk.gray(` (${question.description.default})`)
150
- : ''),
151
- validate: question.validate
152
- ? (value) => {
153
- const result = question.validate?.(value);
154
- if (typeof result === 'boolean') return result;
155
- return chalk.redBright(result);
156
- }
157
- : undefined,
158
- });
159
-
160
- const questionHandler: QuestionHandler = {
161
- input: (question: InputQuestion) => ({
162
- ...baseBuilder(question),
163
- initial: question.default,
164
- }),
165
- select: (question: SelectQuestion) => ({
166
- ...baseBuilder(question),
167
- choices: question.choices,
168
- initial: question.default,
169
- }),
170
- multiselect: (question: MultiselectQuestion) => ({
171
- ...baseBuilder(question),
172
- choices: question.choices,
173
- initial: question.default,
174
- }),
175
- radio: (question: RadioQuestion) => ({
176
- ...baseBuilder(question),
177
- type: 'toggle',
178
- enabled: question.enabled,
179
- disabled: question.disabled,
180
- initial: question.default,
181
- }),
182
- };
183
-
184
- const prompts: PromptObject[] = [];
185
-
186
- this.questions.forEach((question) => {
187
- const handler = questionHandler[question.type] as (
188
- q: typeof question,
189
- ) => PromptObject;
190
-
191
- !question.skip && prompts.push(handler(question));
192
- });
193
-
194
- return prompts;
195
- }
196
-
197
- async prompt(): Promise<ResponseShape<T>> {
198
- const prompts = this.buildPrompt();
199
- return (await prompt(prompts)) as ResponseShape<T>;
200
- }
201
- }
package/src/lib/task.ts DELETED
@@ -1,41 +0,0 @@
1
- export type TaskHandler = (...args: unknown[]) => Promise<unknown> | unknown;
2
-
3
- export interface TaskProps {
4
- title: string;
5
- task?: TaskHandler;
6
- enabled?: TaskHandler;
7
- subTasks?: Task[];
8
- concurrent?: boolean;
9
- rollback?: TaskHandler;
10
- exitOnError?: boolean;
11
- }
12
-
13
- export class Task {
14
- title: string;
15
- task: TaskHandler;
16
- enabled?: TaskHandler;
17
- subTasks: Task[];
18
- concurrent: boolean;
19
- rollback?: TaskHandler;
20
- exitOnError?: boolean;
21
-
22
- constructor(taskProps: TaskProps) {
23
- const {
24
- title,
25
- task,
26
- enabled,
27
- subTasks,
28
- concurrent,
29
- rollback,
30
- exitOnError,
31
- } = taskProps;
32
-
33
- this.title = title;
34
- this.task = task ?? (() => {});
35
- this.enabled = enabled;
36
- this.subTasks = subTasks ?? [];
37
- this.concurrent = concurrent ?? false;
38
- this.rollback = rollback;
39
- this.exitOnError = exitOnError ?? true;
40
- }
41
- }
@@ -1,306 +0,0 @@
1
- import { sep } from 'path';
2
- import { File } from '../../lib/file';
3
- import {
4
- NodejsProject,
5
- NodejsProjectProps,
6
- ProjectDependency,
7
- Resource,
8
- } from '../../lib/project';
9
- import { Prompter } from '../../lib/prompter';
10
- import { Task } from '../../lib/task';
11
- import { execCmd } from '../../utils/exec-cmd';
12
- import { getGitConfiguration, getGitVersion } from '../utils/git';
13
- import {
14
- getLockFileName,
15
- getPackageManagerVersion,
16
- installDependencies,
17
- } from '../utils/package-manager';
18
- import { getReadmeContent } from '../utils/readme';
19
- import { getEditorConfigContent } from './resource/editor-config';
20
- import { getEslintContent } from './resource/eslint';
21
- import { getGitIgnoreContent } from './resource/gitignore';
22
- import { getPackageJsonContent } from './resource/package-json';
23
- import {
24
- getPrettierIgnoreContent,
25
- getPrettierrcContent,
26
- } from './resource/prettier';
27
- import { getProjectDeps } from './resource/project-deps';
28
- import { srcFileContent } from './resource/project-files';
29
- import { getTsConfigContent } from './resource/tsconfig';
30
- import { Regex } from '../../utils';
31
-
32
- export class CreateNodeApp extends NodejsProject {
33
- constructor(props: NodejsProjectProps) {
34
- super(props);
35
- this.isTypescript = false;
36
- this.isEslintPrettier = false;
37
- this.configureCliCommand();
38
- }
39
-
40
- async setup(): Promise<CreateNodeApp> {
41
- // creating the tasks
42
- const {
43
- systemCheckTask,
44
- setupProjectTask,
45
- installDepsTask,
46
- gitInitTask,
47
- } = this.buildTasks(getProjectDeps(this));
48
-
49
- this.tasks = [
50
- systemCheckTask,
51
- setupProjectTask,
52
- installDepsTask,
53
- gitInitTask,
54
- ];
55
-
56
- await this.bootstrap();
57
-
58
- return this;
59
- }
60
-
61
- buildTasks(projectDeps: ProjectDependency): {
62
- systemCheckTask: Task;
63
- setupProjectTask: Task;
64
- installDepsTask: Task;
65
- gitInitTask: Task;
66
- } {
67
- // verify git
68
- const verifyGitTask = new Task({
69
- title: 'Checking Git availability',
70
- task: async () => {
71
- this.systemCheck.git = {
72
- version: await getGitVersion(),
73
- };
74
- },
75
- });
76
-
77
- // git configuration (user.name & user.email)
78
- const verifyGitConfigTask = new Task({
79
- title: 'Checking Git config (user.name & user.email)',
80
- enabled: () => !!this.systemCheck.git,
81
- task: async () => {
82
- const gitConfig = await getGitConfiguration({
83
- global: true,
84
- });
85
-
86
- if (!gitConfig) return;
87
-
88
- const username = gitConfig['user.name'];
89
- const email = gitConfig['user.email'];
90
-
91
- username &&
92
- email &&
93
- (this.systemCheck.git = {
94
- ...this.systemCheck.git,
95
- configuration: {
96
- username,
97
- email,
98
- },
99
- });
100
- },
101
- });
102
-
103
- // verify package manager
104
- const verifyPkgManagerTask = new Task({
105
- title: `Verifying ${this.packageManager} availability`,
106
- task: async () => {
107
- const pkgManagerVersion = await getPackageManagerVersion(
108
- this.packageManager,
109
- );
110
-
111
- if (!pkgManagerVersion)
112
- throw new Error(`${this.packageManager} is not installed`);
113
-
114
- this.packageManagerVersion = pkgManagerVersion;
115
- this.systemCheck.packageManager = `${this.packageManager}@${pkgManagerVersion}`;
116
- },
117
- });
118
-
119
- // checking system requirements
120
- const systemCheckTask = new Task({
121
- title: 'Verifying System Requirements',
122
- subTasks: [
123
- verifyGitTask,
124
- verifyGitConfigTask,
125
- verifyPkgManagerTask,
126
- ],
127
- });
128
-
129
- // creating the project dir
130
- const setupProjectTask = new Task({
131
- title: `Scaffolding ${this.name} project`,
132
- task: async () => {
133
- const resourceFileContent: Resource[] = [
134
- getGitIgnoreContent(),
135
- getEditorConfigContent(),
136
- ...(this.enabledEslintPrettier
137
- ? [getEslintContent(this), getPrettierrcContent()]
138
- : []),
139
- getPackageJsonContent(this),
140
- getPrettierIgnoreContent(),
141
- ...(this.enabledTypescript ? [getTsConfigContent()] : []),
142
- getReadmeContent(this),
143
- srcFileContent(this),
144
- ];
145
-
146
- this.files = resourceFileContent.map(
147
- ({ filename, content }) =>
148
- new File(filename, {
149
- dirPath: this.projectPath,
150
- content: `${content.trim()}\n`,
151
- }),
152
- );
153
-
154
- await Promise.all(this.files.map((file) => file.create()));
155
- },
156
- rollback: () => {
157
- this.files.map((file) => file.delete());
158
- },
159
- });
160
-
161
- // installing dependencies
162
- const installDepsTask = new Task({
163
- title: `Installing dependencies`,
164
- task: async () => {
165
- await installDependencies(
166
- this.projectPath,
167
- this.packageManager,
168
- projectDeps,
169
- );
170
- },
171
- rollback: async () => {
172
- await execCmd(`rm -rf node_modules`, {
173
- cwd: this.projectPath,
174
- throwError: false,
175
- });
176
-
177
- const lockFile = getLockFileName(this.packageManager);
178
-
179
- lockFile &&
180
- (await execCmd(`rm ${lockFile}`, {
181
- cwd: this.projectPath,
182
- throwError: false,
183
- }));
184
- },
185
- });
186
-
187
- // initialize git
188
- const gitInitTask = new Task({
189
- title: 'Initializing git',
190
- enabled: () => !!this.systemCheck.git?.version,
191
- task: async () => {
192
- await execCmd('git init', {
193
- cwd: this.projectPath,
194
- });
195
-
196
- const { username, email } =
197
- this.systemCheck.git?.configuration ?? {};
198
-
199
- username &&
200
- email &&
201
- (await execCmd('git commit -am"✨ Initial project setup"', {
202
- cwd: this.projectPath,
203
- }));
204
- },
205
- rollback: async () =>
206
- this.systemCheck.git?.version &&
207
- (await execCmd(`rm -rf .git`, {
208
- cwd: this.projectPath,
209
- throwError: false,
210
- })),
211
- });
212
-
213
- return {
214
- systemCheckTask,
215
- setupProjectTask,
216
- installDepsTask,
217
- gitInitTask,
218
- };
219
- }
220
-
221
- async configureCliCommand(): Promise<CreateNodeApp> {
222
- this.program
223
- .command('create-node-app')
224
- .description('Create simple node.js app')
225
- .argument('[name]', 'name of the project', '')
226
- .action(async (name) => {
227
- this.name = name;
228
- await this.promptUser();
229
- await this.setup();
230
- });
231
-
232
- return this;
233
- }
234
-
235
- async promptUser(): Promise<void> {
236
- const prompter = new Prompter([
237
- {
238
- type: 'input',
239
- name: 'name',
240
- default: '',
241
- description: {
242
- message: 'Project name',
243
- default: process.cwd().split(sep).slice(-1)[0],
244
- },
245
- validate: (value) =>
246
- !value.length || Regex.PROJECT_NAME.test(value)
247
- ? true
248
- : 'Invalid project name',
249
- skip: !!this.name.length,
250
- },
251
- {
252
- type: 'input',
253
- name: 'description',
254
- default: '',
255
- description: { message: 'Description' },
256
- },
257
- {
258
- type: 'input',
259
- name: 'version',
260
- default: '0.0.1',
261
- description: { message: 'Version' },
262
- validate: (value) =>
263
- Regex.VERSION.test(value) ? true : 'Invalid version',
264
- },
265
- {
266
- type: 'input',
267
- name: 'author',
268
- default: '',
269
- description: { message: 'Author' },
270
- },
271
- {
272
- type: 'select',
273
- name: 'packageManager',
274
- choices: ['npm', 'yarn', 'pnpm'],
275
- default: 0,
276
- description: { message: 'Package manager', default: 'npm' },
277
- },
278
- {
279
- type: 'radio',
280
- name: 'isTypescript',
281
- enabled: 'Yes',
282
- disabled: 'No',
283
- default: true,
284
- description: { message: 'Use TypeScript' },
285
- },
286
- {
287
- type: 'radio',
288
- name: 'isEslintPrettier',
289
- enabled: 'Yes',
290
- disabled: 'No',
291
- default: true,
292
- description: { message: 'Use ESLint & Prettier formatting' },
293
- },
294
- ] as const);
295
-
296
- const { isEslintPrettier, isTypescript, ...restResponse } =
297
- await prompter.prompt();
298
-
299
- this.configureProject({
300
- ...restResponse,
301
- });
302
-
303
- this.isTypescript = isTypescript;
304
- this.isEslintPrettier = isEslintPrettier;
305
- }
306
- }
@@ -1,24 +0,0 @@
1
- import { Resource } from '../../../lib/project';
2
-
3
- const content = `
4
- root = true
5
-
6
- [*]
7
- indent_style = space
8
- indent_size = 4
9
- end_of_line = lf
10
- charset = utf-8
11
- trim_trailing_whitespace = true
12
- insert_final_newline = true
13
-
14
- [*.yml]
15
- indent_style = space
16
- indent_size = 2
17
- `;
18
-
19
- export const getEditorConfigContent = (): Resource => {
20
- return {
21
- filename: '.editorconfig',
22
- content,
23
- };
24
- };
@@ -1,62 +0,0 @@
1
- import { NodejsProject, Resource } from '../../../lib/project';
2
-
3
- const eslintTsContent = `
4
- import js from "@eslint/js";
5
- import globals from "globals";
6
- import tseslint from "typescript-eslint";
7
- import eslintPluginTs from '@typescript-eslint/eslint-plugin';
8
- import { defineConfig } from "eslint/config";
9
-
10
- export default defineConfig([
11
- { files: ["**/*.{js,mjs,cjs,ts}"], plugins: { js }, extends: ["js/recommended"] },
12
- { files: ["**/*.{js,mjs,cjs,ts}"], languageOptions: { globals: globals.node } },
13
- tseslint.configs.recommended,
14
- {
15
- plugins: {
16
- '@typescript-eslint': eslintPluginTs,
17
- },
18
- rules: {
19
- "@typescript-eslint/no-namespace": "off",
20
- "@typescript-eslint/no-empty-interface": "off",
21
- "@typescript-eslint/no-empty-object-type": "off",
22
- "@typescript-eslint/no-unused-expressions": "off"
23
- }
24
- },
25
- { ignores: ["node_modules/*", "dist/*"] },
26
- ]);
27
- `;
28
-
29
- const eslintJsContent = `
30
- import js from "@eslint/js";
31
- import globals from "globals";
32
- import tseslint from "typescript-eslint";
33
- import eslintPluginTs from '@typescript-eslint/eslint-plugin';
34
- import { defineConfig } from "eslint/config";
35
-
36
- export default defineConfig([
37
- { files: ["**/*.{js,mjs,cjs,ts}"], plugins: { js }, extends: ["js/recommended"] },
38
- { files: ["**/*.{js,mjs,cjs,ts}"], languageOptions: { globals: globals.node } },
39
- tseslint.configs.recommended,
40
- {
41
- plugins: {
42
- '@typescript-eslint': eslintPluginTs,
43
- },
44
- rules: {
45
- "@typescript-eslint/no-namespace": "off",
46
- "@typescript-eslint/no-empty-interface": "off",
47
- "@typescript-eslint/no-empty-object-type": "off",
48
- "@typescript-eslint/no-unused-expressions": "off"
49
- }
50
- },
51
- { ignores: ["node_modules/*", "dist/*"] },
52
- ]);
53
- `;
54
-
55
- export const getEslintContent = (project: NodejsProject): Resource => {
56
- const filename = 'eslint.config.mjs';
57
-
58
- return {
59
- filename,
60
- content: project.enabledTypescript ? eslintTsContent : eslintJsContent,
61
- };
62
- };
@@ -1,57 +0,0 @@
1
- import { NodejsProject, Resource } from '../../../lib/project';
2
- import { PackageManager } from '../../utils/package-manager';
3
-
4
- export const getPackageJsonContent = (project: NodejsProject): Resource => {
5
- const { name, author, description, packageManager, version } = project;
6
-
7
- const ci = `${packageManager} ${packageManager === PackageManager.NPM ? 'ci' : 'install --frozen-lockfile'}`;
8
-
9
- const prebuildScript = [`${packageManager} run clean`];
10
-
11
- project.enabledEslintPrettier &&
12
- prebuildScript.push(
13
- `${packageManager} run lint`,
14
- `${packageManager} run format`,
15
- );
16
-
17
- const packageJson = {
18
- name,
19
- version,
20
- description,
21
- author,
22
- license: 'ISC',
23
- packageManager: project.getPackageManager(),
24
- main: project.enabledTypescript ? 'dist/' : 'src/',
25
- scripts: {
26
- ci,
27
- ...(project.enabledTypescript
28
- ? {
29
- clean: 'rimraf dist',
30
- build: 'tsc',
31
- dev: 'ts-node src/',
32
- prebuild: prebuildScript.join(' && '),
33
- start: 'node dist/',
34
- ...(project.enabledEslintPrettier
35
- ? {
36
- lint: 'eslint --ext .js,.ts .',
37
- format: 'prettier --write "**/*.+(js|ts)"',
38
- }
39
- : {}),
40
- }
41
- : {
42
- start: 'node src/',
43
- ...(project.enabledEslintPrettier
44
- ? {
45
- lint: 'eslint --ext .js .',
46
- format: 'prettier --write "**/*.+(js)"',
47
- }
48
- : {}),
49
- }),
50
- },
51
- };
52
-
53
- return {
54
- filename: 'package.json',
55
- content: JSON.stringify(packageJson, null, 4),
56
- };
57
- };