@tanstack/cli 0.65.0 → 0.67.0
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/cli.js +203 -2
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
2
|
+
import { relative, resolve } from 'node:path';
|
|
3
3
|
import { Command, InvalidArgumentError, Option } from 'commander';
|
|
4
4
|
import { cancel, confirm, intro, isCancel, log } from '@clack/prompts';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import semver from 'semver';
|
|
7
|
-
import { SUPPORTED_PACKAGE_MANAGERS, addToApp, compileAddOn, compileStarter, createApp, devAddOn, getAllAddOns, getFrameworkByName, getFrameworks, initAddOn, initStarter, } from '@tanstack/create';
|
|
7
|
+
import { SUPPORTED_PACKAGE_MANAGERS, addToApp, compileAddOn, compileStarter, createApp, devAddOn, getAllAddOns, getFrameworkByName, getFrameworks, initAddOn, initStarter, isDemoFilePath, } from '@tanstack/create';
|
|
8
8
|
import { LIBRARY_GROUPS, fetchDocContent, fetchLibraries, fetchPartners, searchTanStackDocs, } from './discovery.js';
|
|
9
9
|
import { getTelemetryStatus, setTelemetryEnabled, } from './telemetry-config.js';
|
|
10
10
|
import { createTelemetryClient } from './telemetry.js';
|
|
@@ -175,6 +175,144 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
175
175
|
process.exit(0);
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
|
+
async function confirmCreateOptions(finalOptions) {
|
|
179
|
+
const lines = [];
|
|
180
|
+
lines.push(` Project: ${finalOptions.projectName}`);
|
|
181
|
+
lines.push(` Location: ${finalOptions.targetDir}`);
|
|
182
|
+
lines.push(` Framework: ${finalOptions.framework.name}`);
|
|
183
|
+
lines.push(` Mode: ${finalOptions.mode}`);
|
|
184
|
+
lines.push(` Package manager: ${finalOptions.packageManager}`);
|
|
185
|
+
if (finalOptions.starter) {
|
|
186
|
+
lines.push(` Template: ${finalOptions.starter.name}`);
|
|
187
|
+
}
|
|
188
|
+
const auth = [];
|
|
189
|
+
const database = [];
|
|
190
|
+
const orm = [];
|
|
191
|
+
const deploy = [];
|
|
192
|
+
const otherAddOns = [];
|
|
193
|
+
for (const addOn of finalOptions.chosenAddOns) {
|
|
194
|
+
switch (addOn.category) {
|
|
195
|
+
case 'auth':
|
|
196
|
+
auth.push(addOn.name);
|
|
197
|
+
break;
|
|
198
|
+
case 'database':
|
|
199
|
+
database.push(addOn.name);
|
|
200
|
+
break;
|
|
201
|
+
case 'orm':
|
|
202
|
+
orm.push(addOn.name);
|
|
203
|
+
break;
|
|
204
|
+
case 'deploy':
|
|
205
|
+
deploy.push(addOn.name);
|
|
206
|
+
break;
|
|
207
|
+
default:
|
|
208
|
+
otherAddOns.push(addOn.name);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (auth.length +
|
|
212
|
+
database.length +
|
|
213
|
+
orm.length +
|
|
214
|
+
deploy.length +
|
|
215
|
+
otherAddOns.length >
|
|
216
|
+
0) {
|
|
217
|
+
lines.push('');
|
|
218
|
+
}
|
|
219
|
+
if (auth.length > 0) {
|
|
220
|
+
lines.push(` Auth: ${auth.join(', ')}`);
|
|
221
|
+
}
|
|
222
|
+
if (database.length > 0) {
|
|
223
|
+
lines.push(` Database: ${database.join(', ')}`);
|
|
224
|
+
}
|
|
225
|
+
if (orm.length > 0) {
|
|
226
|
+
lines.push(` ORM: ${orm.join(', ')}`);
|
|
227
|
+
}
|
|
228
|
+
if (deploy.length > 0) {
|
|
229
|
+
lines.push(` Deploy: ${deploy.join(', ')}`);
|
|
230
|
+
}
|
|
231
|
+
if (otherAddOns.length > 0) {
|
|
232
|
+
lines.push(` Other add-ons: ${otherAddOns.join(', ')}`);
|
|
233
|
+
}
|
|
234
|
+
lines.push('');
|
|
235
|
+
lines.push(` Initialize git: ${finalOptions.git ? 'yes' : 'no'}`);
|
|
236
|
+
lines.push(` Install deps: ${finalOptions.install === false ? 'no' : 'yes'}`);
|
|
237
|
+
lines.push(` Agent skills: ${finalOptions.intent ? 'yes' : 'no'}`);
|
|
238
|
+
log.info(`About to create:\n\n${lines.join('\n')}`);
|
|
239
|
+
const conflicts = findExclusiveConflicts(finalOptions.chosenAddOns);
|
|
240
|
+
if (conflicts.length > 0) {
|
|
241
|
+
log.warn(`Conflicting selections detected:\n${conflicts
|
|
242
|
+
.map((c) => ` • ${c.category}: ${c.names.join(', ')}`)
|
|
243
|
+
.join('\n')}`);
|
|
244
|
+
}
|
|
245
|
+
const shouldContinue = await confirm({
|
|
246
|
+
message: 'Continue with these settings?',
|
|
247
|
+
initialValue: true,
|
|
248
|
+
});
|
|
249
|
+
if (isCancel(shouldContinue) || !shouldContinue) {
|
|
250
|
+
cancel('Operation cancelled.');
|
|
251
|
+
process.exit(0);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const CLEAN_DEMOS_SKIP_DIRS = new Set([
|
|
255
|
+
'node_modules',
|
|
256
|
+
'.git',
|
|
257
|
+
'dist',
|
|
258
|
+
'.output',
|
|
259
|
+
'.tanstack',
|
|
260
|
+
'.nitro',
|
|
261
|
+
'.wrangler',
|
|
262
|
+
]);
|
|
263
|
+
function findDemoFiles(root) {
|
|
264
|
+
const results = [];
|
|
265
|
+
function walk(dir) {
|
|
266
|
+
let entries;
|
|
267
|
+
try {
|
|
268
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
for (const entry of entries) {
|
|
274
|
+
const full = resolve(dir, entry.name);
|
|
275
|
+
if (entry.isDirectory()) {
|
|
276
|
+
if (CLEAN_DEMOS_SKIP_DIRS.has(entry.name))
|
|
277
|
+
continue;
|
|
278
|
+
walk(full);
|
|
279
|
+
}
|
|
280
|
+
else if (entry.isFile() && isDemoFilePath(full)) {
|
|
281
|
+
results.push(full);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
walk(root);
|
|
286
|
+
return results.sort();
|
|
287
|
+
}
|
|
288
|
+
function pruneEmptyDemoDirs(root) {
|
|
289
|
+
const candidates = ['src/routes/demo', 'src/routes/example'];
|
|
290
|
+
for (const rel of candidates) {
|
|
291
|
+
const dir = resolve(root, rel);
|
|
292
|
+
if (!fs.existsSync(dir))
|
|
293
|
+
continue;
|
|
294
|
+
try {
|
|
295
|
+
if (fs.readdirSync(dir).length === 0) {
|
|
296
|
+
fs.rmdirSync(dir);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// ignore
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function findExclusiveConflicts(addOns) {
|
|
305
|
+
const buckets = {};
|
|
306
|
+
for (const addOn of addOns) {
|
|
307
|
+
for (const exclusive of addOn.exclusive || []) {
|
|
308
|
+
buckets[exclusive] ?? (buckets[exclusive] = []);
|
|
309
|
+
buckets[exclusive].push(addOn.name);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return Object.entries(buckets)
|
|
313
|
+
.filter(([_, names]) => names.length > 1)
|
|
314
|
+
.map(([category, names]) => ({ category, names }));
|
|
315
|
+
}
|
|
178
316
|
const availableFrameworks = getFrameworks().map((f) => f.name);
|
|
179
317
|
function resolveBuiltInDevWatchPath(frameworkId) {
|
|
180
318
|
const candidates = [
|
|
@@ -489,6 +627,7 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
489
627
|
if (!wantsInteractiveMode && cliOptions.addOns === true) {
|
|
490
628
|
throw new Error('When running non-interactively, pass explicit add-ons via --add-ons <ids>.');
|
|
491
629
|
}
|
|
630
|
+
let cameFromPrompts = false;
|
|
492
631
|
if (finalOptions) {
|
|
493
632
|
intro(`Creating a new ${appName} app in ${projectName}...`);
|
|
494
633
|
}
|
|
@@ -505,6 +644,7 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
505
644
|
? getFrameworkByName(defaultFramework)?.id
|
|
506
645
|
: undefined,
|
|
507
646
|
});
|
|
647
|
+
cameFromPrompts = true;
|
|
508
648
|
}
|
|
509
649
|
if (!finalOptions) {
|
|
510
650
|
throw new Error('No options were provided');
|
|
@@ -524,6 +664,9 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
524
664
|
else {
|
|
525
665
|
finalOptions.targetDir = resolve(process.cwd(), finalOptions.projectName);
|
|
526
666
|
}
|
|
667
|
+
if (cameFromPrompts) {
|
|
668
|
+
await confirmCreateOptions(finalOptions);
|
|
669
|
+
}
|
|
527
670
|
await confirmTargetDirectorySafety(finalOptions.targetDir, options.force);
|
|
528
671
|
await createApp(environment, finalOptions);
|
|
529
672
|
});
|
|
@@ -975,6 +1118,64 @@ Remove your node_modules directory and package lock file and re-install.`);
|
|
|
975
1118
|
process.exit(1);
|
|
976
1119
|
}
|
|
977
1120
|
});
|
|
1121
|
+
// === CLEAN-DEMOS SUBCOMMAND ===
|
|
1122
|
+
program
|
|
1123
|
+
.command('clean-demos')
|
|
1124
|
+
.description('Remove demo/example files from a scaffolded TanStack project')
|
|
1125
|
+
.argument('[target-dir]', 'project directory (default: current directory)', '.')
|
|
1126
|
+
.addOption(new Option(AGENT_FLAG, 'internal: invocation originated from an agent').hideHelp())
|
|
1127
|
+
.option('-y, --yes', 'skip confirmation prompt', false)
|
|
1128
|
+
.option('--dry-run', 'list files without deleting', false)
|
|
1129
|
+
.action(async (targetDir, cmdOptions) => {
|
|
1130
|
+
try {
|
|
1131
|
+
await runWithTelemetry('clean-demos', {
|
|
1132
|
+
properties: {
|
|
1133
|
+
yes: cmdOptions.yes,
|
|
1134
|
+
dry_run: cmdOptions.dryRun,
|
|
1135
|
+
},
|
|
1136
|
+
}, async (telemetry) => {
|
|
1137
|
+
const root = resolve(targetDir);
|
|
1138
|
+
if (!fs.existsSync(root)) {
|
|
1139
|
+
throw new Error(`Directory not found: ${root}`);
|
|
1140
|
+
}
|
|
1141
|
+
if (!fs.existsSync(resolve(root, '.cta.json'))) {
|
|
1142
|
+
log.warn(`No .cta.json in ${root} — this may not be a TanStack scaffold. Continuing anyway.`);
|
|
1143
|
+
}
|
|
1144
|
+
const demoFiles = findDemoFiles(root);
|
|
1145
|
+
telemetry.mergeProperties({ result_count: demoFiles.length });
|
|
1146
|
+
if (demoFiles.length === 0) {
|
|
1147
|
+
log.info('No demo or example files found.');
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
log.info(`Found ${demoFiles.length} demo/example file(s):\n${demoFiles
|
|
1151
|
+
.map((f) => ` • ${relative(root, f)}`)
|
|
1152
|
+
.join('\n')}`);
|
|
1153
|
+
if (cmdOptions.dryRun) {
|
|
1154
|
+
log.info('(dry run — nothing deleted)');
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
if (!cmdOptions.yes) {
|
|
1158
|
+
const ok = await confirm({
|
|
1159
|
+
message: 'Delete these files?',
|
|
1160
|
+
initialValue: false,
|
|
1161
|
+
});
|
|
1162
|
+
if (isCancel(ok) || !ok) {
|
|
1163
|
+
cancel('Operation cancelled.');
|
|
1164
|
+
process.exit(0);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
for (const file of demoFiles) {
|
|
1168
|
+
fs.rmSync(file, { force: true });
|
|
1169
|
+
}
|
|
1170
|
+
pruneEmptyDemoDirs(root);
|
|
1171
|
+
log.info(`Deleted ${demoFiles.length} file(s). Run your dev server to regenerate routeTree.gen.ts.`);
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
catch (error) {
|
|
1175
|
+
log.error(formatErrorMessage(error));
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
978
1179
|
const telemetryCommand = program.command('telemetry');
|
|
979
1180
|
telemetryCommand
|
|
980
1181
|
.command('status')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.0",
|
|
4
4
|
"description": "TanStack CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"tempy": "^3.1.0",
|
|
45
45
|
"validate-npm-package-name": "^7.0.0",
|
|
46
46
|
"zod": "^3.24.2",
|
|
47
|
-
"@tanstack/create": "0.
|
|
47
|
+
"@tanstack/create": "0.66.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@playwright/test": "^1.58.2",
|