@tanstack/cli 0.62.4 → 0.63.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 +765 -409
- package/dist/telemetry-config.js +140 -0
- package/dist/telemetry.js +195 -0
- package/dist/types/telemetry-config.d.ts +47 -0
- package/dist/types/telemetry.d.ts +24 -0
- package/dist/types/types.d.ts +2 -0
- package/dist/types/ui-environment.d.ts +2 -1
- package/dist/ui-environment.js +18 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,8 @@ import chalk from 'chalk';
|
|
|
6
6
|
import semver from 'semver';
|
|
7
7
|
import { SUPPORTED_PACKAGE_MANAGERS, addToApp, compileAddOn, compileStarter, createApp, devAddOn, getAllAddOns, getFrameworkByName, getFrameworks, initAddOn, initStarter, } from '@tanstack/create';
|
|
8
8
|
import { LIBRARY_GROUPS, fetchDocContent, fetchLibraries, fetchPartners, searchTanStackDocs, } from './discovery.js';
|
|
9
|
+
import { getTelemetryStatus, setTelemetryEnabled, } from './telemetry-config.js';
|
|
10
|
+
import { createTelemetryClient } from './telemetry.js';
|
|
9
11
|
import { promptForAddOns, promptForCreateOptions } from './options.js';
|
|
10
12
|
import { normalizeOptions, validateDevWatchOptions, validateLegacyCreateFlags, } from './command-line.js';
|
|
11
13
|
import { createUIEnvironment } from './ui-environment.js';
|
|
@@ -14,8 +16,127 @@ import { DevWatchManager } from './dev-watch.js';
|
|
|
14
16
|
const packageJsonPath = new URL('../package.json', import.meta.url);
|
|
15
17
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
16
18
|
const VERSION = packageJson.version;
|
|
19
|
+
function isLocalPath(value) {
|
|
20
|
+
return (value.startsWith('./') ||
|
|
21
|
+
value.startsWith('../') ||
|
|
22
|
+
value.startsWith('/') ||
|
|
23
|
+
/^[a-zA-Z]:[\\/]/.test(value));
|
|
24
|
+
}
|
|
25
|
+
function isRemoteUrl(value) {
|
|
26
|
+
return /^https?:\/\//i.test(value) || /^file:\/\//i.test(value);
|
|
27
|
+
}
|
|
28
|
+
function sanitizeId(value) {
|
|
29
|
+
const normalized = value.trim().toLowerCase();
|
|
30
|
+
if (!normalized) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return normalized.replace(/[^a-z0-9._:/-]/g, '-');
|
|
34
|
+
}
|
|
35
|
+
function sanitizeIdList(values) {
|
|
36
|
+
return Array.from(new Set(values
|
|
37
|
+
.map((value) => sanitizeId(value))
|
|
38
|
+
.filter((value) => Boolean(value))));
|
|
39
|
+
}
|
|
40
|
+
function getStarterTelemetryProperties(value) {
|
|
41
|
+
if (!value) {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
if (isRemoteUrl(value)) {
|
|
45
|
+
return {
|
|
46
|
+
starter_kind: 'remote_url',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (isLocalPath(value)) {
|
|
50
|
+
return {
|
|
51
|
+
starter_kind: 'local_path',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
starter_id: sanitizeId(value),
|
|
56
|
+
starter_kind: 'built_in',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function getLengthBucket(value) {
|
|
60
|
+
const length = value.trim().length;
|
|
61
|
+
if (length === 0) {
|
|
62
|
+
return 'empty';
|
|
63
|
+
}
|
|
64
|
+
if (length <= 10) {
|
|
65
|
+
return '1_10';
|
|
66
|
+
}
|
|
67
|
+
if (length <= 25) {
|
|
68
|
+
return '11_25';
|
|
69
|
+
}
|
|
70
|
+
if (length <= 50) {
|
|
71
|
+
return '26_50';
|
|
72
|
+
}
|
|
73
|
+
return '51_plus';
|
|
74
|
+
}
|
|
75
|
+
function getCreateCommandVariant(options) {
|
|
76
|
+
if (options.listAddOns) {
|
|
77
|
+
return 'list_add_ons';
|
|
78
|
+
}
|
|
79
|
+
if (options.addonDetails) {
|
|
80
|
+
return 'addon_details';
|
|
81
|
+
}
|
|
82
|
+
if (options.devWatch) {
|
|
83
|
+
return 'dev_watch';
|
|
84
|
+
}
|
|
85
|
+
return 'scaffold';
|
|
86
|
+
}
|
|
87
|
+
function getCreateTelemetryProperties(projectName, options) {
|
|
88
|
+
const addOnIds = Array.isArray(options.addOns)
|
|
89
|
+
? sanitizeIdList(options.addOns)
|
|
90
|
+
: undefined;
|
|
91
|
+
return {
|
|
92
|
+
...getStarterTelemetryProperties(options.starter || options.templateId || options.template),
|
|
93
|
+
add_on_count: addOnIds?.length,
|
|
94
|
+
add_on_ids: addOnIds,
|
|
95
|
+
addon_details_id: options.addonDetails
|
|
96
|
+
? sanitizeId(options.addonDetails)
|
|
97
|
+
: undefined,
|
|
98
|
+
command_variant: getCreateCommandVariant(options),
|
|
99
|
+
deployment: options.deployment ? sanitizeId(options.deployment) : undefined,
|
|
100
|
+
examples: options.examples,
|
|
101
|
+
framework: options.framework ? sanitizeId(options.framework) : undefined,
|
|
102
|
+
git: options.git,
|
|
103
|
+
install: options.install !== false,
|
|
104
|
+
interactive: !!options.interactive,
|
|
105
|
+
json: !!options.json,
|
|
106
|
+
non_interactive: !!options.nonInteractive || !!options.yes,
|
|
107
|
+
package_manager: options.packageManager,
|
|
108
|
+
project_name_provided: Boolean(projectName),
|
|
109
|
+
router_only: !!options.routerOnly,
|
|
110
|
+
target_dir_flag: Boolean(options.targetDir),
|
|
111
|
+
toolchain: typeof options.toolchain === 'string' ? sanitizeId(options.toolchain) : undefined,
|
|
112
|
+
yes: !!options.yes,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function getResolvedCreateTelemetryProperties(finalOptions, cliOptions) {
|
|
116
|
+
const includeExamples = finalOptions.includeExamples !== false;
|
|
117
|
+
const addOnIds = sanitizeIdList(finalOptions.chosenAddOns.map((addOn) => addOn.id));
|
|
118
|
+
const deployment = finalOptions.chosenAddOns.find((addOn) => addOn.type === 'deployment');
|
|
119
|
+
const toolchain = finalOptions.chosenAddOns.find((addOn) => addOn.type === 'toolchain');
|
|
120
|
+
return {
|
|
121
|
+
...getStarterTelemetryProperties(finalOptions.starter?.id || cliOptions.starter || cliOptions.templateId || cliOptions.template),
|
|
122
|
+
add_on_count: addOnIds.length,
|
|
123
|
+
add_on_ids: addOnIds,
|
|
124
|
+
deployment: deployment ? sanitizeId(deployment.id) : undefined,
|
|
125
|
+
examples: includeExamples,
|
|
126
|
+
framework: sanitizeId(finalOptions.framework.id),
|
|
127
|
+
git: finalOptions.git,
|
|
128
|
+
install: finalOptions.install !== false,
|
|
129
|
+
package_manager: finalOptions.packageManager,
|
|
130
|
+
router_only: !!cliOptions.routerOnly,
|
|
131
|
+
toolchain: toolchain ? sanitizeId(toolchain.id) : undefined,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function formatErrorMessage(error) {
|
|
135
|
+
return error instanceof Error ? error.message : 'An unknown error occurred';
|
|
136
|
+
}
|
|
17
137
|
export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaultFramework, frameworkDefinitionInitializers, showDeploymentOptions = false, legacyAutoCreate = false, defaultRouterOnly = false, }) {
|
|
18
|
-
|
|
138
|
+
let currentTelemetry;
|
|
139
|
+
const environment = createUIEnvironment(appName, false, () => currentTelemetry);
|
|
19
140
|
const program = new Command();
|
|
20
141
|
async function confirmTargetDirectorySafety(targetDir, forced) {
|
|
21
142
|
if (forced) {
|
|
@@ -56,22 +177,18 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
56
177
|
// Validate dev watch options
|
|
57
178
|
const validation = validateDevWatchOptions({ ...options, projectName });
|
|
58
179
|
if (!validation.valid) {
|
|
59
|
-
|
|
60
|
-
process.exit(1);
|
|
180
|
+
throw new Error(validation.error);
|
|
61
181
|
}
|
|
62
182
|
// Enter dev watch mode
|
|
63
183
|
if (!projectName && !options.targetDir) {
|
|
64
|
-
|
|
65
|
-
process.exit(1);
|
|
184
|
+
throw new Error('Project name/target directory is required for dev watch mode');
|
|
66
185
|
}
|
|
67
186
|
if (!options.framework) {
|
|
68
|
-
|
|
69
|
-
process.exit(1);
|
|
187
|
+
throw new Error('Failed to detect framework');
|
|
70
188
|
}
|
|
71
189
|
const framework = getFrameworkByName(options.framework);
|
|
72
190
|
if (!framework) {
|
|
73
|
-
|
|
74
|
-
process.exit(1);
|
|
191
|
+
throw new Error('Failed to detect framework');
|
|
75
192
|
}
|
|
76
193
|
// First, create the app normally using the standard flow
|
|
77
194
|
const normalizedOpts = await normalizeOptions({
|
|
@@ -82,6 +199,7 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
82
199
|
if (!normalizedOpts) {
|
|
83
200
|
throw new Error('Failed to normalize options');
|
|
84
201
|
}
|
|
202
|
+
currentTelemetry?.mergeProperties(getResolvedCreateTelemetryProperties(normalizedOpts, options));
|
|
85
203
|
normalizedOpts.targetDir =
|
|
86
204
|
options.targetDir || resolve(process.cwd(), projectName);
|
|
87
205
|
// Create the initial app with minimal output for dev watch mode
|
|
@@ -90,7 +208,7 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
90
208
|
if (normalizedOpts.install !== false) {
|
|
91
209
|
console.log(chalk.gray('├─') + ' ' + chalk.yellow('⟳') + ' installing packages...');
|
|
92
210
|
}
|
|
93
|
-
const silentEnvironment = createUIEnvironment(appName, true);
|
|
211
|
+
const silentEnvironment = createUIEnvironment(appName, true, () => currentTelemetry);
|
|
94
212
|
await confirmTargetDirectorySafety(normalizedOpts.targetDir, options.force);
|
|
95
213
|
await createApp(silentEnvironment, normalizedOpts);
|
|
96
214
|
console.log(chalk.gray('└─') + ' ' + chalk.green('✓') + ` app created`);
|
|
@@ -152,197 +270,238 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
152
270
|
}
|
|
153
271
|
return parsed;
|
|
154
272
|
}
|
|
273
|
+
async function runWithTelemetry(command, opts, action) {
|
|
274
|
+
const telemetry = await createTelemetryClient({ json: opts.json });
|
|
275
|
+
const startedAt = Date.now();
|
|
276
|
+
currentTelemetry = telemetry;
|
|
277
|
+
telemetry.captureCommandStarted(command, {
|
|
278
|
+
...opts.properties,
|
|
279
|
+
cli_version: VERSION,
|
|
280
|
+
});
|
|
281
|
+
try {
|
|
282
|
+
const result = await action(telemetry);
|
|
283
|
+
await telemetry.captureCommandCompleted(command, Date.now() - startedAt);
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
await telemetry.captureCommandFailed(command, Date.now() - startedAt, error);
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
currentTelemetry = undefined;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
155
294
|
program
|
|
156
295
|
.name(name)
|
|
157
296
|
.description(`${appName} CLI`)
|
|
158
297
|
.version(VERSION, '-v, --version', 'output the current version');
|
|
159
298
|
// Helper to create the create command action handler
|
|
160
299
|
async function handleCreate(projectName, options) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
console.log(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
console.log(`
|
|
300
|
+
try {
|
|
301
|
+
await runWithTelemetry('create', {
|
|
302
|
+
json: options.json,
|
|
303
|
+
properties: getCreateTelemetryProperties(projectName, options),
|
|
304
|
+
}, async (telemetry) => {
|
|
305
|
+
const legacyCreateFlags = validateLegacyCreateFlags(options);
|
|
306
|
+
if (legacyCreateFlags.error) {
|
|
307
|
+
throw new Error(legacyCreateFlags.error);
|
|
308
|
+
}
|
|
309
|
+
for (const warning of legacyCreateFlags.warnings) {
|
|
310
|
+
log.warn(warning);
|
|
311
|
+
}
|
|
312
|
+
if (options.listAddOns) {
|
|
313
|
+
const addOns = await getAllAddOns(getFrameworkByName(options.framework || defaultFramework || 'React'), defaultMode);
|
|
314
|
+
const visibleAddOns = addOns.filter((a) => !forcedAddOns.includes(a.id));
|
|
315
|
+
telemetry.mergeProperties({
|
|
316
|
+
result_count: visibleAddOns.length,
|
|
317
|
+
});
|
|
318
|
+
if (options.json) {
|
|
319
|
+
printJson(visibleAddOns.map((addOn) => ({
|
|
320
|
+
id: addOn.id,
|
|
321
|
+
name: addOn.name,
|
|
322
|
+
description: addOn.description,
|
|
323
|
+
type: addOn.type,
|
|
324
|
+
category: addOn.category,
|
|
325
|
+
phase: addOn.phase,
|
|
326
|
+
modes: addOn.modes,
|
|
327
|
+
link: addOn.link,
|
|
328
|
+
warning: addOn.warning,
|
|
329
|
+
exclusive: addOn.exclusive,
|
|
330
|
+
dependsOn: addOn.dependsOn,
|
|
331
|
+
options: addOn.options,
|
|
332
|
+
})));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
let hasConfigurableAddOns = false;
|
|
336
|
+
for (const addOn of visibleAddOns) {
|
|
337
|
+
const hasOptions = addOn.options && Object.keys(addOn.options).length > 0;
|
|
338
|
+
const optionMarker = hasOptions ? '*' : ' ';
|
|
339
|
+
if (hasOptions)
|
|
340
|
+
hasConfigurableAddOns = true;
|
|
341
|
+
console.log(`${optionMarker} ${chalk.bold(addOn.id)}: ${addOn.description}`);
|
|
342
|
+
}
|
|
343
|
+
if (hasConfigurableAddOns) {
|
|
344
|
+
console.log('\n* = has configuration options');
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (options.addonDetails) {
|
|
349
|
+
const addOns = await getAllAddOns(getFrameworkByName(options.framework || defaultFramework || 'React'), defaultMode);
|
|
350
|
+
const addOn = addOns.find((a) => a.id === options.addonDetails) ??
|
|
351
|
+
addOns.find((a) => a.id.toLowerCase() === options.addonDetails.toLowerCase());
|
|
352
|
+
if (!addOn) {
|
|
353
|
+
throw new Error(`Add-on '${options.addonDetails}' not found`);
|
|
354
|
+
}
|
|
355
|
+
telemetry.mergeProperties({
|
|
356
|
+
add_on_file_count: (await addOn.getFiles()).length,
|
|
357
|
+
});
|
|
358
|
+
if (options.json) {
|
|
359
|
+
const files = await addOn.getFiles();
|
|
360
|
+
printJson({
|
|
361
|
+
id: addOn.id,
|
|
362
|
+
name: addOn.name,
|
|
363
|
+
description: addOn.description,
|
|
364
|
+
type: addOn.type,
|
|
365
|
+
category: addOn.category,
|
|
366
|
+
phase: addOn.phase,
|
|
367
|
+
modes: addOn.modes,
|
|
368
|
+
link: addOn.link,
|
|
369
|
+
warning: addOn.warning,
|
|
370
|
+
exclusive: addOn.exclusive,
|
|
371
|
+
dependsOn: addOn.dependsOn,
|
|
372
|
+
options: addOn.options,
|
|
373
|
+
routes: addOn.routes,
|
|
374
|
+
packageAdditions: addOn.packageAdditions,
|
|
375
|
+
shadcnComponents: addOn.shadcnComponents,
|
|
376
|
+
integrations: addOn.integrations,
|
|
377
|
+
readme: addOn.readme,
|
|
378
|
+
files,
|
|
379
|
+
author: addOn.author,
|
|
380
|
+
version: addOn.version,
|
|
381
|
+
license: addOn.license,
|
|
382
|
+
});
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
console.log(`${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`);
|
|
386
|
+
console.log(`${chalk.bold('ID:')} ${addOn.id}`);
|
|
387
|
+
console.log(`${chalk.bold('Description:')} ${addOn.description}`);
|
|
388
|
+
console.log(`${chalk.bold('Type:')} ${addOn.type}`);
|
|
389
|
+
console.log(`${chalk.bold('Phase:')} ${addOn.phase}`);
|
|
390
|
+
console.log(`${chalk.bold('Supported Modes:')} ${addOn.modes.join(', ')}`);
|
|
391
|
+
if (addOn.link) {
|
|
392
|
+
console.log(`${chalk.bold('Link:')} ${chalk.blue(addOn.link)}`);
|
|
393
|
+
}
|
|
394
|
+
if (addOn.dependsOn && addOn.dependsOn.length > 0) {
|
|
395
|
+
console.log(`${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`);
|
|
396
|
+
}
|
|
397
|
+
if (addOn.options && Object.keys(addOn.options).length > 0) {
|
|
398
|
+
console.log(`\n${chalk.bold.yellow('Configuration Options:')}`);
|
|
399
|
+
for (const [optionName, option] of Object.entries(addOn.options)) {
|
|
400
|
+
if ('type' in option) {
|
|
401
|
+
const opt = option;
|
|
402
|
+
console.log(` ${chalk.bold(optionName)}:`);
|
|
403
|
+
console.log(` Label: ${opt.label}`);
|
|
404
|
+
if (opt.description) {
|
|
405
|
+
console.log(` Description: ${opt.description}`);
|
|
406
|
+
}
|
|
407
|
+
console.log(` Type: ${opt.type}`);
|
|
408
|
+
console.log(` Default: ${opt.default}`);
|
|
409
|
+
if (opt.type === 'select' && opt.options) {
|
|
410
|
+
console.log(` Available values:`);
|
|
411
|
+
for (const choice of opt.options) {
|
|
412
|
+
console.log(` - ${choice.value}: ${choice.label}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
265
415
|
}
|
|
266
416
|
}
|
|
267
417
|
}
|
|
418
|
+
else {
|
|
419
|
+
console.log(`\n${chalk.gray('No configuration options available')}`);
|
|
420
|
+
}
|
|
421
|
+
if (addOn.routes && addOn.routes.length > 0) {
|
|
422
|
+
console.log(`\n${chalk.bold.green('Routes:')}`);
|
|
423
|
+
for (const route of addOn.routes) {
|
|
424
|
+
console.log(` ${chalk.bold(route.url)} (${route.name})`);
|
|
425
|
+
console.log(` File: ${route.path}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
268
429
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
if (addOn.routes && addOn.routes.length > 0) {
|
|
274
|
-
console.log(`\n${chalk.bold.green('Routes:')}`);
|
|
275
|
-
for (const route of addOn.routes) {
|
|
276
|
-
console.log(` ${chalk.bold(route.url)} (${route.name})`);
|
|
277
|
-
console.log(` File: ${route.path}`);
|
|
430
|
+
if (options.devWatch) {
|
|
431
|
+
await startDevWatchMode(projectName, options);
|
|
432
|
+
return;
|
|
278
433
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
cliOptions.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
finalOptions.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
434
|
+
const cliOptions = {
|
|
435
|
+
projectName,
|
|
436
|
+
...options,
|
|
437
|
+
};
|
|
438
|
+
if (defaultRouterOnly && cliOptions.routerOnly === undefined) {
|
|
439
|
+
cliOptions.routerOnly = true;
|
|
440
|
+
}
|
|
441
|
+
if (cliOptions.routerOnly !== true &&
|
|
442
|
+
cliOptions.template &&
|
|
443
|
+
['file-router', 'typescript', 'tsx', 'javascript', 'js', 'jsx'].includes(cliOptions.template.toLowerCase()) &&
|
|
444
|
+
cliOptions.template.toLowerCase() !== 'file-router') {
|
|
445
|
+
cliOptions.routerOnly = true;
|
|
446
|
+
}
|
|
447
|
+
cliOptions.framework = getFrameworkByName(options.framework || defaultFramework || 'React').id;
|
|
448
|
+
const nonInteractive = !!cliOptions.nonInteractive || !!cliOptions.yes;
|
|
449
|
+
if (cliOptions.interactive && nonInteractive) {
|
|
450
|
+
throw new Error('Cannot combine --interactive with --non-interactive/--yes.');
|
|
451
|
+
}
|
|
452
|
+
const addOnsFlagPassed = process.argv.includes('--add-ons');
|
|
453
|
+
const wantsInteractiveMode = !nonInteractive &&
|
|
454
|
+
(cliOptions.interactive ||
|
|
455
|
+
(cliOptions.addOns === true && addOnsFlagPassed));
|
|
456
|
+
let finalOptions;
|
|
457
|
+
if (wantsInteractiveMode) {
|
|
458
|
+
cliOptions.addOns = true;
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
finalOptions = await normalizeOptions(cliOptions, forcedAddOns, { forcedDeployment });
|
|
462
|
+
}
|
|
463
|
+
if (nonInteractive) {
|
|
464
|
+
if (cliOptions.addOns === true) {
|
|
465
|
+
throw new Error('When using --non-interactive/--yes, pass explicit add-ons via --add-ons <ids>.');
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (finalOptions) {
|
|
469
|
+
intro(`Creating a new ${appName} app in ${projectName}...`);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
if (nonInteractive) {
|
|
473
|
+
throw new Error('Project name is required in non-interactive mode. Pass [project-name] or --target-dir.');
|
|
474
|
+
}
|
|
475
|
+
intro(`Let's configure your ${appName} application`);
|
|
476
|
+
finalOptions = await promptForCreateOptions(cliOptions, {
|
|
477
|
+
forcedAddOns,
|
|
478
|
+
showDeploymentOptions,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
if (!finalOptions) {
|
|
482
|
+
throw new Error('No options were provided');
|
|
483
|
+
}
|
|
484
|
+
telemetry.mergeProperties(getResolvedCreateTelemetryProperties(finalOptions, cliOptions));
|
|
485
|
+
finalOptions.routerOnly =
|
|
486
|
+
!!cliOptions.routerOnly;
|
|
487
|
+
if (options.targetDir) {
|
|
488
|
+
finalOptions.targetDir = options.targetDir;
|
|
489
|
+
}
|
|
490
|
+
else if (finalOptions.targetDir) {
|
|
491
|
+
// Keep the normalized target dir.
|
|
492
|
+
}
|
|
493
|
+
else if (projectName === '.') {
|
|
494
|
+
finalOptions.targetDir = resolve(process.cwd());
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
finalOptions.targetDir = resolve(process.cwd(), finalOptions.projectName);
|
|
498
|
+
}
|
|
499
|
+
await confirmTargetDirectorySafety(finalOptions.targetDir, options.force);
|
|
500
|
+
await createApp(environment, finalOptions);
|
|
501
|
+
});
|
|
343
502
|
}
|
|
344
503
|
catch (error) {
|
|
345
|
-
log.error(error
|
|
504
|
+
log.error(formatErrorMessage(error));
|
|
346
505
|
process.exit(1);
|
|
347
506
|
}
|
|
348
507
|
}
|
|
@@ -398,6 +557,8 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
398
557
|
}
|
|
399
558
|
cmd
|
|
400
559
|
.option('--interactive', 'interactive mode', false)
|
|
560
|
+
.option('--non-interactive', 'skip prompts and use defaults', false)
|
|
561
|
+
.option('-y, --yes', 'accept defaults and skip prompts', false)
|
|
401
562
|
.option('--add-ons [...add-ons]', 'pick from a list of available add-ons (comma separated list)', (value) => {
|
|
402
563
|
let addOns = !!value;
|
|
403
564
|
if (typeof value === 'string') {
|
|
@@ -428,21 +589,36 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
428
589
|
.description('Create a sandbox app and watch built-in framework templates/add-ons');
|
|
429
590
|
configureCreateCommand(devCommand);
|
|
430
591
|
devCommand.action(async (projectName, options) => {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
592
|
+
try {
|
|
593
|
+
await runWithTelemetry('dev', {
|
|
594
|
+
properties: {
|
|
595
|
+
framework: options.framework
|
|
596
|
+
? sanitizeId(options.framework)
|
|
597
|
+
: sanitizeId(defaultFramework || 'react'),
|
|
598
|
+
install: options.install !== false,
|
|
599
|
+
run_dev: true,
|
|
600
|
+
},
|
|
601
|
+
}, async () => {
|
|
602
|
+
const frameworkName = options.framework || defaultFramework || 'React';
|
|
603
|
+
const framework = getFrameworkByName(frameworkName);
|
|
604
|
+
if (!framework) {
|
|
605
|
+
throw new Error(`Unknown framework: ${frameworkName}`);
|
|
606
|
+
}
|
|
607
|
+
const watchPath = resolveBuiltInDevWatchPath(framework.id);
|
|
608
|
+
const devOptions = {
|
|
609
|
+
...options,
|
|
610
|
+
framework: framework.name,
|
|
611
|
+
devWatch: watchPath,
|
|
612
|
+
runDev: true,
|
|
613
|
+
install: options.install ?? true,
|
|
614
|
+
};
|
|
615
|
+
await startDevWatchMode(projectName, devOptions);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
catch (error) {
|
|
619
|
+
log.error(formatErrorMessage(error));
|
|
435
620
|
process.exit(1);
|
|
436
621
|
}
|
|
437
|
-
const watchPath = resolveBuiltInDevWatchPath(framework.id);
|
|
438
|
-
const devOptions = {
|
|
439
|
-
...options,
|
|
440
|
-
framework: framework.name,
|
|
441
|
-
devWatch: watchPath,
|
|
442
|
-
runDev: true,
|
|
443
|
-
install: options.install ?? true,
|
|
444
|
-
};
|
|
445
|
-
await startDevWatchMode(projectName, devOptions);
|
|
446
622
|
});
|
|
447
623
|
// === LIBRARIES SUBCOMMAND ===
|
|
448
624
|
program
|
|
@@ -452,41 +628,52 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
452
628
|
.option('--json', 'output JSON for automation', false)
|
|
453
629
|
.action(async (options) => {
|
|
454
630
|
try {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
631
|
+
await runWithTelemetry('libraries', {
|
|
632
|
+
json: options.json,
|
|
633
|
+
properties: {
|
|
634
|
+
group: options.group ? sanitizeId(options.group) : undefined,
|
|
635
|
+
json: options.json,
|
|
636
|
+
},
|
|
637
|
+
}, async (telemetry) => {
|
|
638
|
+
const data = await fetchLibraries();
|
|
639
|
+
let libraries = data.libraries;
|
|
640
|
+
if (options.group &&
|
|
641
|
+
Object.prototype.hasOwnProperty.call(data.groups, options.group)) {
|
|
642
|
+
const groupIds = data.groups[options.group];
|
|
643
|
+
libraries = libraries.filter((lib) => groupIds.includes(lib.id));
|
|
644
|
+
}
|
|
645
|
+
const groupName = options.group
|
|
646
|
+
? data.groupNames[options.group] || options.group
|
|
647
|
+
: 'All Libraries';
|
|
648
|
+
const payload = {
|
|
649
|
+
group: groupName,
|
|
650
|
+
count: libraries.length,
|
|
651
|
+
libraries: libraries.map((lib) => ({
|
|
652
|
+
id: lib.id,
|
|
653
|
+
name: lib.name,
|
|
654
|
+
tagline: lib.tagline,
|
|
655
|
+
description: lib.description,
|
|
656
|
+
frameworks: lib.frameworks,
|
|
657
|
+
latestVersion: lib.latestVersion,
|
|
658
|
+
docsUrl: lib.docsUrl,
|
|
659
|
+
githubUrl: lib.githubUrl,
|
|
660
|
+
})),
|
|
661
|
+
};
|
|
662
|
+
telemetry.mergeProperties({
|
|
663
|
+
result_count: payload.count,
|
|
664
|
+
});
|
|
665
|
+
if (options.json) {
|
|
666
|
+
printJson(payload);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
console.log(chalk.bold(groupName));
|
|
670
|
+
for (const lib of payload.libraries) {
|
|
671
|
+
console.log(`${chalk.bold(lib.id)} (${lib.latestVersion}) - ${lib.tagline}`);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
487
674
|
}
|
|
488
675
|
catch (error) {
|
|
489
|
-
log.error(
|
|
676
|
+
log.error(formatErrorMessage(error));
|
|
490
677
|
process.exit(1);
|
|
491
678
|
}
|
|
492
679
|
});
|
|
@@ -500,56 +687,69 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
500
687
|
.option('--json', 'output JSON for automation', false)
|
|
501
688
|
.action(async (libraryId, path, options) => {
|
|
502
689
|
try {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
if (
|
|
529
|
-
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
690
|
+
await runWithTelemetry('doc', {
|
|
691
|
+
json: options.json,
|
|
692
|
+
properties: {
|
|
693
|
+
doc_path_depth: path.split('/').filter(Boolean).length,
|
|
694
|
+
docs_version: sanitizeId(options.docsVersion),
|
|
695
|
+
json: options.json,
|
|
696
|
+
library: sanitizeId(libraryId),
|
|
697
|
+
},
|
|
698
|
+
}, async (telemetry) => {
|
|
699
|
+
const data = await fetchLibraries();
|
|
700
|
+
const library = data.libraries.find((l) => l.id === libraryId);
|
|
701
|
+
if (!library) {
|
|
702
|
+
throw new Error(`Library "${libraryId}" not found. Use \`tanstack libraries\` to see available libraries.`);
|
|
703
|
+
}
|
|
704
|
+
if (options.docsVersion !== 'latest' &&
|
|
705
|
+
!library.availableVersions.includes(options.docsVersion)) {
|
|
706
|
+
throw new Error(`Version "${options.docsVersion}" not found for ${library.name}. Available: ${library.availableVersions.join(', ')}`);
|
|
707
|
+
}
|
|
708
|
+
const branch = options.docsVersion === 'latest' ||
|
|
709
|
+
options.docsVersion === library.latestVersion
|
|
710
|
+
? library.latestBranch || 'main'
|
|
711
|
+
: options.docsVersion;
|
|
712
|
+
const docsRoot = library.docsRoot || 'docs';
|
|
713
|
+
const filePath = `${docsRoot}/${path}.md`;
|
|
714
|
+
const content = await fetchDocContent(library.repo, branch, filePath);
|
|
715
|
+
if (!content) {
|
|
716
|
+
throw new Error(`Document not found: ${library.name} / ${path} (version: ${options.docsVersion})`);
|
|
717
|
+
}
|
|
718
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
719
|
+
let title = path.split('/').pop() || 'Untitled';
|
|
720
|
+
let docContent = content;
|
|
721
|
+
if (frontmatterMatch && frontmatterMatch[1]) {
|
|
722
|
+
const frontmatter = frontmatterMatch[1];
|
|
723
|
+
const titleMatch = frontmatter.match(/title:\s*['"]?([^'"\n]+)['"]?/);
|
|
724
|
+
if (titleMatch && titleMatch[1]) {
|
|
725
|
+
title = titleMatch[1];
|
|
726
|
+
}
|
|
727
|
+
docContent = content.slice(frontmatterMatch[0].length).trim();
|
|
728
|
+
}
|
|
729
|
+
const payload = {
|
|
730
|
+
title,
|
|
731
|
+
content: docContent,
|
|
732
|
+
url: `https://tanstack.com/${libraryId}/${options.docsVersion}/docs/${path}`,
|
|
733
|
+
library: library.name,
|
|
734
|
+
version: options.docsVersion === 'latest'
|
|
735
|
+
? library.latestVersion
|
|
736
|
+
: options.docsVersion,
|
|
737
|
+
};
|
|
738
|
+
telemetry.mergeProperties({
|
|
739
|
+
content_length_bucket: getLengthBucket(docContent),
|
|
740
|
+
});
|
|
741
|
+
if (options.json) {
|
|
742
|
+
printJson(payload);
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
console.log(chalk.bold(payload.title));
|
|
746
|
+
console.log(chalk.blue(payload.url));
|
|
747
|
+
console.log('');
|
|
748
|
+
console.log(payload.content);
|
|
749
|
+
});
|
|
550
750
|
}
|
|
551
751
|
catch (error) {
|
|
552
|
-
log.error(
|
|
752
|
+
log.error(formatErrorMessage(error));
|
|
553
753
|
process.exit(1);
|
|
554
754
|
}
|
|
555
755
|
});
|
|
@@ -564,22 +764,39 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
564
764
|
.option('--json', 'output JSON for automation', false)
|
|
565
765
|
.action(async (query, options) => {
|
|
566
766
|
try {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
767
|
+
await runWithTelemetry('search-docs', {
|
|
768
|
+
json: options.json,
|
|
769
|
+
properties: {
|
|
770
|
+
framework: options.framework
|
|
771
|
+
? sanitizeId(options.framework)
|
|
772
|
+
: undefined,
|
|
773
|
+
has_query: query.trim().length > 0,
|
|
774
|
+
json: options.json,
|
|
775
|
+
library: options.library ? sanitizeId(options.library) : undefined,
|
|
776
|
+
limit: options.limit,
|
|
777
|
+
query_length_bucket: getLengthBucket(query),
|
|
778
|
+
},
|
|
779
|
+
}, async (telemetry) => {
|
|
780
|
+
const payload = await searchTanStackDocs({
|
|
781
|
+
query,
|
|
782
|
+
library: options.library,
|
|
783
|
+
framework: options.framework,
|
|
784
|
+
limit: options.limit,
|
|
785
|
+
});
|
|
786
|
+
telemetry.mergeProperties({
|
|
787
|
+
result_count: payload.totalHits,
|
|
788
|
+
});
|
|
789
|
+
if (options.json) {
|
|
790
|
+
printJson(payload);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
for (const result of payload.results) {
|
|
794
|
+
console.log(`${chalk.bold(result.title)} [${result.library}]\n${chalk.blue(result.url)}\n${result.snippet}\n`);
|
|
795
|
+
}
|
|
572
796
|
});
|
|
573
|
-
if (options.json) {
|
|
574
|
-
printJson(payload);
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
for (const result of payload.results) {
|
|
578
|
-
console.log(`${chalk.bold(result.title)} [${result.library}]\n${chalk.blue(result.url)}\n${result.snippet}\n`);
|
|
579
|
-
}
|
|
580
797
|
}
|
|
581
798
|
catch (error) {
|
|
582
|
-
log.error(
|
|
799
|
+
log.error(formatErrorMessage(error));
|
|
583
800
|
process.exit(1);
|
|
584
801
|
}
|
|
585
802
|
});
|
|
@@ -592,48 +809,63 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
592
809
|
.option('--json', 'output JSON for automation', false)
|
|
593
810
|
.action(async (options) => {
|
|
594
811
|
try {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
resolvedCategory = undefined;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
const library = options.library?.toLowerCase().trim();
|
|
605
|
-
const partners = data.partners
|
|
606
|
-
.filter((partner) => resolvedCategory ? partner.category === resolvedCategory : true)
|
|
607
|
-
.filter((partner) => library ? partner.libraries.some((l) => l === library) : true)
|
|
608
|
-
.map((partner) => ({
|
|
609
|
-
id: partner.id,
|
|
610
|
-
name: partner.name,
|
|
611
|
-
tagline: partner.tagline,
|
|
612
|
-
description: partner.description,
|
|
613
|
-
category: partner.category,
|
|
614
|
-
categoryLabel: partner.categoryLabel,
|
|
615
|
-
url: partner.url,
|
|
616
|
-
libraries: partner.libraries,
|
|
617
|
-
}));
|
|
618
|
-
const payload = {
|
|
619
|
-
query: {
|
|
620
|
-
category: options.category,
|
|
621
|
-
categoryResolved: resolvedCategory,
|
|
622
|
-
library: options.library,
|
|
812
|
+
await runWithTelemetry('ecosystem', {
|
|
813
|
+
json: options.json,
|
|
814
|
+
properties: {
|
|
815
|
+
category: options.category ? sanitizeId(options.category) : undefined,
|
|
816
|
+
json: options.json,
|
|
817
|
+
library: options.library ? sanitizeId(options.library) : undefined,
|
|
623
818
|
},
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
819
|
+
}, async (telemetry) => {
|
|
820
|
+
const data = await fetchPartners();
|
|
821
|
+
let resolvedCategory;
|
|
822
|
+
if (options.category) {
|
|
823
|
+
const normalized = options.category.toLowerCase().trim();
|
|
824
|
+
resolvedCategory = categoryAliases[normalized] || normalized;
|
|
825
|
+
if (!data.categories.includes(resolvedCategory)) {
|
|
826
|
+
resolvedCategory = undefined;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const library = options.library?.toLowerCase().trim();
|
|
830
|
+
const partners = data.partners
|
|
831
|
+
.filter((partner) => resolvedCategory ? partner.category === resolvedCategory : true)
|
|
832
|
+
.filter((partner) => library ? partner.libraries.some((l) => l === library) : true)
|
|
833
|
+
.map((partner) => ({
|
|
834
|
+
id: partner.id,
|
|
835
|
+
name: partner.name,
|
|
836
|
+
tagline: partner.tagline,
|
|
837
|
+
description: partner.description,
|
|
838
|
+
category: partner.category,
|
|
839
|
+
categoryLabel: partner.categoryLabel,
|
|
840
|
+
url: partner.url,
|
|
841
|
+
libraries: partner.libraries,
|
|
842
|
+
}));
|
|
843
|
+
const payload = {
|
|
844
|
+
query: {
|
|
845
|
+
category: options.category,
|
|
846
|
+
categoryResolved: resolvedCategory,
|
|
847
|
+
library: options.library,
|
|
848
|
+
},
|
|
849
|
+
count: partners.length,
|
|
850
|
+
partners,
|
|
851
|
+
};
|
|
852
|
+
telemetry.mergeProperties({
|
|
853
|
+
category_resolved: resolvedCategory
|
|
854
|
+
? sanitizeId(resolvedCategory)
|
|
855
|
+
: undefined,
|
|
856
|
+
result_count: payload.count,
|
|
857
|
+
});
|
|
858
|
+
if (options.json) {
|
|
859
|
+
printJson(payload);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
for (const partner of partners) {
|
|
863
|
+
console.log(`${chalk.bold(partner.name)} [${partner.category}] - ${partner.description}\n${chalk.blue(partner.url)}`);
|
|
864
|
+
}
|
|
865
|
+
});
|
|
634
866
|
}
|
|
635
867
|
catch (error) {
|
|
636
|
-
log.error(
|
|
868
|
+
log.error(formatErrorMessage(error));
|
|
637
869
|
process.exit(1);
|
|
638
870
|
}
|
|
639
871
|
});
|
|
@@ -642,90 +874,158 @@ export function cli({ name, appName, forcedAddOns = [], forcedDeployment, defaul
|
|
|
642
874
|
.command('pin-versions')
|
|
643
875
|
.description('Pin versions of the TanStack libraries')
|
|
644
876
|
.action(async () => {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
650
|
-
const packages = {
|
|
651
|
-
'@tanstack/react-router': '',
|
|
652
|
-
'@tanstack/router-generator': '',
|
|
653
|
-
'@tanstack/react-router-devtools': '',
|
|
654
|
-
'@tanstack/react-start': '',
|
|
655
|
-
'@tanstack/react-start-config': '',
|
|
656
|
-
'@tanstack/router-plugin': '',
|
|
657
|
-
'@tanstack/react-start-client': '',
|
|
658
|
-
'@tanstack/react-start-plugin': '1.115.0',
|
|
659
|
-
'@tanstack/react-start-server': '',
|
|
660
|
-
'@tanstack/start-server-core': '1.115.0',
|
|
661
|
-
};
|
|
662
|
-
function sortObject(obj) {
|
|
663
|
-
return Object.keys(obj)
|
|
664
|
-
.sort()
|
|
665
|
-
.reduce((acc, key) => {
|
|
666
|
-
acc[key] = obj[key];
|
|
667
|
-
return acc;
|
|
668
|
-
}, {});
|
|
669
|
-
}
|
|
670
|
-
if (!packageJson.dependencies['@tanstack/react-start']) {
|
|
671
|
-
console.error('@tanstack/react-start not found in dependencies');
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
let changed = 0;
|
|
675
|
-
const startVersion = packageJson.dependencies['@tanstack/react-start'].replace(/^\^/, '');
|
|
676
|
-
for (const pkg of Object.keys(packages)) {
|
|
677
|
-
if (!packageJson.dependencies[pkg]) {
|
|
678
|
-
packageJson.dependencies[pkg] = packages[pkg].length
|
|
679
|
-
? semver.maxSatisfying([startVersion, packages[pkg]], `^${packages[pkg]}`)
|
|
680
|
-
: startVersion;
|
|
681
|
-
changed++;
|
|
682
|
-
}
|
|
683
|
-
else {
|
|
684
|
-
if (packageJson.dependencies[pkg].startsWith('^')) {
|
|
685
|
-
packageJson.dependencies[pkg] = packageJson.dependencies[pkg].replace(/^\^/, '');
|
|
686
|
-
changed++;
|
|
877
|
+
try {
|
|
878
|
+
await runWithTelemetry('pin-versions', {}, async (telemetry) => {
|
|
879
|
+
if (!fs.existsSync('package.json')) {
|
|
880
|
+
throw new Error('package.json not found');
|
|
687
881
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
882
|
+
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
883
|
+
const packages = {
|
|
884
|
+
'@tanstack/react-router': '',
|
|
885
|
+
'@tanstack/router-generator': '',
|
|
886
|
+
'@tanstack/react-router-devtools': '',
|
|
887
|
+
'@tanstack/react-start': '',
|
|
888
|
+
'@tanstack/react-start-config': '',
|
|
889
|
+
'@tanstack/router-plugin': '',
|
|
890
|
+
'@tanstack/react-start-client': '',
|
|
891
|
+
'@tanstack/react-start-plugin': '1.115.0',
|
|
892
|
+
'@tanstack/react-start-server': '',
|
|
893
|
+
'@tanstack/start-server-core': '1.115.0',
|
|
894
|
+
};
|
|
895
|
+
function sortObject(obj) {
|
|
896
|
+
return Object.keys(obj)
|
|
897
|
+
.sort()
|
|
898
|
+
.reduce((acc, key) => {
|
|
899
|
+
acc[key] = obj[key];
|
|
900
|
+
return acc;
|
|
901
|
+
}, {});
|
|
902
|
+
}
|
|
903
|
+
if (!packageJson.dependencies['@tanstack/react-start']) {
|
|
904
|
+
throw new Error('@tanstack/react-start not found in dependencies');
|
|
905
|
+
}
|
|
906
|
+
let changed = 0;
|
|
907
|
+
const startVersion = packageJson.dependencies['@tanstack/react-start'].replace(/^\^/, '');
|
|
908
|
+
for (const pkg of Object.keys(packages)) {
|
|
909
|
+
if (!packageJson.dependencies[pkg]) {
|
|
910
|
+
packageJson.dependencies[pkg] = packages[pkg].length
|
|
911
|
+
? semver.maxSatisfying([startVersion, packages[pkg]], `^${packages[pkg]}`)
|
|
912
|
+
: startVersion;
|
|
913
|
+
changed++;
|
|
914
|
+
}
|
|
915
|
+
else {
|
|
916
|
+
if (packageJson.dependencies[pkg].startsWith('^')) {
|
|
917
|
+
packageJson.dependencies[pkg] = packageJson.dependencies[pkg].replace(/^\^/, '');
|
|
918
|
+
changed++;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
telemetry.mergeProperties({
|
|
923
|
+
changed_count: changed,
|
|
924
|
+
});
|
|
925
|
+
packageJson.dependencies = sortObject(packageJson.dependencies);
|
|
926
|
+
if (changed > 0) {
|
|
927
|
+
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
|
|
928
|
+
console.log(`${changed} packages updated.
|
|
694
929
|
|
|
695
930
|
Remove your node_modules directory and package lock file and re-install.`);
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
console.log('No changes needed. The relevant TanStack packages are already pinned.');
|
|
934
|
+
}
|
|
935
|
+
});
|
|
696
936
|
}
|
|
697
|
-
|
|
698
|
-
|
|
937
|
+
catch (error) {
|
|
938
|
+
log.error(formatErrorMessage(error));
|
|
939
|
+
process.exit(1);
|
|
699
940
|
}
|
|
700
941
|
});
|
|
942
|
+
const telemetryCommand = program.command('telemetry');
|
|
943
|
+
telemetryCommand
|
|
944
|
+
.command('status')
|
|
945
|
+
.description('Show anonymous telemetry status')
|
|
946
|
+
.option('--json', 'output JSON for automation', false)
|
|
947
|
+
.action(async (options) => {
|
|
948
|
+
const status = await getTelemetryStatus({ createIfMissing: true });
|
|
949
|
+
const payload = {
|
|
950
|
+
configPath: status.configPath,
|
|
951
|
+
disabledBy: status.disabledBy,
|
|
952
|
+
distinctId: status.distinctId,
|
|
953
|
+
enabled: status.enabled,
|
|
954
|
+
noticeVersion: status.noticeVersion,
|
|
955
|
+
};
|
|
956
|
+
if (options.json) {
|
|
957
|
+
printJson(payload);
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
console.log(`Telemetry ${status.enabled ? 'enabled' : 'disabled'}`);
|
|
961
|
+
console.log(`Config: ${status.configPath}`);
|
|
962
|
+
if (status.disabledBy) {
|
|
963
|
+
console.log(`Disabled by: ${status.disabledBy}`);
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
telemetryCommand
|
|
967
|
+
.command('enable')
|
|
968
|
+
.description('Enable anonymous telemetry')
|
|
969
|
+
.action(async () => {
|
|
970
|
+
await setTelemetryEnabled(true);
|
|
971
|
+
console.log('Anonymous telemetry enabled');
|
|
972
|
+
});
|
|
973
|
+
telemetryCommand
|
|
974
|
+
.command('disable')
|
|
975
|
+
.description('Disable anonymous telemetry')
|
|
976
|
+
.action(async () => {
|
|
977
|
+
await setTelemetryEnabled(false);
|
|
978
|
+
console.log('Anonymous telemetry disabled');
|
|
979
|
+
});
|
|
701
980
|
// === ADD SUBCOMMAND ===
|
|
702
981
|
program
|
|
703
982
|
.command('add')
|
|
704
983
|
.argument('[add-on...]', 'Name of the add-ons (or add-ons separated by spaces or commas)')
|
|
705
984
|
.option('--forced', 'Force the add-on to be added', false)
|
|
706
985
|
.action(async (addOns, options) => {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
parsedAddOns
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
986
|
+
try {
|
|
987
|
+
await runWithTelemetry('add', {
|
|
988
|
+
properties: {
|
|
989
|
+
forced: options.forced,
|
|
990
|
+
},
|
|
991
|
+
}, async (telemetry) => {
|
|
992
|
+
const parsedAddOns = [];
|
|
993
|
+
for (const addOn of addOns) {
|
|
994
|
+
if (addOn.includes(',') || addOn.includes(' ')) {
|
|
995
|
+
parsedAddOns.push(...addOn.split(/[\s,]+/).map((addon) => addon.trim()));
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
parsedAddOns.push(addOn.trim());
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (parsedAddOns.length < 1) {
|
|
1002
|
+
const selectedAddOns = await promptForAddOns();
|
|
1003
|
+
telemetry.mergeProperties({
|
|
1004
|
+
add_on_count: selectedAddOns.length,
|
|
1005
|
+
add_on_ids: sanitizeIdList(selectedAddOns),
|
|
1006
|
+
prompted: true,
|
|
1007
|
+
});
|
|
1008
|
+
if (selectedAddOns.length) {
|
|
1009
|
+
await addToApp(environment, selectedAddOns, resolve(process.cwd()), {
|
|
1010
|
+
forced: options.forced,
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
telemetry.mergeProperties({
|
|
1016
|
+
add_on_count: parsedAddOns.length,
|
|
1017
|
+
add_on_ids: sanitizeIdList(parsedAddOns),
|
|
1018
|
+
prompted: false,
|
|
1019
|
+
});
|
|
1020
|
+
await addToApp(environment, parsedAddOns, resolve(process.cwd()), {
|
|
720
1021
|
forced: options.forced,
|
|
721
1022
|
});
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
else {
|
|
725
|
-
await addToApp(environment, parsedAddOns, resolve(process.cwd()), {
|
|
726
|
-
forced: options.forced,
|
|
727
1023
|
});
|
|
728
1024
|
}
|
|
1025
|
+
catch (error) {
|
|
1026
|
+
log.error(formatErrorMessage(error));
|
|
1027
|
+
process.exit(1);
|
|
1028
|
+
}
|
|
729
1029
|
});
|
|
730
1030
|
// === ADD-ON SUBCOMMAND ===
|
|
731
1031
|
const addOnCommand = program.command('add-on');
|
|
@@ -733,19 +1033,43 @@ Remove your node_modules directory and package lock file and re-install.`);
|
|
|
733
1033
|
.command('init')
|
|
734
1034
|
.description('Initialize an add-on from the current project')
|
|
735
1035
|
.action(async () => {
|
|
736
|
-
|
|
1036
|
+
try {
|
|
1037
|
+
await runWithTelemetry('add-on:init', {}, async () => {
|
|
1038
|
+
await initAddOn(environment);
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
catch (error) {
|
|
1042
|
+
log.error(formatErrorMessage(error));
|
|
1043
|
+
process.exit(1);
|
|
1044
|
+
}
|
|
737
1045
|
});
|
|
738
1046
|
addOnCommand
|
|
739
1047
|
.command('compile')
|
|
740
1048
|
.description('Update add-on from the current project')
|
|
741
1049
|
.action(async () => {
|
|
742
|
-
|
|
1050
|
+
try {
|
|
1051
|
+
await runWithTelemetry('add-on:compile', {}, async () => {
|
|
1052
|
+
await compileAddOn(environment);
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
catch (error) {
|
|
1056
|
+
log.error(formatErrorMessage(error));
|
|
1057
|
+
process.exit(1);
|
|
1058
|
+
}
|
|
743
1059
|
});
|
|
744
1060
|
addOnCommand
|
|
745
1061
|
.command('dev')
|
|
746
1062
|
.description('Watch project files and continuously refresh .add-on and add-on.json')
|
|
747
1063
|
.action(async () => {
|
|
748
|
-
|
|
1064
|
+
try {
|
|
1065
|
+
await runWithTelemetry('add-on:dev', {}, async () => {
|
|
1066
|
+
await devAddOn(environment);
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
catch (error) {
|
|
1070
|
+
log.error(formatErrorMessage(error));
|
|
1071
|
+
process.exit(1);
|
|
1072
|
+
}
|
|
749
1073
|
});
|
|
750
1074
|
// === TEMPLATE SUBCOMMAND ===
|
|
751
1075
|
const templateCommand = program.command('template');
|
|
@@ -753,13 +1077,29 @@ Remove your node_modules directory and package lock file and re-install.`);
|
|
|
753
1077
|
.command('init')
|
|
754
1078
|
.description('Initialize a project template from the current project')
|
|
755
1079
|
.action(async () => {
|
|
756
|
-
|
|
1080
|
+
try {
|
|
1081
|
+
await runWithTelemetry('template:init', {}, async () => {
|
|
1082
|
+
await initStarter(environment);
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
catch (error) {
|
|
1086
|
+
log.error(formatErrorMessage(error));
|
|
1087
|
+
process.exit(1);
|
|
1088
|
+
}
|
|
757
1089
|
});
|
|
758
1090
|
templateCommand
|
|
759
1091
|
.command('compile')
|
|
760
1092
|
.description('Compile the template JSON file for the current project')
|
|
761
1093
|
.action(async () => {
|
|
762
|
-
|
|
1094
|
+
try {
|
|
1095
|
+
await runWithTelemetry('template:compile', {}, async () => {
|
|
1096
|
+
await compileStarter(environment);
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
catch (error) {
|
|
1100
|
+
log.error(formatErrorMessage(error));
|
|
1101
|
+
process.exit(1);
|
|
1102
|
+
}
|
|
763
1103
|
});
|
|
764
1104
|
// Legacy alias for template command
|
|
765
1105
|
const starterCommand = program.command('starter');
|
|
@@ -767,13 +1107,29 @@ Remove your node_modules directory and package lock file and re-install.`);
|
|
|
767
1107
|
.command('init')
|
|
768
1108
|
.description('Deprecated alias: initialize a project template')
|
|
769
1109
|
.action(async () => {
|
|
770
|
-
|
|
1110
|
+
try {
|
|
1111
|
+
await runWithTelemetry('starter:init', {}, async () => {
|
|
1112
|
+
await initStarter(environment);
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
catch (error) {
|
|
1116
|
+
log.error(formatErrorMessage(error));
|
|
1117
|
+
process.exit(1);
|
|
1118
|
+
}
|
|
771
1119
|
});
|
|
772
1120
|
starterCommand
|
|
773
1121
|
.command('compile')
|
|
774
1122
|
.description('Deprecated alias: compile the template JSON file')
|
|
775
1123
|
.action(async () => {
|
|
776
|
-
|
|
1124
|
+
try {
|
|
1125
|
+
await runWithTelemetry('starter:compile', {}, async () => {
|
|
1126
|
+
await compileStarter(environment);
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
catch (error) {
|
|
1130
|
+
log.error(formatErrorMessage(error));
|
|
1131
|
+
process.exit(1);
|
|
1132
|
+
}
|
|
777
1133
|
});
|
|
778
1134
|
// === LEGACY AUTO-CREATE MODE ===
|
|
779
1135
|
// For backward compatibility with cli-aliases (create-tsrouter-app, etc.)
|