@tanstack/cli 0.67.1 → 0.69.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
@@ -353,8 +353,7 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
353
353
  throw new Error('Failed to normalize options');
354
354
  }
355
355
  currentTelemetry?.mergeProperties(getResolvedCreateTelemetryProperties(normalizedOpts, options));
356
- normalizedOpts.targetDir =
357
- options.targetDir || resolve(process.cwd(), projectName);
356
+ normalizedOpts.targetDir = resolve(normalizedOpts.targetDir);
358
357
  // Create the initial app with minimal output for dev watch mode
359
358
  console.log(chalk.bold('\ndev-watch'));
360
359
  console.log(chalk.gray('├─') + ' ' + `creating initial ${appName} app...`);
@@ -629,7 +628,10 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
629
628
  }
630
629
  let cameFromPrompts = false;
631
630
  if (finalOptions) {
632
- intro(`Creating a new ${appName} app in ${projectName}...`);
631
+ const createLocation = resolve(finalOptions.targetDir) === resolve(process.cwd())
632
+ ? 'the current directory'
633
+ : finalOptions.projectName;
634
+ intro(`Creating a new ${appName} app in ${createLocation}...`);
633
635
  }
634
636
  else {
635
637
  if (!wantsInteractiveMode) {
@@ -652,14 +654,11 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
652
654
  telemetry.mergeProperties(getResolvedCreateTelemetryProperties(finalOptions, cliOptions));
653
655
  finalOptions.routerOnly =
654
656
  !!cliOptions.routerOnly;
655
- if (options.targetDir) {
656
- finalOptions.targetDir = options.targetDir;
657
- }
658
- else if (finalOptions.targetDir) {
657
+ if (finalOptions.targetDir) {
659
658
  // Keep the normalized target dir.
660
659
  }
661
- else if (projectName === '.') {
662
- finalOptions.targetDir = resolve(process.cwd());
660
+ else if (options.targetDir) {
661
+ finalOptions.targetDir = resolve(options.targetDir);
663
662
  }
664
663
  else {
665
664
  finalOptions.targetDir = resolve(process.cwd(), finalOptions.projectName);
@@ -1,7 +1,7 @@
1
1
  import { resolve } from 'node:path';
2
2
  import fs from 'node:fs';
3
3
  import { DEFAULT_PACKAGE_MANAGER, finalizeAddOns, getFrameworkById, getPackageManager, getRawRegistry, loadStarter, populateAddOnOptionsDefaults, } from '@tanstack/create';
4
- import { getCurrentDirectoryName, sanitizePackageName, validateProjectName, } from './utils.js';
4
+ import { resolveProjectLocation, validateProjectName, } from './utils.js';
5
5
  const SUPPORTED_LEGACY_TEMPLATES = new Set([
6
6
  'file-router',
7
7
  'typescript',
@@ -264,19 +264,15 @@ export function validateLegacyCreateFlags(cliOptions) {
264
264
  return { warnings };
265
265
  }
266
266
  export async function normalizeOptions(cliOptions, forcedAddOns, opts) {
267
- let projectName = (cliOptions.projectName ?? '').trim();
268
- let targetDir;
269
- // Handle "." as project name - use current directory
270
- if (projectName === '.') {
271
- projectName = sanitizePackageName(getCurrentDirectoryName());
272
- targetDir = resolve(process.cwd());
273
- }
274
- else {
275
- targetDir = resolve(process.cwd(), projectName);
276
- }
277
- if (!projectName && !opts?.disableNameCheck) {
267
+ const projectLocation = resolveProjectLocation({
268
+ projectName: cliOptions.projectName,
269
+ targetDir: cliOptions.targetDir,
270
+ });
271
+ if (!projectLocation && !opts?.disableNameCheck) {
278
272
  return undefined;
279
273
  }
274
+ const projectName = projectLocation?.projectName ?? '';
275
+ const targetDir = projectLocation?.targetDir ?? resolve(process.cwd());
280
276
  if (projectName) {
281
277
  const { valid, error } = validateProjectName(projectName);
282
278
  if (!valid) {
package/dist/options.js CHANGED
@@ -2,7 +2,7 @@ import { intro } from '@clack/prompts';
2
2
  import { finalizeAddOns, getFrameworkById, getFrameworks, getPackageManager, loadStarter, populateAddOnOptionsDefaults, readConfigFile, } from '@tanstack/create';
3
3
  import { getProjectName, promptForAddOnOptions, promptForEnvVars, selectAddOns, selectDeployment, selectExamples, selectFramework, selectGit, selectInstall, selectPackageManager, selectTemplate, selectToolchain, } from './ui-prompts.js';
4
4
  import { listTemplateChoices, resolveStarterSpecifier, } from './command-line.js';
5
- import { getCurrentDirectoryName, sanitizePackageName, validateProjectName, } from './utils.js';
5
+ import { resolveProjectLocation, validateProjectName, } from './utils.js';
6
6
  export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], forcedDeployment, showDeploymentOptions = true, defaultFrameworkId, }) {
7
7
  const options = {};
8
8
  if (cliOptions.framework) {
@@ -17,23 +17,20 @@ export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], fo
17
17
  options.framework = await selectFramework(availableFrameworks, defaultFrameworkId);
18
18
  }
19
19
  }
20
- // Validate project name
21
- if (cliOptions.projectName) {
22
- // Handle "." as project name - use sanitized current directory name
23
- if (cliOptions.projectName === '.') {
24
- options.projectName = sanitizePackageName(getCurrentDirectoryName());
25
- }
26
- else {
27
- options.projectName = cliOptions.projectName;
28
- }
29
- const { valid, error } = validateProjectName(options.projectName);
30
- if (!valid) {
31
- console.error(error);
32
- process.exit(1);
33
- }
34
- }
35
- else {
36
- options.projectName = await getProjectName();
20
+ const projectLocation = resolveProjectLocation({
21
+ projectName: cliOptions.projectName ?? (await getProjectName()),
22
+ targetDir: cliOptions.targetDir,
23
+ emptyProjectNameIsCurrentDirectory: true,
24
+ });
25
+ if (!projectLocation) {
26
+ throw new Error('Project name or target directory is required');
27
+ }
28
+ options.projectName = projectLocation.projectName;
29
+ options.targetDir = projectLocation.targetDir;
30
+ const { valid, error } = validateProjectName(options.projectName);
31
+ if (!valid) {
32
+ console.error(error);
33
+ process.exit(1);
37
34
  }
38
35
  // Mode is always file-router (TanStack Start)
39
36
  options.mode = 'file-router';
package/dist/telemetry.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { version as nodeVersion } from 'node:process';
2
- import { getTelemetryStatus, markTelemetryNoticeSeen, TELEMETRY_NOTICE_VERSION, } from './telemetry-config.js';
2
+ import { TELEMETRY_NOTICE_VERSION, getTelemetryStatus, markTelemetryNoticeSeen, } from './telemetry-config.js';
3
3
  const TELEMETRY_TRANSPORT_ENDPOINT = 'https://www.google-analytics.com/g/collect';
4
4
  const TELEMETRY_PROPERTY_ID = 'G-JMT1Z50SPS';
5
5
  const TELEMETRY_NOTICE = 'TanStack CLI sends anonymous usage telemetry by default. It never sends project names, paths, raw search text, template URLs, add-on config values, or raw error messages. Disable it with `tanstack telemetry disable` or `TANSTACK_CLI_TELEMETRY_DISABLED=1`.';
@@ -1,5 +1,16 @@
1
1
  export declare function sanitizePackageName(name: string): string;
2
2
  export declare function getCurrentDirectoryName(): string;
3
+ export declare function getDirectoryPackageName(directory: string): string;
4
+ export declare function getCurrentDirectoryPackageName(): string;
5
+ export declare function isCurrentDirectoryProjectNameInput(name: string): boolean;
6
+ export declare function resolveProjectLocation({ projectName, targetDir, emptyProjectNameIsCurrentDirectory, }: {
7
+ projectName?: string;
8
+ targetDir?: string;
9
+ emptyProjectNameIsCurrentDirectory?: boolean;
10
+ }): {
11
+ projectName: string;
12
+ targetDir: string;
13
+ } | undefined;
3
14
  export declare function validateProjectName(name: string): {
4
15
  valid: boolean;
5
16
  error: string;
@@ -1,6 +1,6 @@
1
1
  import { cancel, confirm, isCancel, multiselect, note, password, select, text, } from '@clack/prompts';
2
2
  import { DEFAULT_PACKAGE_MANAGER, SUPPORTED_PACKAGE_MANAGERS, getAllAddOns, } from '@tanstack/create';
3
- import { validateProjectName } from './utils.js';
3
+ import { isCurrentDirectoryProjectNameInput, validateProjectName, } from './utils.js';
4
4
  export async function selectFramework(frameworks, defaultFrameworkId) {
5
5
  const initialValue = (defaultFrameworkId &&
6
6
  frameworks.find((f) => f.id.toLowerCase() === defaultFrameworkId.toLowerCase())?.id) ||
@@ -34,10 +34,10 @@ export async function selectInstall() {
34
34
  export async function getProjectName() {
35
35
  const value = await text({
36
36
  message: 'What would you like to name your project?',
37
- defaultValue: 'my-app',
37
+ placeholder: 'Leave empty to initialize in the current directory',
38
38
  validate(value) {
39
- if (!value) {
40
- return 'Please enter a name';
39
+ if (isCurrentDirectoryProjectNameInput(value)) {
40
+ return;
41
41
  }
42
42
  const { valid, error } = validateProjectName(value);
43
43
  if (!valid) {
@@ -49,7 +49,7 @@ export async function getProjectName() {
49
49
  cancel('Operation cancelled.');
50
50
  process.exit(0);
51
51
  }
52
- return value;
52
+ return value.trim();
53
53
  }
54
54
  export async function selectPackageManager() {
55
55
  const packageManager = await select({
@@ -208,25 +208,19 @@ export async function promptForAddOnOptions(addOnIds, framework) {
208
208
  continue;
209
209
  addOnOptions[addOnId] = {};
210
210
  for (const [optionName, option] of Object.entries(addOn.options)) {
211
- if (option && typeof option === 'object' && 'type' in option) {
212
- if (option.type === 'select') {
213
- const selectOption = option;
214
- const value = await select({
215
- message: `${addOn.name}: ${selectOption.label}`,
216
- options: selectOption.options.map((opt) => ({
217
- value: opt.value,
218
- label: opt.label,
219
- })),
220
- initialValue: selectOption.default,
221
- });
222
- if (isCancel(value)) {
223
- cancel('Operation cancelled.');
224
- process.exit(0);
225
- }
226
- addOnOptions[addOnId][optionName] = value;
227
- }
228
- // Future option types can be added here
211
+ const value = await select({
212
+ message: `${addOn.name}: ${option.label}`,
213
+ options: option.options.map((opt) => ({
214
+ value: opt.value,
215
+ label: opt.label,
216
+ })),
217
+ initialValue: option.default,
218
+ });
219
+ if (isCancel(value)) {
220
+ cancel('Operation cancelled.');
221
+ process.exit(0);
229
222
  }
223
+ addOnOptions[addOnId][optionName] = value;
230
224
  }
231
225
  }
232
226
  return addOnOptions;
package/dist/utils.js CHANGED
@@ -1,5 +1,6 @@
1
- import { basename } from 'node:path';
1
+ import { basename, resolve } from 'node:path';
2
2
  import validatePackageName from 'validate-npm-package-name';
3
+ const FALLBACK_PACKAGE_NAME = 'tanstack-app';
3
4
  export function sanitizePackageName(name) {
4
5
  return name
5
6
  .toLowerCase()
@@ -13,6 +14,47 @@ export function sanitizePackageName(name) {
13
14
  export function getCurrentDirectoryName() {
14
15
  return basename(process.cwd());
15
16
  }
17
+ export function getDirectoryPackageName(directory) {
18
+ return sanitizePackageName(basename(resolve(directory))) || FALLBACK_PACKAGE_NAME;
19
+ }
20
+ export function getCurrentDirectoryPackageName() {
21
+ return getDirectoryPackageName(process.cwd());
22
+ }
23
+ export function isCurrentDirectoryProjectNameInput(name) {
24
+ const normalized = name.trim();
25
+ return normalized === '' || normalized === '.';
26
+ }
27
+ export function resolveProjectLocation({ projectName, targetDir, emptyProjectNameIsCurrentDirectory = false, }) {
28
+ const normalizedProjectName = projectName?.trim() ?? '';
29
+ if (normalizedProjectName === '.') {
30
+ return {
31
+ projectName: getCurrentDirectoryPackageName(),
32
+ targetDir: resolve(process.cwd()),
33
+ };
34
+ }
35
+ if (normalizedProjectName) {
36
+ return {
37
+ projectName: normalizedProjectName,
38
+ targetDir: targetDir
39
+ ? resolve(targetDir)
40
+ : resolve(process.cwd(), normalizedProjectName),
41
+ };
42
+ }
43
+ if (targetDir) {
44
+ const resolvedTargetDir = resolve(targetDir);
45
+ return {
46
+ projectName: getDirectoryPackageName(resolvedTargetDir),
47
+ targetDir: resolvedTargetDir,
48
+ };
49
+ }
50
+ if (emptyProjectNameIsCurrentDirectory) {
51
+ return {
52
+ projectName: getCurrentDirectoryPackageName(),
53
+ targetDir: resolve(process.cwd()),
54
+ };
55
+ }
56
+ return undefined;
57
+ }
16
58
  export function validateProjectName(name) {
17
59
  const { validForNewPackages, validForOldPackages, errors, warnings } = validatePackageName(name);
18
60
  const error = errors?.[0] || warnings?.[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/cli",
3
- "version": "0.67.1",
3
+ "version": "0.69.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.67.0"
47
+ "@tanstack/create": "0.68.1"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@playwright/test": "^1.58.2",