-
Notifications
You must be signed in to change notification settings - Fork 5
/
preset.ts
256 lines (212 loc) · 8.13 KB
/
preset.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
import { Preset, color } from 'apply';
type Dependencies = {
[key: string]: {
version: string;
type?: 'DEV' | 'PEER';
reliesOn?: string | string[];
}
}
type Configuration = {
[key: string]: {
message: string;
default: any;
question?: true;
}
}
/**
* Svelte adder utility class.
*
* Used to simplify interaction with Preset and approach
* file-structure modification in a configuration-based way.
*/
abstract class Adder {
/**
* The adder's name, which is displayed as the name
* of the Preset. Specified on the implementation level.
*
* @protected
*/
protected abstract readonly ADDER_NAME: string;
/**
* A dictionary of configuration options. Each option
* will either be determined interactively; when the
* `--interaction` flag is present, or through CLI flags.
*
* @protected
*/
protected abstract readonly CONFIGURATION: Configuration;
/**
* A dictionary of required dependencies. Each dependency
* has an associated version, and the type can be either
* explicitly set as 'DEV' or 'PEER', or implicitly
* inferred as a core dependency when left `undefined`.
*
* Each dependency can also specify whether or not they
* should be installed based on the presence of a
* configuration option, defined in `CONFIGURATION`.
*
* @protected
*/
protected abstract readonly REQUIRED_DEPENDENCIES: Dependencies;
/**
* Runs an adder. Initialises all configuration and
* dependencies, followed by running the implementation
* specific functionality.
*/
run(): void {
this.initialiseAdder();
}
/**
* Safely extracts a file, ensuring the user acknowledges
* that their previously defined data will be overwritten.
*
* @param title The title of the specific action being
* performed.
* @param filename The filename to move from the templates
* folder.
* @protected
*/
protected safeExtract(title: string, filename: string) {
return Preset.extract(filename).whenConflict('ask').withTitle(title);
}
protected getConfiguration<T>(key): T {
return Preset.isInteractive() ? Preset.prompts[key] : Preset.options[key];
}
private initialiseAdder(): void {
Preset.setName(this.ADDER_NAME);
this.setupConfiguration();
this.setupDependencies();
}
private setupConfiguration(): void {
Object.keys(this.CONFIGURATION).forEach(configurationKey => {
const configuration = this.CONFIGURATION[configurationKey];
this.configure(
configurationKey,
configuration.message,
configuration.default,
configuration.question || false
);
});
}
private setupDependencies(): void {
Preset.group(preset => {
Object.keys(this.REQUIRED_DEPENDENCIES).forEach(dependencyName => {
const dependencyConfig = this.REQUIRED_DEPENDENCIES[dependencyName];
let action;
switch (dependencyConfig.type) {
case 'DEV':
action = preset.editNodePackages().addDev(dependencyName, dependencyConfig.version);
break;
case 'PEER':
action = preset.editNodePackages().addPeer(dependencyName, dependencyConfig.version);
break;
case undefined:
action = preset.editNodePackages().add(dependencyName, dependencyConfig.version);
break;
}
action.if(() => dependencyConfig.reliesOn
? Array.isArray(dependencyConfig.reliesOn)
? dependencyConfig.reliesOn.every(Boolean)
: this.getConfiguration(dependencyConfig.reliesOn)
: true
);
});
}).withTitle('Adding required dependencies');
}
private configure(key: string, msg: string, init: any, confirm: boolean): void {
Preset
.group(preset => {
preset.confirm(key, msg, init).if(() => confirm);
preset.input(key, msg, init).if(() => !confirm);
})
.withoutTitle()
.ifInteractive();
Preset
.group(preset => preset.option(key, init))
.withoutTitle()
.if(() => !Preset.isInteractive());
}
}
class SvelteJestAdder extends Adder {
protected readonly ADDER_NAME = 'svelte-add-jest';
protected readonly CONFIGURATION: Configuration = {
'jest-dom': {message: 'Enable Jest DOM support?', default: true, question: true},
'ts': {message: 'Enable TypeScript support?', default: false, question: true},
'examples': {message: 'Generate example test file?', default: true, question: true},
'jsdom': {message: 'Enable JSDOM environment by default?', default: true, question: true}
};
protected readonly REQUIRED_DEPENDENCIES: Dependencies = {
'@babel/core': {version: '^7.14.0', type: 'DEV'},
'@babel/preset-env': {version: '^7.14.0', type: 'DEV'},
'jest': {version: '^27.0.0', type: 'DEV'},
'babel-jest': {version: '^27.0.0', type: 'DEV'},
'svelte-jester': {version: '^2.0.1', type: 'DEV'},
'@testing-library/svelte': {version: '^3.0.0', type: 'DEV'},
'cross-env': {version: '^7.0.3', type: 'DEV'},
'@testing-library/jest-dom': {version: '^5.14.0', type: 'DEV', reliesOn: 'jest-dom'},
'ts-jest': {version: '^27.0.0', type: 'DEV', reliesOn: 'ts'},
'@types/jest': {version: '^27.0.0', type: 'DEV', reliesOn: 'ts'},
'@types/testing-library__jest-dom': {version: '^5.14.0', type: 'DEV', reliesOn: ['ts', 'jest-dom']}
};
run(): void {
super.run();
this.safeExtract('Initializing Jest config', 'jest.config.json');
this.safeExtract('Initializing Babel config', '.babelrc');
Preset
.editJson('jest.config.json')
.merge({setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect']})
.withTitle('Enabling Jest DOM Support')
.if(() => this.getConfiguration('jest-dom'));
Preset
.editJson('tsconfig.json').merge({
exclude: ['src/**/*.spec.ts']
})
.withTitle('Modifying TypeScript config for project')
.if(() => this.getConfiguration('ts'));
Preset
.editJson('jest.config.json').merge({
transform: {
'^.+\\.svelte$': ['svelte-jester/dist/transformer.mjs', {preprocess: true}],
'^.+\\.ts$': 'ts-jest'
},
moduleFileExtensions: ['ts'],
extensionsToTreatAsEsm: ['.ts'],
globals: {
'ts-jest': {
tsconfig: 'tsconfig.spec.json',
'useESM': true
}
}
})
.withTitle('Modifying Jest config for TypeScript transformation')
.if(() => this.getConfiguration('ts'));
Preset
.editJson('jest.config.json').merge({
testEnvironment: 'jsdom'
})
.withTitle('Modifying Jest config to enable JSDOM environment')
.if(() => this.getConfiguration('jsdom'));
this.safeExtract('Initializing TypeScript config for tests', 'tsconfig.spec.json')
.if(() => this.getConfiguration('ts'));
this.safeExtract('Initializing example test file', 'index.spec.ts')
.to('src/routes/')
.if(() => this.getConfiguration('examples') && this.getConfiguration('ts') && !this.getConfiguration('jest-dom'));
this.safeExtract('Initializing example test file', 'index.spec.js')
.to('src/routes/')
.if(() => this.getConfiguration('examples') && !this.getConfiguration('ts') && !this.getConfiguration('jest-dom'));
this.safeExtract('Initializing example test file', 'index-dom.spec.ts')
.to('src/routes/')
.if(() => this.getConfiguration('examples') && this.getConfiguration('ts') && this.getConfiguration('jest-dom'));
this.safeExtract('Initializing example test file', 'index-dom.spec.js')
.to('src/routes/')
.if(() => this.getConfiguration('examples') && !this.getConfiguration('ts') && this.getConfiguration('jest-dom'));
Preset
.editJson('package.json')
.merge({scripts: {'test': 'cross-env NODE_OPTIONS=--experimental-vm-modules jest src --config jest.config.json', 'test:watch': 'npm run test -- --watch'}})
.withTitle('Adding test scripts to package.json');
Preset
.instruct(`Run ${color.magenta("npm install")}, ${color.magenta("pnpm install")}, or ${color.magenta("yarn")} to install dependencies`)
.withHeading("What's next?");
}
}
new SvelteJestAdder().run();