@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.
Files changed (2) hide show
  1. package/dist/cli.js +203 -2
  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.65.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.64.0"
47
+ "@tanstack/create": "0.66.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@playwright/test": "^1.58.2",