@tanstack/cli 0.64.6 → 0.66.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 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';
@@ -114,6 +114,7 @@ function getCreateTelemetryProperties(projectName, options) {
114
114
  framework: options.framework ? sanitizeId(options.framework) : undefined,
115
115
  git: options.git,
116
116
  install: options.install !== false,
117
+ intent: options.intent !== false,
117
118
  interactive: !!options.interactive,
118
119
  json: !!options.json,
119
120
  non_interactive: !!options.nonInteractive || !!options.yes,
@@ -139,6 +140,7 @@ function getResolvedCreateTelemetryProperties(finalOptions, cliOptions) {
139
140
  framework: sanitizeId(finalOptions.framework.id),
140
141
  git: finalOptions.git,
141
142
  install: finalOptions.install !== false,
143
+ intent: finalOptions.intent,
142
144
  package_manager: finalOptions.packageManager,
143
145
  router_only: !!cliOptions.routerOnly,
144
146
  toolchain: toolchain ? sanitizeId(toolchain.id) : undefined,
@@ -173,6 +175,144 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
173
175
  process.exit(0);
174
176
  }
175
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
+ }
176
316
  const availableFrameworks = getFrameworks().map((f) => f.name);
177
317
  function resolveBuiltInDevWatchPath(frameworkId) {
178
318
  const candidates = [
@@ -487,6 +627,7 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
487
627
  if (!wantsInteractiveMode && cliOptions.addOns === true) {
488
628
  throw new Error('When running non-interactively, pass explicit add-ons via --add-ons <ids>.');
489
629
  }
630
+ let cameFromPrompts = false;
490
631
  if (finalOptions) {
491
632
  intro(`Creating a new ${appName} app in ${projectName}...`);
492
633
  }
@@ -503,6 +644,7 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
503
644
  ? getFrameworkByName(defaultFramework)?.id
504
645
  : undefined,
505
646
  });
647
+ cameFromPrompts = true;
506
648
  }
507
649
  if (!finalOptions) {
508
650
  throw new Error('No options were provided');
@@ -522,6 +664,9 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
522
664
  else {
523
665
  finalOptions.targetDir = resolve(process.cwd(), finalOptions.projectName);
524
666
  }
667
+ if (cameFromPrompts) {
668
+ await confirmCreateOptions(finalOptions);
669
+ }
525
670
  await confirmTargetDirectorySafety(finalOptions.targetDir, options.force);
526
671
  await createApp(environment, finalOptions);
527
672
  });
@@ -598,6 +743,8 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
598
743
  .option('--json', 'output JSON for automation', false)
599
744
  .option('--git', 'create a git repository')
600
745
  .option('--no-git', 'do not create a git repository')
746
+ .option('--intent', 'set up TanStack Intent skill mappings for coding agents')
747
+ .option('--no-intent', 'skip TanStack Intent setup')
601
748
  .option('--target-dir <path>', 'the target directory for the application root')
602
749
  .option('--add-on-config <config>', 'JSON string with add-on configuration options')
603
750
  .option('-f, --force', 'force project creation even if the target directory is not empty', false);
@@ -971,6 +1118,64 @@ Remove your node_modules directory and package lock file and re-install.`);
971
1118
  process.exit(1);
972
1119
  }
973
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
+ });
974
1179
  const telemetryCommand = program.command('telemetry');
975
1180
  telemetryCommand
976
1181
  .command('status')
@@ -1018,6 +1223,8 @@ Remove your node_modules directory and package lock file and re-install.`);
1018
1223
  .addOption(new Option(AGENT_FLAG, 'internal: invocation originated from an agent').hideHelp())
1019
1224
  .argument('[add-on...]', 'Name of the add-ons (or add-ons separated by spaces or commas)')
1020
1225
  .option('--forced', 'Force the add-on to be added', false)
1226
+ .option('--intent', 'set up TanStack Intent skill mappings for coding agents')
1227
+ .option('--no-intent', 'skip TanStack Intent setup')
1021
1228
  .action(async (addOns, options) => {
1022
1229
  try {
1023
1230
  await runWithTelemetry('add', {
@@ -1044,6 +1251,7 @@ Remove your node_modules directory and package lock file and re-install.`);
1044
1251
  if (selectedAddOns.length) {
1045
1252
  await addToApp(environment, selectedAddOns, resolve(process.cwd()), {
1046
1253
  forced: options.forced,
1254
+ intent: options.intent,
1047
1255
  });
1048
1256
  }
1049
1257
  return;
@@ -1055,6 +1263,7 @@ Remove your node_modules directory and package lock file and re-install.`);
1055
1263
  });
1056
1264
  await addToApp(environment, parsedAddOns, resolve(process.cwd()), {
1057
1265
  forced: options.forced,
1266
+ intent: options.intent,
1058
1267
  });
1059
1268
  });
1060
1269
  }
@@ -381,6 +381,7 @@ export async function normalizeOptions(cliOptions, forcedAddOns, opts) {
381
381
  DEFAULT_PACKAGE_MANAGER,
382
382
  git: cliOptions.git ?? true,
383
383
  install: cliOptions.install,
384
+ intent: cliOptions.intent ?? true,
384
385
  chosenAddOns,
385
386
  addOnOptions: {
386
387
  ...populateAddOnOptionsDefaults(chosenAddOns),
package/dist/dev-watch.js CHANGED
@@ -189,6 +189,7 @@ export class DevWatchManager {
189
189
  targetDir: this.tempDir,
190
190
  git: false,
191
191
  install: packageMetadataChanged,
192
+ intent: false,
192
193
  };
193
194
  // Show package installation indicator if needed
194
195
  if (packageMetadataChanged) {
@@ -19,6 +19,7 @@ export interface CliOptions {
19
19
  devWatch?: string;
20
20
  runDev?: boolean;
21
21
  install?: boolean;
22
+ intent?: boolean;
22
23
  addOnConfig?: string;
23
24
  force?: boolean;
24
25
  routerOnly?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/cli",
3
- "version": "0.64.6",
3
+ "version": "0.66.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.63.9"
47
+ "@tanstack/create": "0.65.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@playwright/test": "^1.58.2",