create-tsrouter-app 0.3.0-alpha.7 → 0.3.0-alpha.9
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/dist/add-ons.js +9 -0
- package/dist/cli.js +32 -17
- package/dist/create-app.js +88 -24
- package/dist/options.js +62 -48
- package/package.json +1 -1
- package/src/add-ons.ts +15 -17
- package/src/cli.ts +38 -18
- package/src/create-app.ts +149 -48
- package/src/options.ts +77 -53
- package/src/types.ts +2 -1
- package/templates/react/add-on/clerk/assets/src/integrations/clerk/header-user.tsx +19 -0
- package/templates/react/add-on/clerk/assets/src/integrations/clerk/provider.tsx +18 -0
- package/templates/react/add-on/clerk/info.json +0 -16
- package/templates/react/add-on/convex/assets/src/integrations/convex/provider.tsx +20 -0
- package/templates/react/add-on/convex/info.json +0 -15
- package/templates/react/add-on/form/assets/src/routes/{demo.form.tsx → demo.form.tsx.ejs} +15 -3
- package/templates/react/add-on/form/info.json +1 -1
- package/templates/react/add-on/sentry/info.json +1 -1
- package/templates/react/add-on/store/assets/src/routes/{demo.store.page1.tsx → demo.store.page1.tsx.ejs} +14 -6
- package/templates/react/add-on/store/assets/src/routes/{demo.store.page2.tsx → demo.store.page2.tsx.ejs} +15 -6
- package/templates/react/add-on/store/info.json +1 -1
- package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/layout.tsx +5 -0
- package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +9 -0
- package/templates/react/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx.ejs +38 -0
- package/templates/react/add-on/tanstack-query/info.json +1 -19
- package/templates/react/{file-router → base}/src/components/Header.tsx.ejs +8 -10
- package/templates/react/code-router/src/main.tsx.ejs +17 -1
- package/templates/react/file-router/src/routes/__root.tsx.ejs +16 -25
- package/templates/solid/add-on/form/assets/src/routes/{demo.form.tsx → demo.form.tsx.ejs} +14 -2
- package/templates/solid/add-on/form/info.json +1 -1
- package/templates/solid/add-on/store/assets/src/routes/{demo.store.page1.tsx → demo.store.page1.tsx.ejs} +15 -4
- package/templates/solid/add-on/store/assets/src/routes/{demo.store.page2.tsx → demo.store.page2.tsx.ejs} +14 -5
- package/templates/solid/add-on/store/info.json +1 -1
- package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/header-user.tsx +5 -0
- package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +15 -0
- package/templates/solid/add-on/tanstack-query/info.json +0 -18
- package/templates/solid/{file-router → base}/src/components/Header.tsx.ejs +7 -5
- package/templates/solid/code-router/src/main.tsx.ejs +18 -2
- package/templates/solid/file-router/src/routes/__root.tsx.ejs +13 -19
- package/templates/react/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx +0 -30
package/dist/add-ons.js
CHANGED
|
@@ -2,6 +2,8 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { DEFAULT_FRAMEWORK } from './constants.js';
|
|
5
7
|
function isDirectory(path) {
|
|
6
8
|
return statSync(path).isDirectory();
|
|
7
9
|
}
|
|
@@ -58,3 +60,10 @@ export async function finalizeAddOns(framework, template, chosenAddOnIDs) {
|
|
|
58
60
|
}
|
|
59
61
|
return [...finalAddOnIDs].map((id) => addOns.find((a) => a.id === id));
|
|
60
62
|
}
|
|
63
|
+
export async function listAddOns(options) {
|
|
64
|
+
const mode = options.template === 'file-router' ? 'file-router' : 'code-router';
|
|
65
|
+
const addOns = await getAllAddOns(options.framework || DEFAULT_FRAMEWORK, mode);
|
|
66
|
+
for (const addOn of addOns) {
|
|
67
|
+
console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { intro, log } from '@clack/prompts';
|
|
|
3
3
|
import { createApp } from './create-app.js';
|
|
4
4
|
import { normalizeOptions, promptForOptions } from './options.js';
|
|
5
5
|
import { SUPPORTED_PACKAGE_MANAGERS } from './package-manager.js';
|
|
6
|
+
import { listAddOns } from './add-ons.js';
|
|
6
7
|
import { DEFAULT_FRAMEWORK, SUPPORTED_FRAMEWORKS } from './constants.js';
|
|
7
8
|
export function cli() {
|
|
8
9
|
const program = new Command();
|
|
@@ -32,26 +33,40 @@ export function cli() {
|
|
|
32
33
|
return value;
|
|
33
34
|
})
|
|
34
35
|
.option('--tailwind', 'add Tailwind CSS', false)
|
|
35
|
-
.option('--add-ons', 'pick from a list of available add-ons',
|
|
36
|
+
.option('--add-ons [...add-ons]', 'pick from a list of available add-ons (comma separated list)', (value) => {
|
|
37
|
+
let addOns = !!value;
|
|
38
|
+
if (typeof value === 'string') {
|
|
39
|
+
addOns = value.split(',').map((addon) => addon.trim());
|
|
40
|
+
}
|
|
41
|
+
return addOns;
|
|
42
|
+
})
|
|
43
|
+
.option('--list-add-ons', 'list all available add-ons', false)
|
|
36
44
|
.action(async (projectName, options) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
if (options.listAddOns) {
|
|
46
|
+
await listAddOns(options);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
try {
|
|
50
|
+
const cliOptions = {
|
|
51
|
+
projectName,
|
|
52
|
+
...options,
|
|
53
|
+
};
|
|
54
|
+
let finalOptions = await normalizeOptions(cliOptions);
|
|
55
|
+
if (finalOptions) {
|
|
56
|
+
intro(`Creating a new TanStack app in ${projectName}...`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
intro("Let's configure your TanStack application");
|
|
60
|
+
finalOptions = await promptForOptions(cliOptions);
|
|
61
|
+
}
|
|
62
|
+
await createApp(finalOptions);
|
|
45
63
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
catch (error) {
|
|
65
|
+
log.error(error instanceof Error
|
|
66
|
+
? error.message
|
|
67
|
+
: 'An unknown error occurred');
|
|
68
|
+
process.exit(1);
|
|
49
69
|
}
|
|
50
|
-
await createApp(finalOptions);
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
log.error(error instanceof Error ? error.message : 'An unknown error occurred');
|
|
54
|
-
process.exit(1);
|
|
55
70
|
}
|
|
56
71
|
});
|
|
57
72
|
program.parse();
|
package/dist/create-app.js
CHANGED
|
@@ -25,8 +25,14 @@ function createCopyFiles(targetDir) {
|
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
function jsSafeName(name) {
|
|
29
|
+
return name
|
|
30
|
+
.split(/[^a-zA-Z0-9]/)
|
|
31
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
32
|
+
.join('');
|
|
33
|
+
}
|
|
28
34
|
function createTemplateFile(projectName, options, targetDir) {
|
|
29
|
-
return async function templateFile(templateDir, file, targetFileName) {
|
|
35
|
+
return async function templateFile(templateDir, file, targetFileName, extraTemplateValues) {
|
|
30
36
|
const templateValues = {
|
|
31
37
|
packageManager: options.packageManager,
|
|
32
38
|
projectName: projectName,
|
|
@@ -41,9 +47,18 @@ function createTemplateFile(projectName, options, targetDir) {
|
|
|
41
47
|
return acc;
|
|
42
48
|
}, {}),
|
|
43
49
|
addOns: options.chosenAddOns,
|
|
50
|
+
...extraTemplateValues,
|
|
44
51
|
};
|
|
45
52
|
const template = await readFile(resolve(templateDir, file), 'utf-8');
|
|
46
|
-
let content =
|
|
53
|
+
let content = '';
|
|
54
|
+
try {
|
|
55
|
+
content = render(template, templateValues);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error(chalk.red(`EJS error in file ${file}`));
|
|
59
|
+
console.error(error);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
47
62
|
const target = targetFileName ?? file.replace('.ejs', '');
|
|
48
63
|
if (target.endsWith('.ts') || target.endsWith('.tsx')) {
|
|
49
64
|
content = await format(content, {
|
|
@@ -163,7 +178,6 @@ export async function createApp(options) {
|
|
|
163
178
|
const copyFiles = createCopyFiles(targetDir);
|
|
164
179
|
const templateFile = createTemplateFile(options.projectName, options, targetDir);
|
|
165
180
|
const isAddOnEnabled = (id) => options.chosenAddOns.find((a) => a.id === id);
|
|
166
|
-
log.info(`Creating a new TanStack app in '${basename(targetDir)}'...`);
|
|
167
181
|
// Make the root directory
|
|
168
182
|
await mkdir(targetDir, { recursive: true });
|
|
169
183
|
// Setup the .vscode directory
|
|
@@ -195,27 +209,6 @@ export async function createApp(options) {
|
|
|
195
209
|
await templateFile(templateDirBase, './vite.config.js.ejs');
|
|
196
210
|
await templateFile(templateDirBase, './src/styles.css.ejs');
|
|
197
211
|
copyFiles(templateDirBase, ['./src/logo.svg']);
|
|
198
|
-
// Setup the app component. There are four variations, typescript/javascript and tailwind/non-tailwind.
|
|
199
|
-
if (options.mode === FILE_ROUTER) {
|
|
200
|
-
await templateFile(templateDirRouter, './src/components/Header.tsx.ejs', './src/components/Header.tsx');
|
|
201
|
-
await templateFile(templateDirRouter, './src/routes/__root.tsx.ejs', './src/routes/__root.tsx');
|
|
202
|
-
await templateFile(templateDirBase, './src/App.tsx.ejs', './src/routes/index.tsx');
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
await templateFile(templateDirBase, './src/App.tsx.ejs', options.typescript ? undefined : './src/App.jsx');
|
|
206
|
-
if (options.framework === 'react') {
|
|
207
|
-
await templateFile(templateDirBase, './src/App.test.tsx.ejs', options.typescript ? undefined : './src/App.test.jsx');
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
// Create the main entry point
|
|
211
|
-
if (!isAddOnEnabled('start')) {
|
|
212
|
-
if (options.typescript) {
|
|
213
|
-
await templateFile(templateDirRouter, './src/main.tsx.ejs');
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
await templateFile(templateDirRouter, './src/main.tsx.ejs', './src/main.jsx');
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
212
|
// Setup the main, reportWebVitals and index.html files
|
|
220
213
|
if (!isAddOnEnabled('start') && options.framework === 'react') {
|
|
221
214
|
if (options.typescript) {
|
|
@@ -268,6 +261,77 @@ export async function createApp(options) {
|
|
|
268
261
|
s.stop(`Installed shadcn components`);
|
|
269
262
|
}
|
|
270
263
|
}
|
|
264
|
+
const integrations = [];
|
|
265
|
+
if (existsSync(resolve(targetDir, 'src/integrations'))) {
|
|
266
|
+
for (const integration of readdirSync(resolve(targetDir, 'src/integrations'))) {
|
|
267
|
+
const integrationName = jsSafeName(integration);
|
|
268
|
+
if (existsSync(resolve(targetDir, 'src/integrations', integration, 'layout.tsx'))) {
|
|
269
|
+
integrations.push({
|
|
270
|
+
type: 'layout',
|
|
271
|
+
name: `${integrationName}Layout`,
|
|
272
|
+
path: `integrations/${integration}/layout`,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (existsSync(resolve(targetDir, 'src/integrations', integration, 'provider.tsx'))) {
|
|
276
|
+
integrations.push({
|
|
277
|
+
type: 'provider',
|
|
278
|
+
name: `${integrationName}Provider`,
|
|
279
|
+
path: `integrations/${integration}/provider`,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
if (existsSync(resolve(targetDir, 'src/integrations', integration, 'header-user.tsx'))) {
|
|
283
|
+
integrations.push({
|
|
284
|
+
type: 'header-user',
|
|
285
|
+
name: `${integrationName}Header`,
|
|
286
|
+
path: `integrations/${integration}/header-user`,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const routes = [];
|
|
292
|
+
if (existsSync(resolve(targetDir, 'src/routes'))) {
|
|
293
|
+
for (const file of readdirSync(resolve(targetDir, 'src/routes'))) {
|
|
294
|
+
const name = file.replace(/\.tsx?|\.jsx?/, '');
|
|
295
|
+
const safeRouteName = jsSafeName(name);
|
|
296
|
+
routes.push({
|
|
297
|
+
path: `./routes/${name}`,
|
|
298
|
+
name: safeRouteName,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Create the main entry point
|
|
303
|
+
if (!isAddOnEnabled('start')) {
|
|
304
|
+
if (options.typescript) {
|
|
305
|
+
await templateFile(templateDirRouter, './src/main.tsx.ejs', './src/main.tsx', {
|
|
306
|
+
routes,
|
|
307
|
+
integrations,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
await templateFile(templateDirRouter, './src/main.tsx.ejs', './src/main.jsx', {
|
|
312
|
+
routes,
|
|
313
|
+
integrations,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Setup the app component. There are four variations, typescript/javascript and tailwind/non-tailwind.
|
|
318
|
+
if (options.mode === FILE_ROUTER) {
|
|
319
|
+
await templateFile(templateDirRouter, './src/routes/__root.tsx.ejs', './src/routes/__root.tsx', {
|
|
320
|
+
integrations,
|
|
321
|
+
});
|
|
322
|
+
await templateFile(templateDirBase, './src/App.tsx.ejs', './src/routes/index.tsx');
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
await templateFile(templateDirBase, './src/App.tsx.ejs', options.typescript ? undefined : './src/App.jsx');
|
|
326
|
+
if (options.framework === 'react') {
|
|
327
|
+
await templateFile(templateDirBase, './src/App.test.tsx.ejs', options.typescript ? undefined : './src/App.test.jsx');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (routes.length > 0) {
|
|
331
|
+
await templateFile(templateDirBase, './src/components/Header.tsx.ejs', './src/components/Header.tsx', {
|
|
332
|
+
integrations,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
271
335
|
const warnings = [];
|
|
272
336
|
for (const addOn of options.chosenAddOns) {
|
|
273
337
|
if (addOn.warning) {
|
package/dist/options.js
CHANGED
|
@@ -3,24 +3,31 @@ import { DEFAULT_PACKAGE_MANAGER, SUPPORTED_PACKAGE_MANAGERS, getPackageManager,
|
|
|
3
3
|
import { CODE_ROUTER, DEFAULT_FRAMEWORK, FILE_ROUTER } from './constants.js';
|
|
4
4
|
import { finalizeAddOns, getAllAddOns } from './add-ons.js';
|
|
5
5
|
// If all CLI options are provided, use them directly
|
|
6
|
-
export function normalizeOptions(cliOptions) {
|
|
6
|
+
export async function normalizeOptions(cliOptions) {
|
|
7
7
|
if (cliOptions.projectName) {
|
|
8
8
|
const typescript = cliOptions.template === 'typescript' ||
|
|
9
9
|
cliOptions.template === 'file-router' ||
|
|
10
10
|
cliOptions.framework === 'solid';
|
|
11
|
-
|
|
11
|
+
let tailwind = cliOptions.tailwind === undefined
|
|
12
12
|
? cliOptions.framework === 'solid'
|
|
13
13
|
: cliOptions.tailwind;
|
|
14
|
+
let addOns = false;
|
|
15
|
+
let chosenAddOns = [];
|
|
16
|
+
if (Array.isArray(cliOptions.addOns)) {
|
|
17
|
+
addOns = true;
|
|
18
|
+
chosenAddOns = await finalizeAddOns(cliOptions.framework || DEFAULT_FRAMEWORK, cliOptions.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER, cliOptions.addOns);
|
|
19
|
+
tailwind = true;
|
|
20
|
+
}
|
|
14
21
|
return {
|
|
15
22
|
framework: cliOptions.framework || 'react',
|
|
16
23
|
projectName: cliOptions.projectName,
|
|
17
24
|
typescript,
|
|
18
|
-
tailwind
|
|
25
|
+
tailwind,
|
|
19
26
|
packageManager: cliOptions.packageManager || DEFAULT_PACKAGE_MANAGER,
|
|
20
27
|
mode: cliOptions.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
|
|
21
28
|
git: !!cliOptions.git,
|
|
22
|
-
addOns
|
|
23
|
-
chosenAddOns
|
|
29
|
+
addOns,
|
|
30
|
+
chosenAddOns,
|
|
24
31
|
variableValues: {},
|
|
25
32
|
};
|
|
26
33
|
}
|
|
@@ -71,6 +78,9 @@ export async function promptForOptions(cliOptions) {
|
|
|
71
78
|
options.typescript = true;
|
|
72
79
|
options.tailwind = true;
|
|
73
80
|
}
|
|
81
|
+
if (cliOptions.addOns) {
|
|
82
|
+
options.typescript = true;
|
|
83
|
+
}
|
|
74
84
|
if (!cliOptions.projectName) {
|
|
75
85
|
const value = await text({
|
|
76
86
|
message: 'What would you like to name your project?',
|
|
@@ -172,51 +182,55 @@ export async function promptForOptions(cliOptions) {
|
|
|
172
182
|
else {
|
|
173
183
|
options.packageManager = cliOptions.packageManager;
|
|
174
184
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
let selectedAddOns = [];
|
|
179
|
-
if (options.typescript && cliOptions.addOns && addOns.length > 0) {
|
|
180
|
-
const value = await multiselect({
|
|
181
|
-
message: 'What add-ons would you like for your project:',
|
|
182
|
-
options: addOns.map((addOn) => ({
|
|
183
|
-
value: addOn.id,
|
|
184
|
-
label: addOn.name,
|
|
185
|
-
hint: addOn.description,
|
|
186
|
-
})),
|
|
187
|
-
required: false,
|
|
188
|
-
});
|
|
189
|
-
if (isCancel(value)) {
|
|
190
|
-
cancel('Operation cancelled.');
|
|
191
|
-
process.exit(0);
|
|
192
|
-
}
|
|
193
|
-
selectedAddOns = value;
|
|
194
|
-
}
|
|
195
|
-
// Select any examples
|
|
196
|
-
const examples = allAddOns.filter((addOn) => addOn.type === 'example');
|
|
197
|
-
let selectedExamples = [];
|
|
198
|
-
if (options.typescript && cliOptions.addOns && examples.length > 0) {
|
|
199
|
-
const value = await multiselect({
|
|
200
|
-
message: 'Would you like any examples?',
|
|
201
|
-
options: examples.map((addOn) => ({
|
|
202
|
-
value: addOn.id,
|
|
203
|
-
label: addOn.name,
|
|
204
|
-
hint: addOn.description,
|
|
205
|
-
})),
|
|
206
|
-
required: false,
|
|
207
|
-
});
|
|
208
|
-
if (isCancel(value)) {
|
|
209
|
-
cancel('Operation cancelled.');
|
|
210
|
-
process.exit(0);
|
|
211
|
-
}
|
|
212
|
-
selectedExamples = value;
|
|
213
|
-
}
|
|
214
|
-
if (selectedAddOns.length > 0 || selectedExamples.length > 0) {
|
|
215
|
-
options.chosenAddOns = await finalizeAddOns(options.framework, options.mode, [...selectedAddOns, ...selectedExamples]);
|
|
185
|
+
options.chosenAddOns = [];
|
|
186
|
+
if (Array.isArray(cliOptions.addOns)) {
|
|
187
|
+
options.chosenAddOns = await finalizeAddOns(options.framework, options.mode, cliOptions.addOns);
|
|
216
188
|
options.tailwind = true;
|
|
217
189
|
}
|
|
218
|
-
else {
|
|
219
|
-
|
|
190
|
+
else if (cliOptions.addOns) {
|
|
191
|
+
// Select any add-ons
|
|
192
|
+
const allAddOns = await getAllAddOns(options.framework, options.mode);
|
|
193
|
+
const addOns = allAddOns.filter((addOn) => addOn.type === 'add-on');
|
|
194
|
+
let selectedAddOns = [];
|
|
195
|
+
if (options.typescript && addOns.length > 0) {
|
|
196
|
+
const value = await multiselect({
|
|
197
|
+
message: 'What add-ons would you like for your project:',
|
|
198
|
+
options: addOns.map((addOn) => ({
|
|
199
|
+
value: addOn.id,
|
|
200
|
+
label: addOn.name,
|
|
201
|
+
hint: addOn.description,
|
|
202
|
+
})),
|
|
203
|
+
required: false,
|
|
204
|
+
});
|
|
205
|
+
if (isCancel(value)) {
|
|
206
|
+
cancel('Operation cancelled.');
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
selectedAddOns = value;
|
|
210
|
+
}
|
|
211
|
+
// Select any examples
|
|
212
|
+
const examples = allAddOns.filter((addOn) => addOn.type === 'example');
|
|
213
|
+
let selectedExamples = [];
|
|
214
|
+
if (options.typescript && examples.length > 0) {
|
|
215
|
+
const value = await multiselect({
|
|
216
|
+
message: 'Would you like any examples?',
|
|
217
|
+
options: examples.map((addOn) => ({
|
|
218
|
+
value: addOn.id,
|
|
219
|
+
label: addOn.name,
|
|
220
|
+
hint: addOn.description,
|
|
221
|
+
})),
|
|
222
|
+
required: false,
|
|
223
|
+
});
|
|
224
|
+
if (isCancel(value)) {
|
|
225
|
+
cancel('Operation cancelled.');
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
selectedExamples = value;
|
|
229
|
+
}
|
|
230
|
+
if (selectedAddOns.length > 0 || selectedExamples.length > 0) {
|
|
231
|
+
options.chosenAddOns = await finalizeAddOns(options.framework, options.mode, [...selectedAddOns, ...selectedExamples]);
|
|
232
|
+
options.tailwind = true;
|
|
233
|
+
}
|
|
220
234
|
}
|
|
221
235
|
// Collect variables
|
|
222
236
|
const variables = [];
|
package/package.json
CHANGED
package/src/add-ons.ts
CHANGED
|
@@ -2,8 +2,10 @@ import { readFile } from 'node:fs/promises'
|
|
|
2
2
|
import { existsSync, readdirSync, statSync } from 'node:fs'
|
|
3
3
|
import { resolve } from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import chalk from 'chalk'
|
|
5
6
|
|
|
6
|
-
import
|
|
7
|
+
import { DEFAULT_FRAMEWORK } from './constants.js'
|
|
8
|
+
import type { CliOptions, Framework } from './types.js'
|
|
7
9
|
|
|
8
10
|
type BooleanVariable = {
|
|
9
11
|
name: string
|
|
@@ -35,26 +37,10 @@ export type AddOn = {
|
|
|
35
37
|
description: string
|
|
36
38
|
link: string
|
|
37
39
|
templates: Array<string>
|
|
38
|
-
main?: Array<{
|
|
39
|
-
imports: Array<string>
|
|
40
|
-
initialize: Array<string>
|
|
41
|
-
providers: Array<{
|
|
42
|
-
open: string
|
|
43
|
-
close: string
|
|
44
|
-
}>
|
|
45
|
-
}>
|
|
46
|
-
layout?: {
|
|
47
|
-
imports: Array<string>
|
|
48
|
-
jsx: string
|
|
49
|
-
}
|
|
50
40
|
routes: Array<{
|
|
51
41
|
url: string
|
|
52
42
|
name: string
|
|
53
43
|
}>
|
|
54
|
-
userUi?: {
|
|
55
|
-
import: string
|
|
56
|
-
jsx: string
|
|
57
|
-
}
|
|
58
44
|
directory: string
|
|
59
45
|
packageAdditions: {
|
|
60
46
|
dependencies?: Record<string, string>
|
|
@@ -156,3 +142,15 @@ export async function finalizeAddOns(
|
|
|
156
142
|
|
|
157
143
|
return [...finalAddOnIDs].map((id) => addOns.find((a) => a.id === id)!)
|
|
158
144
|
}
|
|
145
|
+
|
|
146
|
+
export async function listAddOns(options: CliOptions) {
|
|
147
|
+
const mode =
|
|
148
|
+
options.template === 'file-router' ? 'file-router' : 'code-router'
|
|
149
|
+
const addOns = await getAllAddOns(
|
|
150
|
+
options.framework || DEFAULT_FRAMEWORK,
|
|
151
|
+
mode,
|
|
152
|
+
)
|
|
153
|
+
for (const addOn of addOns) {
|
|
154
|
+
console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`)
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Command, InvalidArgumentError } from 'commander'
|
|
2
2
|
import { intro, log } from '@clack/prompts'
|
|
3
|
+
import chalk from 'chalk'
|
|
3
4
|
|
|
4
5
|
import { createApp } from './create-app.js'
|
|
5
6
|
import { normalizeOptions, promptForOptions } from './options.js'
|
|
6
7
|
import { SUPPORTED_PACKAGE_MANAGERS } from './package-manager.js'
|
|
7
8
|
|
|
9
|
+
import { getAllAddOns, listAddOns } from './add-ons.js'
|
|
8
10
|
import { DEFAULT_FRAMEWORK, SUPPORTED_FRAMEWORKS } from './constants.js'
|
|
9
11
|
import type { PackageManager } from './package-manager.js'
|
|
10
12
|
import type { CliOptions, Framework } from './types.js'
|
|
@@ -63,26 +65,44 @@ export function cli() {
|
|
|
63
65
|
},
|
|
64
66
|
)
|
|
65
67
|
.option('--tailwind', 'add Tailwind CSS', false)
|
|
66
|
-
.option
|
|
68
|
+
.option<Array<string> | boolean>(
|
|
69
|
+
'--add-ons [...add-ons]',
|
|
70
|
+
'pick from a list of available add-ons (comma separated list)',
|
|
71
|
+
(value: string) => {
|
|
72
|
+
let addOns: Array<string> | boolean = !!value
|
|
73
|
+
if (typeof value === 'string') {
|
|
74
|
+
addOns = value.split(',').map((addon) => addon.trim())
|
|
75
|
+
}
|
|
76
|
+
return addOns
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
.option('--list-add-ons', 'list all available add-ons', false)
|
|
67
80
|
.action(async (projectName: string, options: CliOptions) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
if (options.listAddOns) {
|
|
82
|
+
await listAddOns(options)
|
|
83
|
+
} else {
|
|
84
|
+
try {
|
|
85
|
+
const cliOptions = {
|
|
86
|
+
projectName,
|
|
87
|
+
...options,
|
|
88
|
+
} as CliOptions
|
|
89
|
+
|
|
90
|
+
let finalOptions = await normalizeOptions(cliOptions)
|
|
91
|
+
if (finalOptions) {
|
|
92
|
+
intro(`Creating a new TanStack app in ${projectName}...`)
|
|
93
|
+
} else {
|
|
94
|
+
intro("Let's configure your TanStack application")
|
|
95
|
+
finalOptions = await promptForOptions(cliOptions)
|
|
96
|
+
}
|
|
97
|
+
await createApp(finalOptions)
|
|
98
|
+
} catch (error) {
|
|
99
|
+
log.error(
|
|
100
|
+
error instanceof Error
|
|
101
|
+
? error.message
|
|
102
|
+
: 'An unknown error occurred',
|
|
103
|
+
)
|
|
104
|
+
process.exit(1)
|
|
79
105
|
}
|
|
80
|
-
await createApp(finalOptions)
|
|
81
|
-
} catch (error) {
|
|
82
|
-
log.error(
|
|
83
|
-
error instanceof Error ? error.message : 'An unknown error occurred',
|
|
84
|
-
)
|
|
85
|
-
process.exit(1)
|
|
86
106
|
}
|
|
87
107
|
})
|
|
88
108
|
|