Skip to content

Commit 05b561d

Browse files
feat: select template language (#64)
1 parent c81ab7a commit 05b561d

File tree

7 files changed

+168
-38
lines changed

7 files changed

+168
-38
lines changed

src/constants/templates.ts

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,98 +7,160 @@ const APP_DESCRIPTION_EXAMPLE =
77
export const TEMPLATES: Template[] = [
88
{
99
framework: `Astro`,
10-
key: `astro-starter`,
10+
keys: [
11+
{
12+
key: 'astro-starter',
13+
language: 'TypeScript'
14+
}
15+
],
1116
type: 'Starter',
1217
description: 'Barebones scaffolding for your new website',
1318
kind: 'website'
1419
},
1520
{
1621
framework: `Next.js`,
17-
key: `nextjs-starter`,
22+
keys: [
23+
{
24+
key: 'nextjs-starter',
25+
language: 'TypeScript'
26+
}
27+
],
1828
type: 'Starter',
1929
description: APP_DESCRIPTION_STARTER,
2030
kind: 'app'
2131
},
2232
{
2333
framework: `Next.js`,
24-
key: `nextjs-example`,
34+
keys: [
35+
{
36+
key: 'nextjs-example',
37+
language: 'TypeScript'
38+
}
39+
],
2540
type: 'Example',
2641
description: APP_DESCRIPTION_EXAMPLE,
2742
kind: 'app'
2843
},
2944
{
3045
framework: `React`,
31-
key: `react-starter`,
46+
keys: [
47+
{
48+
key: 'react-starter',
49+
language: 'JavaScript'
50+
},
51+
{
52+
key: 'react-ts-starter',
53+
language: 'TypeScript'
54+
}
55+
],
3256
type: 'Starter',
3357
description: APP_DESCRIPTION_STARTER,
3458
kind: 'app'
3559
},
3660
{
3761
framework: `React`,
38-
key: `react-example`,
62+
keys: [
63+
{
64+
key: 'react-example',
65+
language: 'JavaScript'
66+
}
67+
],
3968
type: 'Example',
4069
description: APP_DESCRIPTION_EXAMPLE,
4170
kind: 'app'
4271
},
4372
{
4473
framework: `React`,
45-
key: `react-ts-starter`,
46-
type: 'Starter',
47-
description: APP_DESCRIPTION_STARTER,
48-
kind: 'app'
49-
},
50-
{
51-
framework: `React`,
52-
key: `react-workshop`,
74+
keys: [
75+
{
76+
key: 'react-workshop',
77+
language: 'JavaScript'
78+
}
79+
],
5380
type: 'Workshop',
5481
description: 'Explore Juno in an interactive workshop',
5582
kind: 'app'
5683
},
5784
{
5885
framework: `SvelteKit`,
59-
key: `sveltekit-starter`,
86+
keys: [
87+
{
88+
key: 'sveltekit-starter',
89+
language: 'TypeScript'
90+
}
91+
],
6092
type: 'Starter',
6193
description: APP_DESCRIPTION_STARTER,
6294
kind: 'app'
6395
},
6496
{
6597
framework: `SvelteKit`,
66-
key: `sveltekit-example`,
98+
keys: [
99+
{
100+
key: 'sveltekit-example',
101+
language: 'TypeScript'
102+
}
103+
],
67104
type: 'Example',
68105
description: APP_DESCRIPTION_EXAMPLE,
69106
kind: 'app'
70107
},
71108
{
72109
framework: `Vue`,
73-
key: `vue-starter`,
110+
keys: [
111+
{
112+
key: 'vue-starter',
113+
language: 'TypeScript'
114+
}
115+
],
74116
type: 'Starter',
75117
description: APP_DESCRIPTION_STARTER,
76118
kind: 'app'
77119
},
78120
{
79121
framework: `Vue`,
80-
key: `vue-example`,
122+
keys: [
123+
{
124+
key: 'vue-example',
125+
language: 'TypeScript'
126+
}
127+
],
81128
type: 'Example',
82129
description: APP_DESCRIPTION_EXAMPLE,
83130
kind: 'app'
84131
},
85132
{
86133
framework: `Angular`,
87-
key: `angular-starter`,
134+
keys: [
135+
{
136+
key: 'angular-starter',
137+
language: 'TypeScript'
138+
}
139+
],
88140
type: 'Starter',
89141
description: APP_DESCRIPTION_STARTER,
90142
kind: 'app'
91143
},
92144
{
93145
framework: `Angular`,
94-
key: `angular-example`,
146+
keys: [
147+
{
148+
key: 'angular-example',
149+
language: 'TypeScript'
150+
}
151+
],
95152
type: 'Example',
96153
description: APP_DESCRIPTION_EXAMPLE,
97154
kind: 'app'
98155
},
99156
{
100157
framework: `Vanilla JavaScript`,
101-
key: `vanilla-js-example`,
158+
keys: [
159+
{
160+
key: 'vanilla-js-example',
161+
language: 'JavaScript'
162+
}
163+
],
102164
type: 'Example',
103165
description: APP_DESCRIPTION_EXAMPLE,
104166
kind: 'app'

src/services/generate.services.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,21 @@ const removeLocalConfig = async ({where, template}: PopulateInputFn) => {
152152
return;
153153
}
154154

155+
// TODO: this is verbose and difficult to maintain. We should probably introduce those options in some ways in the templates' definition.
155156
const config = join(
156157
process.cwd(),
157158
where ?? '',
158159
template.framework === 'Astro'
159160
? 'astro.config.mjs'
160161
: template.framework === 'Next.js'
161162
? 'next.config.mjs'
162-
: ['React', 'Vanilla JavaScript'].includes(template.framework)
163-
? 'vite.config.js'
164-
: 'vite.config.ts'
163+
: template.framework === 'React'
164+
? template.language === 'TypeScript'
165+
? 'vite.config.ts'
166+
: 'vite.config.js'
167+
: template.framework === 'Vanilla JavaScript'
168+
? 'vite.config.js'
169+
: 'vite.config.ts'
165170
);
166171

167172
const data = await readFile(config, 'utf8');

src/services/prompt.services.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import {isNullish} from '@junobuild/utils';
22
import {red} from 'kleur';
33
import prompts from 'prompts';
44
import {TEMPLATES} from '../constants/templates';
5-
import type {GeneratorInput, ProjectKind} from '../types/generator';
6-
import type {Template, TemplateFramework} from '../types/template';
5+
import type {GeneratorInput, PopulateTemplate, ProjectKind} from '../types/generator';
6+
import type {Template, TemplateFramework, TemplateKeyOption} from '../types/template';
77
import {assertAnswerCtrlC, confirm} from '../utils/prompts.utils';
88

9-
export const promptTemplate = async (projectKind: ProjectKind): Promise<Template> => {
9+
export const promptTemplate = async (projectKind: ProjectKind): Promise<PopulateTemplate> => {
1010
const allTemplates = TEMPLATES.filter(({kind}) => kind === projectKind).reduce<
1111
Partial<Record<TemplateFramework, Template[]>>
1212
>((acc, {framework, ...rest}) => {
@@ -42,7 +42,7 @@ export const promptTemplate = async (projectKind: ProjectKind): Promise<Template
4242
}
4343

4444
if (templates.length === 1) {
45-
return templates[0];
45+
return await promptLanguage(templates[0]);
4646
}
4747

4848
const {template}: {template: Template | undefined} = await prompts({
@@ -60,7 +60,39 @@ export const promptTemplate = async (projectKind: ProjectKind): Promise<Template
6060
process.exit(0);
6161
}
6262

63-
return template;
63+
return await promptLanguage(template);
64+
};
65+
66+
export const promptLanguage = async ({
67+
keys,
68+
...templateRest
69+
}: Template): Promise<PopulateTemplate> => {
70+
// If the template is provided with a sole language, we can auto select it.
71+
if (keys.length === 1) {
72+
return {
73+
...templateRest,
74+
...keys[0]
75+
};
76+
}
77+
78+
const {key}: {key: TemplateKeyOption | undefined} = await prompts({
79+
type: 'select',
80+
name: 'key',
81+
message: 'Which language do you prefer?',
82+
choices: keys.map((key) => ({
83+
title: key.language,
84+
value: key
85+
}))
86+
});
87+
88+
if (isNullish(key)) {
89+
process.exit(0);
90+
}
91+
92+
return {
93+
...templateRest,
94+
...key
95+
};
6496
};
6597

6698
export const promptDestination = async (): Promise<Pick<GeneratorInput, 'destination'>> => {

src/services/template.services.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,38 @@
11
import {nextArg} from '@junobuild/cli-tools';
22
import {isNullish} from '@junobuild/utils';
33
import {TEMPLATES} from '../constants/templates';
4-
import type {Template} from '../types/template';
4+
import type {PopulateTemplate} from '../types/generator';
55
import {promptProjectKind, promptTemplate} from './prompt.services';
66

7-
export const argsTemplate = (args: string[]): Template | undefined => {
7+
export const argsTemplate = (args: string[]): PopulateTemplate | undefined => {
88
const template = nextArg({args, option: '--template'});
99

1010
if (isNullish(template)) {
1111
return undefined;
1212
}
1313

14-
return TEMPLATES.find(({key}) => key === template);
14+
const matchingTemplate = TEMPLATES.find(
15+
({keys}) => keys.find(({key}) => key === template) !== undefined
16+
);
17+
18+
if (isNullish(matchingTemplate)) {
19+
return undefined;
20+
}
21+
22+
const {keys, ...restTemplate} = matchingTemplate;
23+
const matchingKey = keys.find(({key}) => key === template);
24+
25+
if (isNullish(matchingKey)) {
26+
return undefined;
27+
}
28+
29+
return {
30+
...restTemplate,
31+
...matchingKey
32+
};
1533
};
1634

17-
export const initTemplate = async (): Promise<Template> => {
35+
export const initTemplate = async (): Promise<PopulateTemplate> => {
1836
const projectKind = await promptProjectKind();
1937

2038
return await promptTemplate(projectKind);

src/types/generator.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type {Template} from './template';
1+
import type {Template, TemplateKeyOption} from './template';
22

33
export interface GeneratorInput {
44
destination: string | '';
5-
template: Template;
5+
template: PopulateTemplate;
66
gitHubAction: boolean;
77
localDevelopment: boolean;
88
verbose?: boolean;
@@ -13,3 +13,5 @@ export type ProjectKind = 'website' | 'app';
1313
export type PopulateInput = {
1414
where: string | null;
1515
} & Omit<GeneratorInput, 'destination'>;
16+
17+
export type PopulateTemplate = Omit<Template, 'keys'> & TemplateKeyOption;

src/types/template.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import type {ProjectKind} from './generator';
22

3+
export type TemplateKey = string;
4+
5+
export interface TemplateKeyOption {
6+
key: TemplateKey;
7+
language: TemplateLanguage;
8+
}
9+
10+
export type TemplateKeys = TemplateKeyOption[];
11+
312
export interface Template {
4-
key: string;
13+
keys: TemplateKeys;
514
framework: TemplateFramework;
615
type: TemplateType;
716
description: string;
@@ -18,3 +27,5 @@ export type TemplateFramework =
1827
| 'Vanilla JavaScript';
1928

2029
export type TemplateType = 'Starter' | 'Example' | 'Workshop';
30+
31+
export type TemplateLanguage = 'JavaScript' | 'TypeScript';

src/utils/fs.utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import {existsSync, mkdirSync} from 'node:fs';
22
import {dirname, join} from 'node:path';
33
import {fileURLToPath} from 'node:url';
4-
import type {Template} from '../types/template';
4+
import type {TemplateKey} from '../types/template';
55

66
const __filename = fileURLToPath(import.meta.url);
77
const __dirname = dirname(__filename);
88

99
const TEMPLATE_PATH = 'templates';
1010

11-
export const getRelativeTemplatePath = ({key}: Pick<Template, 'key'>) => join(TEMPLATE_PATH, key);
11+
export const getRelativeTemplatePath = ({key}: {key: TemplateKey}) => join(TEMPLATE_PATH, key);
1212

13-
export const getLocalTemplatePath = ({key}: Pick<Template, 'key'>) =>
13+
export const getLocalTemplatePath = ({key}: {key: TemplateKey}) =>
1414
join(__dirname, '..', TEMPLATE_PATH, key);
1515

1616
export const createParentFolders = (target: string) => {

0 commit comments

Comments
 (0)