@quilted/create 0.1.67 → 0.1.69
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.
- package/CHANGELOG.md +14 -0
- package/build/cjs/_virtual/estree.cjs +5 -0
- package/build/cjs/cli.cjs +14 -3
- package/build/cjs/module.cjs +1 -1
- package/build/cjs/node_modules/.pnpm/arg@5.0.2/node_modules/arg/index.cjs +2 -2
- package/build/cjs/node_modules/.pnpm/prettier@3.0.0/node_modules/prettier/plugins/estree.cjs +70 -0
- package/build/cjs/packages/cli-kit/source/prompt.cjs +2 -2
- package/build/cjs/packages/events/source/{abort.cjs → abort/AbortError.cjs} +6 -0
- package/build/cjs/service.cjs +298 -0
- package/build/cjs/shared.cjs +4 -8
- package/build/esm/_virtual/estree.mjs +3 -0
- package/build/esm/app.mjs +2 -2
- package/build/esm/cli.mjs +15 -4
- package/build/esm/module.mjs +4 -4
- package/build/esm/node_modules/.pnpm/arg@5.0.2/node_modules/arg/index.mjs +2 -2
- package/build/esm/node_modules/.pnpm/prettier@3.0.0/node_modules/prettier/plugins/estree.mjs +65 -0
- package/build/esm/package.mjs +2 -2
- package/build/esm/packages/cli-kit/source/prompt.mjs +1 -1
- package/build/esm/packages/events/source/{abort.mjs → abort/AbortError.mjs} +6 -0
- package/build/esm/service.mjs +276 -0
- package/build/esm/shared.mjs +4 -8
- package/build/esnext/_virtual/estree.esnext +3 -0
- package/build/esnext/module.esnext +2 -2
- package/build/esnext/node_modules/.pnpm/prettier@3.0.0/node_modules/prettier/plugins/estree.esnext +65 -0
- package/build/esnext/packages/cli-kit/source/prompt.esnext +1 -1
- package/build/esnext/packages/events/source/{abort.esnext → abort/AbortError.esnext} +6 -0
- package/build/esnext/shared.esnext +4 -8
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/typescript/help.d.ts +1 -1
- package/build/typescript/help.d.ts.map +1 -1
- package/build/typescript/service.d.ts +2 -0
- package/build/typescript/service.d.ts.map +1 -0
- package/build/typescript/shared/prompts.d.ts +1 -1
- package/build/typescript/shared/prompts.d.ts.map +1 -1
- package/build/typescript/shared.d.ts +1 -1
- package/build/typescript/shared.d.ts.map +1 -1
- package/package.json +1 -1
- package/source/cli.ts +7 -1
- package/source/help.ts +1 -1
- package/source/module.ts +1 -1
- package/source/service.ts +388 -0
- package/source/shared/prompts.ts +1 -1
- package/source/shared.ts +12 -7
- package/templates/app-basic/features/Start/Start.test.tsx +2 -2
- package/templates/{app-trpc/tests → app-basic/tests/render}/render.tsx +3 -34
- package/templates/app-basic/tests/render/types.ts +29 -0
- package/templates/app-basic/tests/render.ts +6 -0
- package/templates/app-graphql/features/Start/Start.test.tsx +3 -3
- package/templates/app-graphql/tests/render/render.tsx +59 -0
- package/templates/app-graphql/tests/render/types.ts +63 -0
- package/templates/app-graphql/tests/render.ts +7 -0
- package/templates/app-trpc/App.tsx +32 -2
- package/templates/{app-basic/tests → app-trpc/tests/render}/render.tsx +12 -37
- package/templates/app-trpc/tests/render/types.ts +35 -0
- package/templates/app-trpc/tests/render.ts +6 -0
- package/templates/service-basic/package.json +12 -0
- package/templates/service-basic/quilt.project.ts +9 -0
- package/templates/service-basic/service.ts +7 -0
- package/templates/service-basic/tsconfig.json +9 -0
- package/templates/app-graphql/tests/render.tsx +0 -126
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
import arg from 'arg';
|
|
5
|
+
import * as color from 'colorette';
|
|
6
|
+
import {stripIndent} from 'common-tags';
|
|
7
|
+
|
|
8
|
+
import {printHelp} from './help.ts';
|
|
9
|
+
import {
|
|
10
|
+
format,
|
|
11
|
+
loadTemplate,
|
|
12
|
+
createOutputTarget,
|
|
13
|
+
isEmpty,
|
|
14
|
+
emptyDirectory,
|
|
15
|
+
toValidPackageName,
|
|
16
|
+
relativeDirectoryForDisplay,
|
|
17
|
+
mergeWorkspaceAndProjectPackageJsons,
|
|
18
|
+
} from './shared.ts';
|
|
19
|
+
import {
|
|
20
|
+
prompt,
|
|
21
|
+
getInWorkspace,
|
|
22
|
+
getCreateAsMonorepo,
|
|
23
|
+
getExtrasToSetup,
|
|
24
|
+
getPackageManager,
|
|
25
|
+
getShouldInstall,
|
|
26
|
+
} from './shared/prompts.ts';
|
|
27
|
+
import {addToTsConfig} from './shared/tsconfig.ts';
|
|
28
|
+
import {addToPackageManagerWorkspaces} from './shared/package-manager.ts';
|
|
29
|
+
|
|
30
|
+
type Arguments = ReturnType<typeof getArgv>;
|
|
31
|
+
|
|
32
|
+
export async function createService() {
|
|
33
|
+
const argv = getArgv();
|
|
34
|
+
|
|
35
|
+
if (argv['--help']) {
|
|
36
|
+
printHelp({
|
|
37
|
+
kind: 'service',
|
|
38
|
+
packageManager: argv['--package-manager']?.toLowerCase(),
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const inWorkspace = await getInWorkspace(argv);
|
|
44
|
+
const name = await getName(argv);
|
|
45
|
+
const directory = await getDirectory(argv, {name});
|
|
46
|
+
const entry = await getEntry(argv, {name});
|
|
47
|
+
|
|
48
|
+
const createAsMonorepo =
|
|
49
|
+
!inWorkspace &&
|
|
50
|
+
(await getCreateAsMonorepo(argv, {
|
|
51
|
+
type: 'service',
|
|
52
|
+
default: false,
|
|
53
|
+
}));
|
|
54
|
+
const setupExtras = await getExtrasToSetup(argv, {inWorkspace});
|
|
55
|
+
const shouldInstall = await getShouldInstall(argv);
|
|
56
|
+
const packageManager = await getPackageManager(argv, {root: directory});
|
|
57
|
+
|
|
58
|
+
const partOfMonorepo = inWorkspace || createAsMonorepo;
|
|
59
|
+
|
|
60
|
+
const serviceDirectory = createAsMonorepo
|
|
61
|
+
? path.join(directory, toValidPackageName(name))
|
|
62
|
+
: directory;
|
|
63
|
+
|
|
64
|
+
if (fs.existsSync(directory)) {
|
|
65
|
+
await emptyDirectory(directory);
|
|
66
|
+
|
|
67
|
+
if (serviceDirectory !== directory) {
|
|
68
|
+
fs.mkdirSync(serviceDirectory, {recursive: true});
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
fs.mkdirSync(serviceDirectory, {recursive: true});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const rootDirectory = inWorkspace ? process.cwd() : directory;
|
|
75
|
+
const outputRoot = createOutputTarget(rootDirectory);
|
|
76
|
+
const serviceTemplate = loadTemplate('service-basic');
|
|
77
|
+
const workspaceTemplate = loadTemplate('workspace');
|
|
78
|
+
|
|
79
|
+
// If we aren’t already in a workspace, copy the workspace files over, which
|
|
80
|
+
// are needed if we are making a monorepo or not.
|
|
81
|
+
if (!inWorkspace) {
|
|
82
|
+
await workspaceTemplate.copy(directory, (file) => {
|
|
83
|
+
// When this is a single project, we use the project’s Quilt configuration as the base.
|
|
84
|
+
if (file === 'quilt.workspace.ts') return createAsMonorepo;
|
|
85
|
+
|
|
86
|
+
// We need to make some adjustments to the root package.json
|
|
87
|
+
if (file === 'package.json') return false;
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// If we are creating a monorepo, we need to add the root package.json and
|
|
93
|
+
// package manager workspace configuration.
|
|
94
|
+
if (createAsMonorepo) {
|
|
95
|
+
const serviceRelativeToRoot = relativeDirectoryForDisplay(
|
|
96
|
+
path.relative(directory, serviceDirectory),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const workspacePackageJson = JSON.parse(
|
|
100
|
+
await workspaceTemplate.read('package.json'),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
workspacePackageJson.name = toValidPackageName(name!);
|
|
104
|
+
workspacePackageJson.workspaces = [serviceRelativeToRoot, './packages/*'];
|
|
105
|
+
|
|
106
|
+
if (packageManager.type === 'pnpm') {
|
|
107
|
+
await outputRoot.write(
|
|
108
|
+
'pnpm-workspace.yaml',
|
|
109
|
+
await format(
|
|
110
|
+
`
|
|
111
|
+
packages:
|
|
112
|
+
- '${serviceRelativeToRoot}'
|
|
113
|
+
- './packages/*'
|
|
114
|
+
`,
|
|
115
|
+
{as: 'yaml'},
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await outputRoot.write(
|
|
121
|
+
'package.json',
|
|
122
|
+
await format(JSON.stringify(workspacePackageJson), {
|
|
123
|
+
as: 'json-stringify',
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
} else {
|
|
127
|
+
const [projectPackageJson, projectTSConfig, workspacePackageJson] =
|
|
128
|
+
await Promise.all([
|
|
129
|
+
serviceTemplate
|
|
130
|
+
.read('package.json')
|
|
131
|
+
.then((content) => JSON.parse(content)),
|
|
132
|
+
serviceTemplate
|
|
133
|
+
.read('tsconfig.json')
|
|
134
|
+
.then((content) => JSON.parse(content)),
|
|
135
|
+
workspaceTemplate
|
|
136
|
+
.read('package.json')
|
|
137
|
+
.then((content) => JSON.parse(content)),
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
const combinedPackageJson = mergeWorkspaceAndProjectPackageJsons(
|
|
141
|
+
projectPackageJson,
|
|
142
|
+
workspacePackageJson,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
adjustPackageJson(combinedPackageJson, {name, entry});
|
|
146
|
+
delete combinedPackageJson.workspaces;
|
|
147
|
+
|
|
148
|
+
let quiltProject = await serviceTemplate.read('quilt.project.ts');
|
|
149
|
+
quiltProject = quiltProject
|
|
150
|
+
.replace('quiltService', 'quiltWorkspace, quiltService')
|
|
151
|
+
.replace('quiltService(', 'quiltWorkspace(), quiltService(')
|
|
152
|
+
.replace('service.ts', entry.replace(/^\.[/]/, ''));
|
|
153
|
+
|
|
154
|
+
await outputRoot.write(
|
|
155
|
+
'quilt.project.ts',
|
|
156
|
+
await format(quiltProject, {as: 'typescript'}),
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
await outputRoot.write(
|
|
160
|
+
'package.json',
|
|
161
|
+
await format(JSON.stringify(combinedPackageJson), {
|
|
162
|
+
as: 'json-stringify',
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
await outputRoot.write(
|
|
167
|
+
'tsconfig.json',
|
|
168
|
+
await format(JSON.stringify(projectTSConfig), {as: 'json'}),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (setupExtras.has('github')) {
|
|
173
|
+
await loadTemplate('github').copy(directory);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (setupExtras.has('vscode')) {
|
|
177
|
+
await loadTemplate('vscode').copy(directory);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await serviceTemplate.copy(serviceDirectory, (file) => {
|
|
182
|
+
// If we are in a monorepo, we can use all the template files as they are
|
|
183
|
+
if (file === 'tsconfig.json') {
|
|
184
|
+
return partOfMonorepo;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// We will adjust the entry file and quilt project file
|
|
188
|
+
if (file === 'service.ts' || file === 'quilt.project.ts') {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// We need to make some adjustments the project’s package.json
|
|
193
|
+
return file !== 'package.json';
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await outputRoot.write(
|
|
197
|
+
path.join(serviceDirectory, entry),
|
|
198
|
+
await serviceTemplate.read('service.ts'),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
let quiltProject = await serviceTemplate.read('quilt.project.ts');
|
|
202
|
+
quiltProject = quiltProject.replace(
|
|
203
|
+
'service.ts',
|
|
204
|
+
entry.replace(/^\.[/]/, ''),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
await outputRoot.write(
|
|
208
|
+
path.join(serviceDirectory, 'quilt.project.ts'),
|
|
209
|
+
await format(quiltProject, {as: 'typescript'}),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
if (partOfMonorepo) {
|
|
213
|
+
// Write the app’s package.json (the root one was already created)
|
|
214
|
+
const projectPackageJson = JSON.parse(
|
|
215
|
+
await serviceTemplate.read('package.json'),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
adjustPackageJson(projectPackageJson, {name, entry});
|
|
219
|
+
|
|
220
|
+
await outputRoot.write(
|
|
221
|
+
path.join(serviceDirectory, 'package.json'),
|
|
222
|
+
await format(JSON.stringify(projectPackageJson), {
|
|
223
|
+
as: 'json-stringify',
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
await Promise.all([
|
|
228
|
+
addToTsConfig(serviceDirectory, outputRoot),
|
|
229
|
+
addToPackageManagerWorkspaces(
|
|
230
|
+
serviceDirectory,
|
|
231
|
+
outputRoot,
|
|
232
|
+
packageManager.type,
|
|
233
|
+
),
|
|
234
|
+
]);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (shouldInstall) {
|
|
238
|
+
console.log();
|
|
239
|
+
// TODO: better loading, handle errors
|
|
240
|
+
await packageManager.install();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const commands: string[] = [];
|
|
244
|
+
|
|
245
|
+
if (!inWorkspace && directory !== process.cwd()) {
|
|
246
|
+
commands.push(
|
|
247
|
+
`cd ${color.cyan(
|
|
248
|
+
relativeDirectoryForDisplay(path.relative(process.cwd(), directory)),
|
|
249
|
+
)} ${color.dim('# Move into your new service’s directory')}`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!shouldInstall) {
|
|
254
|
+
commands.push(
|
|
255
|
+
`${packageManager.commands.install()} ${color.dim(
|
|
256
|
+
'# Install all your dependencies',
|
|
257
|
+
)}`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (commands.length === 0) {
|
|
262
|
+
console.log();
|
|
263
|
+
console.log('Your new service is ready to go!');
|
|
264
|
+
} else {
|
|
265
|
+
const whatsNext = stripIndent`
|
|
266
|
+
Your new service is ready to go! There’s just ${
|
|
267
|
+
commands.length > 1 ? 'a few more steps' : 'one more step'
|
|
268
|
+
} you’ll need to take
|
|
269
|
+
in order to start developing:
|
|
270
|
+
`;
|
|
271
|
+
|
|
272
|
+
console.log();
|
|
273
|
+
console.log(whatsNext);
|
|
274
|
+
console.log();
|
|
275
|
+
console.log(commands.map((command) => ` ${command}`).join('\n'));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const followUp = stripIndent`
|
|
279
|
+
Quilt can also help you build, develop, test, lint, and type-check your new service.
|
|
280
|
+
You can learn more about building services with Quilt by reading the documentation:
|
|
281
|
+
${color.underline(
|
|
282
|
+
color.magenta(
|
|
283
|
+
'https://github.com/lemonmade/quilt/tree/main/documentation',
|
|
284
|
+
),
|
|
285
|
+
)}
|
|
286
|
+
|
|
287
|
+
Have fun! 🎉
|
|
288
|
+
`;
|
|
289
|
+
|
|
290
|
+
console.log();
|
|
291
|
+
console.log(followUp);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Argument handling
|
|
295
|
+
|
|
296
|
+
function getArgv() {
|
|
297
|
+
const argv = arg(
|
|
298
|
+
{
|
|
299
|
+
'--yes': Boolean,
|
|
300
|
+
'-y': '--yes',
|
|
301
|
+
'--name': String,
|
|
302
|
+
'--directory': String,
|
|
303
|
+
'--entry': String,
|
|
304
|
+
'--install': Boolean,
|
|
305
|
+
'--no-install': Boolean,
|
|
306
|
+
'--monorepo': Boolean,
|
|
307
|
+
'--no-monorepo': Boolean,
|
|
308
|
+
'--package-manager': String,
|
|
309
|
+
'--extras': [String],
|
|
310
|
+
'--no-extras': Boolean,
|
|
311
|
+
'--help': Boolean,
|
|
312
|
+
'-h': '--help',
|
|
313
|
+
},
|
|
314
|
+
{permissive: true},
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
return argv;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function getName(argv: Arguments) {
|
|
321
|
+
let {'--name': name} = argv;
|
|
322
|
+
|
|
323
|
+
if (name == null) {
|
|
324
|
+
name = await prompt({
|
|
325
|
+
type: 'text',
|
|
326
|
+
message: 'What would you like to name your new service?',
|
|
327
|
+
initial: 'my-service',
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return name!;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function getEntry(argv: Arguments, {name}: {name: string}) {
|
|
335
|
+
if (argv['--entry']) {
|
|
336
|
+
return argv['--entry'];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return `${toValidPackageName(name)}.ts`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function getDirectory(argv: Arguments, {name}: {name: string}) {
|
|
343
|
+
let directory = path.resolve(
|
|
344
|
+
argv['--directory'] ?? toValidPackageName(name!),
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
while (!argv['--yes']) {
|
|
348
|
+
if (fs.existsSync(directory) && !(await isEmpty(directory))) {
|
|
349
|
+
const relativeDirectory = path.relative(process.cwd(), directory);
|
|
350
|
+
|
|
351
|
+
const empty = await prompt({
|
|
352
|
+
type: 'confirm',
|
|
353
|
+
message: `Directory ${color.bold(
|
|
354
|
+
relativeDirectoryForDisplay(relativeDirectory),
|
|
355
|
+
)} is not empty, is it safe to empty it?`,
|
|
356
|
+
initial: true,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
if (empty) break;
|
|
360
|
+
|
|
361
|
+
const promptDirectory = await prompt({
|
|
362
|
+
type: 'text',
|
|
363
|
+
message: 'What directory do you want to create your new service in?',
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
directory = path.resolve(promptDirectory);
|
|
367
|
+
} else {
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return directory;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function adjustPackageJson(
|
|
376
|
+
packageJson: Record<string, any>,
|
|
377
|
+
{
|
|
378
|
+
name,
|
|
379
|
+
}: {
|
|
380
|
+
name: string;
|
|
381
|
+
entry: string;
|
|
382
|
+
},
|
|
383
|
+
) {
|
|
384
|
+
packageJson.name = name;
|
|
385
|
+
packageJson.main = `./build/runtime/runtime.js`;
|
|
386
|
+
|
|
387
|
+
return packageJson;
|
|
388
|
+
}
|
package/source/shared/prompts.ts
CHANGED
|
@@ -35,7 +35,7 @@ export async function getCreateAsMonorepo(
|
|
|
35
35
|
{
|
|
36
36
|
type,
|
|
37
37
|
default: defaultCreateAsMonorepo = true,
|
|
38
|
-
}: {type: 'app' | 'package' | 'module'; default?: boolean},
|
|
38
|
+
}: {type: 'app' | 'package' | 'module' | 'service'; default?: boolean},
|
|
39
39
|
) {
|
|
40
40
|
let createAsMonorepo: boolean;
|
|
41
41
|
|
package/source/shared.ts
CHANGED
|
@@ -13,6 +13,7 @@ export function loadTemplate(
|
|
|
13
13
|
| 'app-graphql'
|
|
14
14
|
| 'app-trpc'
|
|
15
15
|
| 'module'
|
|
16
|
+
| 'service-basic'
|
|
16
17
|
| 'workspace'
|
|
17
18
|
| 'github'
|
|
18
19
|
| 'vscode',
|
|
@@ -84,6 +85,7 @@ async function templateDirectory(
|
|
|
84
85
|
| 'app-graphql'
|
|
85
86
|
| 'app-trpc'
|
|
86
87
|
| 'module'
|
|
88
|
+
| 'service-basic'
|
|
87
89
|
| 'workspace'
|
|
88
90
|
| 'github'
|
|
89
91
|
| 'vscode',
|
|
@@ -162,14 +164,17 @@ export async function format(
|
|
|
162
164
|
) {
|
|
163
165
|
const [
|
|
164
166
|
{format: rootFormat, default: prettier},
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
babel,
|
|
168
|
+
typescript,
|
|
169
|
+
yaml,
|
|
170
|
+
estree,
|
|
168
171
|
] = await Promise.all([
|
|
169
172
|
import('prettier/standalone'),
|
|
170
|
-
import('prettier/
|
|
171
|
-
import('prettier/
|
|
172
|
-
import('prettier/
|
|
173
|
+
import('prettier/plugins/babel'),
|
|
174
|
+
import('prettier/plugins/typescript'),
|
|
175
|
+
import('prettier/plugins/yaml'),
|
|
176
|
+
// @ts-expect-error Types are not generated correctly for this entry
|
|
177
|
+
import('prettier/plugins/estree'),
|
|
173
178
|
]);
|
|
174
179
|
|
|
175
180
|
// CJS workaround
|
|
@@ -181,7 +186,7 @@ export async function format(
|
|
|
181
186
|
singleQuote: true,
|
|
182
187
|
trailingComma: 'all',
|
|
183
188
|
parser,
|
|
184
|
-
plugins: [babel, typescript, yaml],
|
|
189
|
+
plugins: [babel, typescript, yaml, estree],
|
|
185
190
|
});
|
|
186
191
|
}
|
|
187
192
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {describe, it, expect} from '@quilted/quilt/testing';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {renderApp} from '~/tests/render.ts';
|
|
4
4
|
|
|
5
5
|
import Start from './Start.tsx';
|
|
6
6
|
|
|
7
7
|
describe('<Start />', () => {
|
|
8
8
|
it('includes a welcome message', async () => {
|
|
9
|
-
const start = await
|
|
9
|
+
const start = await renderApp(<Start />);
|
|
10
10
|
expect(start).toContainReactText('Hello world!');
|
|
11
11
|
});
|
|
12
12
|
});
|
|
@@ -1,49 +1,18 @@
|
|
|
1
|
-
import '@quilted/quilt/matchers';
|
|
2
|
-
|
|
3
1
|
import {
|
|
4
2
|
createRender,
|
|
5
3
|
QuiltAppTesting,
|
|
6
4
|
createTestRouter,
|
|
7
5
|
} from '@quilted/quilt/testing';
|
|
8
6
|
|
|
9
|
-
import {
|
|
10
|
-
AppContextReact,
|
|
11
|
-
type AppContext as AppContextType,
|
|
12
|
-
} from '~/shared/context.ts';
|
|
13
|
-
|
|
14
|
-
type Router = ReturnType<typeof createTestRouter>;
|
|
15
|
-
|
|
16
|
-
export {createTestRouter};
|
|
17
|
-
|
|
18
|
-
export interface RenderOptions {
|
|
19
|
-
/**
|
|
20
|
-
* A custom router to use for this component test. You can use a
|
|
21
|
-
* custom router to simulate a particular URL, and you can spy on
|
|
22
|
-
* its navigation method to check that components navigate as
|
|
23
|
-
* you expect.
|
|
24
|
-
*/
|
|
25
|
-
router?: Router;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* A custom locale to use for this component test.
|
|
29
|
-
*/
|
|
30
|
-
locale?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface RenderContext extends AppContextType {
|
|
34
|
-
/**
|
|
35
|
-
* The router used for this component test.
|
|
36
|
-
*/
|
|
37
|
-
router: Router;
|
|
38
|
-
}
|
|
7
|
+
import {AppContextReact} from '~/shared/context.ts';
|
|
39
8
|
|
|
40
|
-
|
|
9
|
+
import {RenderOptions, RenderContext, RenderActions} from './types.ts';
|
|
41
10
|
|
|
42
11
|
/**
|
|
43
12
|
* Renders a component with test-friendly versions of all global
|
|
44
13
|
* context available to the application.
|
|
45
14
|
*/
|
|
46
|
-
export const
|
|
15
|
+
export const renderApp = createRender<
|
|
47
16
|
RenderOptions,
|
|
48
17
|
RenderContext,
|
|
49
18
|
RenderActions,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type {createTestRouter} from '@quilted/quilt/testing';
|
|
2
|
+
|
|
3
|
+
import type {AppContext} from '~/shared/context.ts';
|
|
4
|
+
|
|
5
|
+
type Router = ReturnType<typeof createTestRouter>;
|
|
6
|
+
|
|
7
|
+
export interface RenderOptions {
|
|
8
|
+
/**
|
|
9
|
+
* A custom router to use for this component test. You can use a
|
|
10
|
+
* custom router to simulate a particular URL, and you can spy on
|
|
11
|
+
* its navigation method to check that components navigate as
|
|
12
|
+
* you expect.
|
|
13
|
+
*/
|
|
14
|
+
readonly router?: Router;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A custom locale to use for this component test.
|
|
18
|
+
*/
|
|
19
|
+
readonly locale?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RenderContext extends AppContext {
|
|
23
|
+
/**
|
|
24
|
+
* The router used for this component test.
|
|
25
|
+
*/
|
|
26
|
+
readonly router: Router;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RenderActions extends Record<string, never> {}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {describe, it, expect} from '@quilted/quilt/testing';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
createGraphQLController,
|
|
4
|
+
renderApp,
|
|
6
5
|
fillGraphQL,
|
|
6
|
+
createGraphQLController,
|
|
7
7
|
} from '~/tests/render.tsx';
|
|
8
8
|
|
|
9
9
|
import Start from './Start.tsx';
|
|
@@ -14,7 +14,7 @@ describe('<Start />', () => {
|
|
|
14
14
|
const name = 'Winston';
|
|
15
15
|
const graphql = createGraphQLController(fillGraphQL(startQuery, {name}));
|
|
16
16
|
|
|
17
|
-
const start = await
|
|
17
|
+
const start = await renderApp(<Start />, {graphql});
|
|
18
18
|
|
|
19
19
|
expect(graphql).toHavePerformedGraphQLOperation(startQuery);
|
|
20
20
|
expect(start).toContainReactText(`Hello ${name}!`);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRender,
|
|
3
|
+
QuiltAppTesting,
|
|
4
|
+
createTestRouter,
|
|
5
|
+
} from '@quilted/quilt/testing';
|
|
6
|
+
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
|
|
7
|
+
|
|
8
|
+
import {AppContextReact} from '~/shared/context.ts';
|
|
9
|
+
|
|
10
|
+
import {TestGraphQL, createGraphQLController} from '../graphql.ts';
|
|
11
|
+
|
|
12
|
+
import {RenderOptions, RenderContext, RenderActions} from './types.ts';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Renders a component with test-friendly versions of all global
|
|
16
|
+
* context available to the application.
|
|
17
|
+
*/
|
|
18
|
+
export const renderApp = createRender<
|
|
19
|
+
RenderOptions,
|
|
20
|
+
RenderContext,
|
|
21
|
+
RenderActions,
|
|
22
|
+
true
|
|
23
|
+
>({
|
|
24
|
+
// Create context that can be used by the `render` function, and referenced by test
|
|
25
|
+
// authors on the `root.context` property. Context is used to share data between your
|
|
26
|
+
// React tree and your test code, and is ideal for mocking out global context providers.
|
|
27
|
+
context({router = createTestRouter(), graphql = createGraphQLController()}) {
|
|
28
|
+
return {router, graphql, queryClient: new QueryClient()};
|
|
29
|
+
},
|
|
30
|
+
// Render all of our app-wide context providers around each component under test.
|
|
31
|
+
render(element, context, {locale}) {
|
|
32
|
+
const {router, graphql, queryClient} = context;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<QuiltAppTesting routing={router} localization={locale}>
|
|
36
|
+
<AppContextReact.Provider value={context}>
|
|
37
|
+
<TestGraphQL controller={graphql}>
|
|
38
|
+
<QueryClientProvider client={queryClient}>
|
|
39
|
+
{element}
|
|
40
|
+
</QueryClientProvider>
|
|
41
|
+
</TestGraphQL>
|
|
42
|
+
</AppContextReact.Provider>
|
|
43
|
+
</QuiltAppTesting>
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
async afterRender(wrapper) {
|
|
47
|
+
// If your components need to resolve data before they can render, you can
|
|
48
|
+
// use this hook to wait for that data to be ready. This will cause the
|
|
49
|
+
// `render` function to return a promise, so that the component is only usable
|
|
50
|
+
// once the data is ready.
|
|
51
|
+
|
|
52
|
+
await wrapper.act(async () => {
|
|
53
|
+
await wrapper.context.graphql.resolveAll();
|
|
54
|
+
|
|
55
|
+
// react-query needs an extra tick to set state in response to GraphQL queries.
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type {createTestRouter} from '@quilted/quilt/testing';
|
|
2
|
+
import type {QueryClient} from '@tanstack/react-query';
|
|
3
|
+
|
|
4
|
+
import type {AppContext} from '~/shared/context.ts';
|
|
5
|
+
|
|
6
|
+
import type {GraphQLController} from '../graphql.ts';
|
|
7
|
+
|
|
8
|
+
type Router = ReturnType<typeof createTestRouter>;
|
|
9
|
+
|
|
10
|
+
export interface RenderOptions {
|
|
11
|
+
/**
|
|
12
|
+
* A custom router to use for this component test. You can use a
|
|
13
|
+
* custom router to simulate a particular URL, and you can spy on
|
|
14
|
+
* its navigation method to check that components navigate as
|
|
15
|
+
* you expect.
|
|
16
|
+
*/
|
|
17
|
+
readonly router?: Router;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* An object that controls the responses to GraphQL queries and mutations
|
|
21
|
+
* for the component under test. You can customize the responses using
|
|
22
|
+
* the `fillGraphQL` and `createGraphQLController` utilities provided
|
|
23
|
+
* by this module.
|
|
24
|
+
*
|
|
25
|
+
* ```tsx
|
|
26
|
+
* import {renderWithAppContext, fillGraphQL, createGraphQLController} from '~/tests/render.tsx';
|
|
27
|
+
*
|
|
28
|
+
* import {MyComponent} from './MyComponent.tsx';
|
|
29
|
+
* import myComponentQuery from './MyComponentQuery.graphql';
|
|
30
|
+
*
|
|
31
|
+
* const myComponent = await renderWithAppContext(<MyComponent />, {
|
|
32
|
+
* graphql: createGraphQLController(
|
|
33
|
+
* fillGraphQL(myComponentQuery, {user: {name: 'Winston'}}),
|
|
34
|
+
* ),
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
readonly graphql?: GraphQLController;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A custom locale to use for this component test.
|
|
42
|
+
*/
|
|
43
|
+
readonly locale?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RenderContext extends AppContext {
|
|
47
|
+
/**
|
|
48
|
+
* The router used for this component test.
|
|
49
|
+
*/
|
|
50
|
+
readonly router: Router;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The GraphQL controller used for this component test.
|
|
54
|
+
*/
|
|
55
|
+
readonly graphql: GraphQLController;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The react-query client used for this component test.
|
|
59
|
+
*/
|
|
60
|
+
readonly queryClient: QueryClient;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RenderActions extends Record<string, never> {}
|