@quilted/create 0.1.49 → 0.1.51
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/app.cjs +16 -211
- package/build/cjs/index.cjs +13 -1
- package/build/cjs/index2.cjs +157 -326
- package/build/cjs/index3.cjs +307 -7793
- package/build/cjs/index4.cjs +7402 -7181
- package/build/cjs/index5.cjs +7633 -0
- package/build/cjs/module.cjs +302 -0
- package/build/cjs/package.cjs +1 -1
- package/build/cjs/shared/package-manager.cjs +2 -2
- package/build/esm/app.mjs +1 -196
- package/build/esm/index.mjs +13 -1
- package/build/esm/index2.mjs +157 -325
- package/build/esm/index3.mjs +306 -7793
- package/build/esm/index4.mjs +7402 -7174
- package/build/esm/index5.mjs +7624 -0
- package/build/esm/module.mjs +280 -0
- package/build/esm/package.mjs +1 -1
- package/build/esm/shared/package-manager.mjs +2 -2
- 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/module.d.ts +2 -0
- package/build/typescript/module.d.ts.map +1 -0
- package/build/typescript/shared/prompts.d.ts +2 -2
- 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/create.ts +7 -1
- package/source/help.ts +6 -1
- package/source/module.ts +412 -0
- package/source/shared/prompts.ts +2 -2
- package/source/shared.ts +2 -0
- package/templates/app-basic/features/Start/Start.test.tsx +2 -2
- package/templates/app-basic/foundation/Head/Head.test.tsx +3 -3
- package/templates/app-basic/foundation/Http/Http.test.tsx +3 -3
- package/templates/app-basic/package.json +1 -1
- package/templates/app-basic/tests/{mount.tsx → render.tsx} +11 -11
- package/templates/app-empty/package.json +1 -1
- package/templates/app-graphql/features/Start/Start.test.tsx +3 -3
- package/templates/app-graphql/foundation/Head/Head.test.tsx +3 -3
- package/templates/app-graphql/foundation/Http/Http.test.tsx +3 -3
- package/templates/app-graphql/package.json +2 -2
- package/templates/app-graphql/tests/{mount.tsx → render.tsx} +13 -13
- package/templates/app-single-file/package.json +1 -1
- package/templates/module/module.ts +3 -0
- package/templates/module/package.json +21 -0
- package/templates/module/quilt.project.ts +5 -0
- package/templates/module/tsconfig.json +9 -0
package/package.json
CHANGED
package/source/create.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {AbortError, stripIndent, color, parseArguments} from '@quilted/cli-kit';
|
|
|
5
5
|
import {printHelp} from './help';
|
|
6
6
|
import {prompt} from './shared';
|
|
7
7
|
|
|
8
|
-
const VALID_PROJECT_KINDS = new Set(['app', 'package']);
|
|
8
|
+
const VALID_PROJECT_KINDS = new Set(['app', 'package', 'module']);
|
|
9
9
|
|
|
10
10
|
run().catch((error) => {
|
|
11
11
|
if (AbortError.test(error)) return;
|
|
@@ -57,6 +57,7 @@ async function run() {
|
|
|
57
57
|
message: 'What kind of project would you like to create?',
|
|
58
58
|
choices: [
|
|
59
59
|
{title: 'App', value: 'app'},
|
|
60
|
+
{title: 'Module', value: 'module'},
|
|
60
61
|
{title: 'Package', value: 'package'},
|
|
61
62
|
],
|
|
62
63
|
});
|
|
@@ -68,6 +69,11 @@ async function run() {
|
|
|
68
69
|
await createApp();
|
|
69
70
|
break;
|
|
70
71
|
}
|
|
72
|
+
case 'module': {
|
|
73
|
+
const {createModule} = await import('./module');
|
|
74
|
+
await createModule();
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
71
77
|
case 'package': {
|
|
72
78
|
const {createProject} = await import('./package');
|
|
73
79
|
await createProject();
|
package/source/help.ts
CHANGED
|
@@ -4,7 +4,11 @@ export function printHelp({
|
|
|
4
4
|
kind,
|
|
5
5
|
options: customOptions,
|
|
6
6
|
packageManager,
|
|
7
|
-
}: {
|
|
7
|
+
}: {
|
|
8
|
+
kind?: 'app' | 'package' | 'module';
|
|
9
|
+
options?: string;
|
|
10
|
+
packageManager?: string;
|
|
11
|
+
} = {}) {
|
|
8
12
|
const command = createCommand(packageManager);
|
|
9
13
|
|
|
10
14
|
const usage = stripIndent`
|
|
@@ -31,6 +35,7 @@ export function printHelp({
|
|
|
31
35
|
|
|
32
36
|
- ${color.magenta('app')}, a web application
|
|
33
37
|
- ${color.magenta('package')}, a shared library of code
|
|
38
|
+
- ${color.magenta('module')}, a standalone JavaScript module for a browser
|
|
34
39
|
|
|
35
40
|
You’ll be asked a few additional questions based on the kind you choose.
|
|
36
41
|
`;
|
package/source/module.ts
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
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';
|
|
9
|
+
import {
|
|
10
|
+
format,
|
|
11
|
+
loadTemplate,
|
|
12
|
+
createOutputTarget,
|
|
13
|
+
isEmpty,
|
|
14
|
+
emptyDirectory,
|
|
15
|
+
toValidPackageName,
|
|
16
|
+
relativeDirectoryForDisplay,
|
|
17
|
+
mergeWorkspaceAndProjectPackageJsons,
|
|
18
|
+
} from './shared';
|
|
19
|
+
import {
|
|
20
|
+
prompt,
|
|
21
|
+
getInWorkspace,
|
|
22
|
+
getCreateAsMonorepo,
|
|
23
|
+
getExtrasToSetup,
|
|
24
|
+
getPackageManager,
|
|
25
|
+
getShouldInstall,
|
|
26
|
+
} from './shared/prompts';
|
|
27
|
+
import {addToTsConfig} from './shared/tsconfig';
|
|
28
|
+
import {addToPackageManagerWorkspaces} from './shared/package-manager';
|
|
29
|
+
|
|
30
|
+
type Arguments = ReturnType<typeof getArgv>;
|
|
31
|
+
|
|
32
|
+
export async function createModule() {
|
|
33
|
+
const argv = getArgv();
|
|
34
|
+
|
|
35
|
+
if (argv['--help']) {
|
|
36
|
+
printHelp({
|
|
37
|
+
kind: 'module',
|
|
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
|
+
|
|
47
|
+
const useReact = await getReact(argv);
|
|
48
|
+
|
|
49
|
+
const entry = `${toValidPackageName(name)}.${useReact ? 'tsx' : 'ts'}`;
|
|
50
|
+
|
|
51
|
+
const createAsMonorepo =
|
|
52
|
+
!inWorkspace &&
|
|
53
|
+
(await getCreateAsMonorepo(argv, {
|
|
54
|
+
type: 'module',
|
|
55
|
+
default: false,
|
|
56
|
+
}));
|
|
57
|
+
const setupExtras = await getExtrasToSetup(argv, {inWorkspace});
|
|
58
|
+
const shouldInstall = await getShouldInstall(argv, {type: 'module'});
|
|
59
|
+
const packageManager = await getPackageManager(argv, {root: directory});
|
|
60
|
+
|
|
61
|
+
const partOfMonorepo = inWorkspace || createAsMonorepo;
|
|
62
|
+
|
|
63
|
+
const moduleDirectory = createAsMonorepo
|
|
64
|
+
? path.join(directory, 'app')
|
|
65
|
+
: directory;
|
|
66
|
+
|
|
67
|
+
if (fs.existsSync(directory)) {
|
|
68
|
+
await emptyDirectory(directory);
|
|
69
|
+
|
|
70
|
+
if (moduleDirectory !== directory) {
|
|
71
|
+
fs.mkdirSync(moduleDirectory, {recursive: true});
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
fs.mkdirSync(moduleDirectory, {recursive: true});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const rootDirectory = inWorkspace ? process.cwd() : directory;
|
|
78
|
+
const outputRoot = createOutputTarget(rootDirectory);
|
|
79
|
+
const moduleTemplate = loadTemplate('module');
|
|
80
|
+
const workspaceTemplate = loadTemplate('workspace');
|
|
81
|
+
|
|
82
|
+
// If we aren’t already in a workspace, copy the workspace files over, which
|
|
83
|
+
// are needed if we are making a monorepo or not.
|
|
84
|
+
if (!inWorkspace) {
|
|
85
|
+
await workspaceTemplate.copy(directory, (file) => {
|
|
86
|
+
// When this is a single project, we use the project’s Quilt configuration as the base.
|
|
87
|
+
if (file === 'quilt.workspace.ts') return createAsMonorepo;
|
|
88
|
+
|
|
89
|
+
// We need to make some adjustments to the root package.json
|
|
90
|
+
if (file === 'package.json') return false;
|
|
91
|
+
|
|
92
|
+
return true;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// If we are creating a monorepo, we need to add the root package.json and
|
|
96
|
+
// package manager workspace configuration.
|
|
97
|
+
if (createAsMonorepo) {
|
|
98
|
+
const moduleRelativeToRoot = relativeDirectoryForDisplay(
|
|
99
|
+
path.relative(directory, moduleDirectory),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const workspacePackageJson = JSON.parse(
|
|
103
|
+
await workspaceTemplate.read('package.json'),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
workspacePackageJson.name = toValidPackageName(name!);
|
|
107
|
+
workspacePackageJson.workspaces = [moduleRelativeToRoot, './packages/*'];
|
|
108
|
+
|
|
109
|
+
if (packageManager.type === 'pnpm') {
|
|
110
|
+
await outputRoot.write(
|
|
111
|
+
'pnpm-workspace.yaml',
|
|
112
|
+
await format(
|
|
113
|
+
`
|
|
114
|
+
packages:
|
|
115
|
+
- '${moduleRelativeToRoot}'
|
|
116
|
+
- './packages/*'
|
|
117
|
+
`,
|
|
118
|
+
{as: 'yaml'},
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await outputRoot.write(
|
|
124
|
+
'package.json',
|
|
125
|
+
await format(JSON.stringify(workspacePackageJson), {
|
|
126
|
+
as: 'json-stringify',
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
} else {
|
|
130
|
+
const [projectPackageJson, projectTSConfig, workspacePackageJson] =
|
|
131
|
+
await Promise.all([
|
|
132
|
+
moduleTemplate
|
|
133
|
+
.read('package.json')
|
|
134
|
+
.then((content) => JSON.parse(content)),
|
|
135
|
+
moduleTemplate
|
|
136
|
+
.read('tsconfig.json')
|
|
137
|
+
.then((content) => JSON.parse(content)),
|
|
138
|
+
workspaceTemplate
|
|
139
|
+
.read('package.json')
|
|
140
|
+
.then((content) => JSON.parse(content)),
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
const combinedPackageJson = mergeWorkspaceAndProjectPackageJsons(
|
|
144
|
+
projectPackageJson,
|
|
145
|
+
workspacePackageJson,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
adjustPackageJson(combinedPackageJson, {name, entry, react: useReact});
|
|
149
|
+
delete combinedPackageJson.workspaces;
|
|
150
|
+
|
|
151
|
+
let quiltProject = await moduleTemplate.read('quilt.project.ts');
|
|
152
|
+
quiltProject = quiltProject
|
|
153
|
+
.replace('quiltModule', 'quiltWorkspace, quiltModule')
|
|
154
|
+
.replace('quiltModule(', 'quiltWorkspace(), quiltModule(');
|
|
155
|
+
|
|
156
|
+
if (!useReact) {
|
|
157
|
+
quiltProject = quiltProject.replace(
|
|
158
|
+
'quiltPackage()',
|
|
159
|
+
'quiltPackage({react: false})',
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await outputRoot.write(
|
|
164
|
+
'quilt.project.ts',
|
|
165
|
+
await format(quiltProject, {as: 'typescript'}),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
await outputRoot.write(
|
|
169
|
+
'package.json',
|
|
170
|
+
await format(JSON.stringify(combinedPackageJson), {
|
|
171
|
+
as: 'json-stringify',
|
|
172
|
+
}),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
await outputRoot.write(
|
|
176
|
+
'tsconfig.json',
|
|
177
|
+
await format(JSON.stringify(projectTSConfig), {as: 'json'}),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (setupExtras.has('github')) {
|
|
182
|
+
await loadTemplate('github').copy(directory);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (setupExtras.has('vscode')) {
|
|
186
|
+
await loadTemplate('vscode').copy(directory);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
await moduleTemplate.copy(moduleDirectory, (file) => {
|
|
191
|
+
// If we are in a monorepo, we can use all the template files as they are
|
|
192
|
+
if (file === 'quilt.project.ts' || file === 'tsconfig.json') {
|
|
193
|
+
return partOfMonorepo;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// We will adjust the entry file
|
|
197
|
+
if (file === 'module.ts') {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// We need to make some adjustments the project’s package.json
|
|
202
|
+
return file !== 'package.json';
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
await outputRoot.write(
|
|
206
|
+
path.join(moduleDirectory, entry),
|
|
207
|
+
await moduleTemplate.read('module.ts'),
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
if (partOfMonorepo) {
|
|
211
|
+
// Write the app’s package.json (the root one was already created)
|
|
212
|
+
const projectPackageJson = JSON.parse(
|
|
213
|
+
await moduleTemplate.read('package.json'),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
adjustPackageJson(projectPackageJson, {name, entry, react: useReact});
|
|
217
|
+
|
|
218
|
+
await outputRoot.write(
|
|
219
|
+
path.join(moduleDirectory, 'package.json'),
|
|
220
|
+
await format(JSON.stringify(projectPackageJson), {
|
|
221
|
+
as: 'json-stringify',
|
|
222
|
+
}),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
await Promise.all([
|
|
226
|
+
addToTsConfig(moduleDirectory, outputRoot),
|
|
227
|
+
addToPackageManagerWorkspaces(
|
|
228
|
+
moduleDirectory,
|
|
229
|
+
outputRoot,
|
|
230
|
+
packageManager.type,
|
|
231
|
+
),
|
|
232
|
+
]);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (shouldInstall) {
|
|
236
|
+
console.log();
|
|
237
|
+
// TODO: better loading, handle errors
|
|
238
|
+
await packageManager.install();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const commands: string[] = [];
|
|
242
|
+
|
|
243
|
+
if (!inWorkspace && directory !== process.cwd()) {
|
|
244
|
+
commands.push(
|
|
245
|
+
`cd ${color.cyan(
|
|
246
|
+
relativeDirectoryForDisplay(path.relative(process.cwd(), directory)),
|
|
247
|
+
)} ${color.dim('# Move into your new module’s directory')}`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!shouldInstall) {
|
|
252
|
+
commands.push(
|
|
253
|
+
`${packageManager.commands.install()} ${color.dim(
|
|
254
|
+
'# Install all your dependencies',
|
|
255
|
+
)}`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (commands.length === 0) {
|
|
260
|
+
console.log();
|
|
261
|
+
console.log('Your new module is ready to go!');
|
|
262
|
+
} else {
|
|
263
|
+
const whatsNext = stripIndent`
|
|
264
|
+
Your new module is ready to go! There’s just ${
|
|
265
|
+
commands.length > 1 ? 'a few more steps' : 'one more step'
|
|
266
|
+
} you’ll need to take
|
|
267
|
+
in order to start developing:
|
|
268
|
+
`;
|
|
269
|
+
|
|
270
|
+
console.log();
|
|
271
|
+
console.log(whatsNext);
|
|
272
|
+
console.log();
|
|
273
|
+
console.log(commands.map((command) => ` ${command}`).join('\n'));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const followUp = stripIndent`
|
|
277
|
+
Quilt can also help you build, test, lint, and type-check your new module.
|
|
278
|
+
You can learn more about building modules with Quilt by reading the documentation:
|
|
279
|
+
${color.underline(
|
|
280
|
+
color.magenta(
|
|
281
|
+
'https://github.com/lemonmade/quilt/tree/main/documentation',
|
|
282
|
+
),
|
|
283
|
+
)}
|
|
284
|
+
|
|
285
|
+
Have fun! 🎉
|
|
286
|
+
`;
|
|
287
|
+
|
|
288
|
+
console.log();
|
|
289
|
+
console.log(followUp);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Argument handling
|
|
293
|
+
|
|
294
|
+
function getArgv() {
|
|
295
|
+
const argv = arg(
|
|
296
|
+
{
|
|
297
|
+
'--yes': Boolean,
|
|
298
|
+
'-y': '--yes',
|
|
299
|
+
'--name': String,
|
|
300
|
+
'--directory': String,
|
|
301
|
+
'--install': Boolean,
|
|
302
|
+
'--no-install': Boolean,
|
|
303
|
+
'--monorepo': Boolean,
|
|
304
|
+
'--no-monorepo': Boolean,
|
|
305
|
+
'--package-manager': String,
|
|
306
|
+
'--extras': [String],
|
|
307
|
+
'--no-extras': Boolean,
|
|
308
|
+
'--react': Boolean,
|
|
309
|
+
'--no-react': Boolean,
|
|
310
|
+
'--help': Boolean,
|
|
311
|
+
'-h': '--help',
|
|
312
|
+
},
|
|
313
|
+
{permissive: true},
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
return argv;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function getName(argv: Arguments) {
|
|
320
|
+
let {'--name': name} = argv;
|
|
321
|
+
|
|
322
|
+
if (name == null) {
|
|
323
|
+
name = await prompt({
|
|
324
|
+
type: 'text',
|
|
325
|
+
message: 'What would you like to name your new module?',
|
|
326
|
+
initial: 'my-module',
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return name!;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function getDirectory(argv: Arguments, {name}: {name: string}) {
|
|
334
|
+
let directory = path.resolve(
|
|
335
|
+
argv['--directory'] ?? toValidPackageName(name!),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
while (!argv['--yes']) {
|
|
339
|
+
if (fs.existsSync(directory) && !(await isEmpty(directory))) {
|
|
340
|
+
const relativeDirectory = path.relative(process.cwd(), directory);
|
|
341
|
+
|
|
342
|
+
const empty = await prompt({
|
|
343
|
+
type: 'confirm',
|
|
344
|
+
message: `Directory ${color.bold(
|
|
345
|
+
relativeDirectoryForDisplay(relativeDirectory),
|
|
346
|
+
)} is not empty, is it safe to empty it?`,
|
|
347
|
+
initial: true,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
if (empty) break;
|
|
351
|
+
|
|
352
|
+
const promptDirectory = await prompt({
|
|
353
|
+
type: 'text',
|
|
354
|
+
message: 'What directory do you want to create your new module in?',
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
directory = path.resolve(promptDirectory);
|
|
358
|
+
} else {
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return directory;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function getReact(args: Arguments) {
|
|
367
|
+
let useReact: boolean;
|
|
368
|
+
|
|
369
|
+
if (args['--react'] || args['--yes']) {
|
|
370
|
+
useReact = true;
|
|
371
|
+
} else if (args['--no-react']) {
|
|
372
|
+
useReact = false;
|
|
373
|
+
} else {
|
|
374
|
+
useReact = await prompt({
|
|
375
|
+
type: 'confirm',
|
|
376
|
+
message: 'Will this module depend on React?',
|
|
377
|
+
initial: false,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return useReact;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function adjustPackageJson(
|
|
385
|
+
packageJson: Record<string, any>,
|
|
386
|
+
{
|
|
387
|
+
name,
|
|
388
|
+
entry,
|
|
389
|
+
react,
|
|
390
|
+
}: {
|
|
391
|
+
name: string;
|
|
392
|
+
entry: string;
|
|
393
|
+
react: boolean;
|
|
394
|
+
},
|
|
395
|
+
) {
|
|
396
|
+
packageJson.name = name;
|
|
397
|
+
packageJson.main = `./${entry}`;
|
|
398
|
+
|
|
399
|
+
if (!react) {
|
|
400
|
+
delete packageJson.devDependencies['@types/react'];
|
|
401
|
+
delete packageJson.devDependencies['@types/react-dom'];
|
|
402
|
+
delete packageJson.devDependencies['preact'];
|
|
403
|
+
delete packageJson.devDependencies['react'];
|
|
404
|
+
delete packageJson.devDependencies['react-dom'];
|
|
405
|
+
|
|
406
|
+
packageJson.eslintConfig.extends = packageJson.eslintConfig.extends.filter(
|
|
407
|
+
(extend: string) => !extend.includes('react'),
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return packageJson;
|
|
412
|
+
}
|
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'; default?: boolean},
|
|
38
|
+
}: {type: 'app' | 'package' | 'module'; default?: boolean},
|
|
39
39
|
) {
|
|
40
40
|
let createAsMonorepo: boolean;
|
|
41
41
|
|
|
@@ -56,7 +56,7 @@ export async function getCreateAsMonorepo(
|
|
|
56
56
|
|
|
57
57
|
export async function getShouldInstall(
|
|
58
58
|
argv: BaseArguments,
|
|
59
|
-
{type}: {type: 'app' | 'package'},
|
|
59
|
+
{type}: {type: 'app' | 'package' | 'module'},
|
|
60
60
|
) {
|
|
61
61
|
let shouldInstall: boolean;
|
|
62
62
|
|
package/source/shared.ts
CHANGED
|
@@ -11,6 +11,7 @@ export function loadTemplate(
|
|
|
11
11
|
| 'app-single-file'
|
|
12
12
|
| 'app-empty'
|
|
13
13
|
| 'app-graphql'
|
|
14
|
+
| 'module'
|
|
14
15
|
| 'workspace'
|
|
15
16
|
| 'github'
|
|
16
17
|
| 'vscode',
|
|
@@ -80,6 +81,7 @@ async function templateDirectory(
|
|
|
80
81
|
| 'app-single-file'
|
|
81
82
|
| 'app-empty'
|
|
82
83
|
| 'app-graphql'
|
|
84
|
+
| 'module'
|
|
83
85
|
| 'workspace'
|
|
84
86
|
| 'github'
|
|
85
87
|
| 'vscode',
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {describe, it, expect} from '@quilted/quilt/testing';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {renderWithAppContext} from '~/tests/render';
|
|
4
4
|
|
|
5
5
|
import Start from './Start';
|
|
6
6
|
|
|
7
7
|
describe('<Start />', () => {
|
|
8
8
|
it('includes a welcome message', async () => {
|
|
9
|
-
const start = await
|
|
9
|
+
const start = await renderWithAppContext(<Start />);
|
|
10
10
|
expect(start).toContainReactText('Hello world!');
|
|
11
11
|
});
|
|
12
12
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {Viewport, SearchRobots} from '@quilted/quilt/html';
|
|
2
2
|
import {describe, it, expect} from '@quilted/quilt/testing';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {renderWithAppContext} from '~/tests/render';
|
|
5
5
|
|
|
6
6
|
import {Head} from './Head';
|
|
7
7
|
|
|
8
8
|
describe('<Head />', () => {
|
|
9
9
|
it('includes a responsive viewport tag', async () => {
|
|
10
|
-
const head = await
|
|
10
|
+
const head = await renderWithAppContext(<Head />);
|
|
11
11
|
|
|
12
12
|
expect(head).toContainReactComponent(Viewport, {
|
|
13
13
|
cover: true,
|
|
@@ -15,7 +15,7 @@ describe('<Head />', () => {
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
it('prevents search robots from indexing the application', async () => {
|
|
18
|
-
const head = await
|
|
18
|
+
const head = await renderWithAppContext(<Head />);
|
|
19
19
|
|
|
20
20
|
expect(head).toContainReactComponent(SearchRobots, {
|
|
21
21
|
index: false,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {CacheControl, ContentSecurityPolicy} from '@quilted/quilt/http';
|
|
2
2
|
import {describe, it, expect} from '@quilted/quilt/testing';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {renderWithAppContext} from '~/tests/render';
|
|
5
5
|
|
|
6
6
|
import {Http} from './Http';
|
|
7
7
|
|
|
8
8
|
describe('<Http />', () => {
|
|
9
9
|
it('does not cache the response', async () => {
|
|
10
|
-
const http = await
|
|
10
|
+
const http = await renderWithAppContext(<Http />);
|
|
11
11
|
|
|
12
12
|
expect(http).toContainReactComponent(CacheControl, {
|
|
13
13
|
cache: false,
|
|
@@ -15,7 +15,7 @@ describe('<Http />', () => {
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
it('adds a content security policy with a strict default policy', async () => {
|
|
18
|
-
const http = await
|
|
18
|
+
const http = await renderWithAppContext(<Http />);
|
|
19
19
|
|
|
20
20
|
expect(http).toContainReactComponent(ContentSecurityPolicy, {
|
|
21
21
|
defaultSources: ["'self'"],
|
|
@@ -2,7 +2,7 @@ import '@quilted/quilt/matchers';
|
|
|
2
2
|
|
|
3
3
|
import {type PropsWithChildren} from '@quilted/quilt';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
createRender,
|
|
6
6
|
QuiltAppTesting,
|
|
7
7
|
createTestRouter,
|
|
8
8
|
} from '@quilted/quilt/testing';
|
|
@@ -11,7 +11,7 @@ type Router = ReturnType<typeof createTestRouter>;
|
|
|
11
11
|
|
|
12
12
|
export {createTestRouter};
|
|
13
13
|
|
|
14
|
-
export interface
|
|
14
|
+
export interface RenderOptions {
|
|
15
15
|
/**
|
|
16
16
|
* A custom router to use for this component test. You can use a
|
|
17
17
|
* custom router to simulate a particular URL, and you can spy on
|
|
@@ -26,23 +26,23 @@ export interface MountOptions {
|
|
|
26
26
|
locale?: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export interface
|
|
29
|
+
export interface RenderContext {
|
|
30
30
|
/**
|
|
31
31
|
* The router used for this component test.
|
|
32
32
|
*/
|
|
33
33
|
router: Router;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export interface
|
|
36
|
+
export interface RenderActions extends Record<string, never> {}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
39
|
+
* Renders a component with test-friendly versions of all global
|
|
40
40
|
* context available to the application.
|
|
41
41
|
*/
|
|
42
|
-
export const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
export const renderWithAppContext = createRender<
|
|
43
|
+
RenderOptions,
|
|
44
|
+
RenderContext,
|
|
45
|
+
RenderActions,
|
|
46
46
|
true
|
|
47
47
|
>({
|
|
48
48
|
// Create context that can be used by the `render` function, and referenced by test
|
|
@@ -59,10 +59,10 @@ export const mountWithAppContext = createMount<
|
|
|
59
59
|
</QuiltAppTesting>
|
|
60
60
|
);
|
|
61
61
|
},
|
|
62
|
-
async
|
|
62
|
+
async afterRender() {
|
|
63
63
|
// If your components need to resolve data before they can render, you can
|
|
64
64
|
// use this hook to wait for that data to be ready. This will cause the
|
|
65
|
-
// `
|
|
65
|
+
// `render` function to return a promise, so that the component is only usable
|
|
66
66
|
// once the data is ready.
|
|
67
67
|
},
|
|
68
68
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {describe, it, expect} from '@quilted/quilt/testing';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
renderWithAppContext,
|
|
5
5
|
createGraphQLController,
|
|
6
6
|
fillGraphQL,
|
|
7
|
-
} from '~/tests/
|
|
7
|
+
} from '~/tests/render';
|
|
8
8
|
|
|
9
9
|
import Start from './Start';
|
|
10
10
|
import startQuery from './StartQuery.graphql';
|
|
@@ -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 renderWithAppContext(<Start />, {graphql});
|
|
18
18
|
|
|
19
19
|
expect(graphql).toHavePerformedGraphQLOperation(startQuery);
|
|
20
20
|
expect(start).toContainReactText(`Hello ${name}!`);
|