@tanstack/cta-engine 0.10.0-alpha.6 → 0.11.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.
@@ -1,6 +1,8 @@
1
1
  import type { Environment, Options } from './types.js';
2
- export declare function createApp(options: Options, { silent, environment, cwd, }: {
2
+ export declare function createApp(options: Options, { silent, environment, cwd, appName, }: {
3
3
  silent?: boolean;
4
4
  environment: Environment;
5
5
  cwd?: string;
6
+ name?: string;
7
+ appName?: string;
6
8
  }): Promise<void>;
@@ -0,0 +1,2 @@
1
+ export declare function readFileHelper(path: string): string;
2
+ export declare function getBinaryFile(content: string): string | null;
@@ -1 +1,7 @@
1
- export default function runServer(sse: boolean): Promise<void>;
1
+ import type { TemplateOptions } from './types.js';
2
+ export default function runServer(sse: boolean, { forcedAddOns, appName, name, }: {
3
+ forcedMode?: TemplateOptions;
4
+ forcedAddOns?: Array<string>;
5
+ appName?: string;
6
+ name?: string;
7
+ }): Promise<void>;
@@ -1,3 +1,6 @@
1
1
  import type { CliOptions, Options } from './types.js';
2
- export declare function normalizeOptions(cliOptions: CliOptions): Promise<Options | undefined>;
3
- export declare function promptForOptions(cliOptions: CliOptions): Promise<Required<Options>>;
2
+ export declare function normalizeOptions(cliOptions: CliOptions, forcedAddOns?: Array<string>): Promise<Options | undefined>;
3
+ export declare function promptForOptions(cliOptions: CliOptions, { forcedAddOns, forcedMode, }: {
4
+ forcedAddOns?: Array<string>;
5
+ forcedMode?: 'typescript' | 'javascript' | 'file-router';
6
+ }): Promise<Required<Options>>;
@@ -2,6 +2,7 @@ import type { CODE_ROUTER, FILE_ROUTER } from './constants.js';
2
2
  import type { PackageManager } from './package-manager.js';
3
3
  import type { ToolChain } from './toolchain.js';
4
4
  export type Framework = 'solid' | 'react';
5
+ export type TemplateOptions = 'typescript' | 'javascript' | 'file-router';
5
6
  export interface Options {
6
7
  framework: Framework;
7
8
  projectName: string;
@@ -17,7 +18,7 @@ export interface Options {
17
18
  overlay?: AddOn | undefined;
18
19
  }
19
20
  export interface CliOptions {
20
- template?: 'typescript' | 'javascript' | 'file-router';
21
+ template?: TemplateOptions;
21
22
  framework?: Framework;
22
23
  tailwind?: boolean;
23
24
  packageManager?: PackageManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/cta-engine",
3
- "version": "0.10.0-alpha.6",
3
+ "version": "0.11.0",
4
4
  "description": "Tanstack Application Builder Engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/add-ons.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import { readFile } from 'node:fs/promises'
2
- import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
2
+ import { existsSync, readdirSync, statSync } from 'node:fs'
3
3
  import { resolve } from 'node:path'
4
4
  import chalk from 'chalk'
5
- import { getTemplatesRoot } from './templates.js'
6
5
 
6
+ import { getTemplatesRoot } from './templates.js'
7
7
  import { DEFAULT_FRAMEWORK } from './constants.js'
8
- import type { AddOn, CliOptions, Framework } from './types.js'
8
+ import { readFileHelper } from './file-helper.js'
9
+
10
+ import type { AddOn, CliOptions, Framework, TemplateOptions } from './types.js'
9
11
 
10
12
  function isDirectory(path: string): boolean {
11
13
  return statSync(path).isDirectory()
@@ -18,7 +20,7 @@ function findFilesRecursively(path: string, files: Record<string, string>) {
18
20
  if (isDirectory(filePath)) {
19
21
  findFilesRecursively(filePath, files)
20
22
  } else {
21
- files[filePath] = readFileSync(filePath, 'utf-8').toString()
23
+ files[filePath] = readFileHelper(filePath)
22
24
  }
23
25
  }
24
26
  }
@@ -121,14 +123,21 @@ export async function finalizeAddOns(
121
123
  return finalAddOns
122
124
  }
123
125
 
124
- export async function listAddOns(options: CliOptions) {
125
- const mode =
126
- options.template === 'file-router' ? 'file-router' : 'code-router'
126
+ export async function listAddOns(
127
+ options: CliOptions,
128
+ {
129
+ forcedMode,
130
+ forcedAddOns = [],
131
+ }: {
132
+ forcedMode?: TemplateOptions
133
+ forcedAddOns?: Array<string>
134
+ },
135
+ ) {
127
136
  const addOns = await getAllAddOns(
128
137
  options.framework || DEFAULT_FRAMEWORK,
129
- mode,
138
+ forcedMode || options.template || 'typescript',
130
139
  )
131
- for (const addOn of addOns) {
140
+ for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
132
141
  console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`)
133
142
  }
134
143
  }
package/src/add.ts CHANGED
@@ -25,7 +25,7 @@ import { readConfigFile, writeConfigFile } from './config-file.js'
25
25
 
26
26
  import type { PersistedOptions } from './config-file.js'
27
27
 
28
- import type { Options } from './types.js'
28
+ import type { Framework, Options } from './types.js'
29
29
 
30
30
  function isDirectory(path: string) {
31
31
  return statSync(path).isDirectory()
@@ -43,10 +43,11 @@ async function createOptions(
43
43
  return {
44
44
  ...json,
45
45
  tailwind: true,
46
- chosenAddOns: await finalizeAddOns(json.framework!, json.mode!, [
47
- ...json.existingAddOns,
48
- ...addOns,
49
- ]),
46
+ chosenAddOns: await finalizeAddOns(
47
+ json.framework as Framework,
48
+ json.mode as string,
49
+ [...json.existingAddOns, ...addOns],
50
+ ),
50
51
  } as Required<Options>
51
52
  }
52
53
 
@@ -56,6 +57,7 @@ async function runCreateApp(options: Required<Options>) {
56
57
  silent: true,
57
58
  environment,
58
59
  cwd: process.cwd(),
60
+ name: 'create-tsrouter-app',
59
61
  })
60
62
  return output
61
63
  }
package/src/cli.ts CHANGED
@@ -18,12 +18,20 @@ import type { PackageManager } from './package-manager.js'
18
18
  import type { ToolChain } from './toolchain.js'
19
19
  import type { CliOptions, Framework } from './types.js'
20
20
 
21
- export function cli() {
21
+ export function cli({
22
+ name,
23
+ appName,
24
+ forcedMode,
25
+ forcedAddOns,
26
+ }: {
27
+ name: string
28
+ appName: string
29
+ forcedMode?: 'typescript' | 'javascript' | 'file-router'
30
+ forcedAddOns?: Array<string>
31
+ }) {
22
32
  const program = new Command()
23
33
 
24
- program
25
- .name('create-tsrouter-app')
26
- .description('CLI to create a new TanStack application')
34
+ program.name(name).description(`CLI to create a new ${appName} application`)
27
35
 
28
36
  // program
29
37
  // .command('add')
@@ -46,26 +54,10 @@ export function cli() {
46
54
  // await initAddOn('overlay')
47
55
  // })
48
56
 
49
- program // 104 22
50
- .argument('[project-name]', 'name of the project')
51
- .option('--no-git', 'do not create a git repository')
52
- .option('--target-dir <path>', 'the directory to create the project in')
53
- .option<Framework>(
54
- '--framework <type>',
55
- 'project framework (solid, react)',
56
- (value) => {
57
- if (!SUPPORTED_FRAMEWORKS.includes(value as Framework)) {
58
- throw new InvalidArgumentError(
59
- `Invalid framework: ${value}. Only the following are allowed: ${SUPPORTED_FRAMEWORKS.join(
60
- ', ',
61
- )}`,
62
- )
63
- }
64
- return value as Framework
65
- },
66
- DEFAULT_FRAMEWORK,
67
- )
68
- .option<'typescript' | 'javascript' | 'file-router'>(
57
+ program.argument('[project-name]', 'name of the project')
58
+
59
+ if (!forcedMode) {
60
+ program.option<'typescript' | 'javascript' | 'file-router'>(
69
61
  '--template <type>',
70
62
  'project template (typescript, javascript, file-router)',
71
63
  (value) => {
@@ -81,6 +73,24 @@ export function cli() {
81
73
  return value
82
74
  },
83
75
  )
76
+ }
77
+ program
78
+ .option<Framework>(
79
+ '--framework <type>',
80
+ 'project framework (solid, react)',
81
+ (value) => {
82
+ if (!SUPPORTED_FRAMEWORKS.includes(value as Framework)) {
83
+ throw new InvalidArgumentError(
84
+ `Invalid framework: ${value}. Only the following are allowed: ${SUPPORTED_FRAMEWORKS.join(
85
+ ', ',
86
+ )}`,
87
+ )
88
+ }
89
+ return value as Framework
90
+ },
91
+ DEFAULT_FRAMEWORK,
92
+ )
93
+ // .option('--overlay [url]', 'add an overlay from a URL', false)
84
94
  .option<PackageManager>(
85
95
  `--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`,
86
96
  `Explicitly tell the CLI to use this package manager`,
@@ -122,42 +132,58 @@ export function cli() {
122
132
  },
123
133
  )
124
134
  .option('--list-add-ons', 'list all available add-ons', false)
125
- .option('--overlay [url]', 'add an overlay from a URL', false)
135
+ .option('--no-git', 'do not create a git repository')
136
+ .option('--target-dir <path>', 'the directory to create the project in')
126
137
  .option('--mcp', 'run the MCP server', false)
127
138
  .option('--mcp-sse', 'run the MCP server in SSE mode', false)
128
- .action(async (projectName: string, options: CliOptions) => {
129
- if (options.listAddOns) {
130
- await listAddOns(options)
131
- } else if (options.mcp || options.mcpSse) {
132
- await runServer(!!options.mcpSse)
133
- } else {
134
- try {
135
- const cliOptions = {
136
- projectName,
137
- ...options,
138
- } as CliOptions
139
139
 
140
- let finalOptions = await normalizeOptions(cliOptions)
141
- if (finalOptions) {
142
- intro(`Creating a new TanStack app in ${projectName}...`)
143
- } else {
144
- intro("Let's configure your TanStack application")
145
- finalOptions = await promptForOptions(cliOptions)
146
- }
147
- await createApp(finalOptions, {
148
- environment: createDefaultEnvironment(),
149
- cwd: options.targetDir || undefined,
140
+ program.action(async (projectName: string, options: CliOptions) => {
141
+ if (options.listAddOns) {
142
+ await listAddOns(options, {
143
+ forcedMode,
144
+ forcedAddOns,
145
+ })
146
+ } else if (options.mcp || options.mcpSse) {
147
+ await runServer(!!options.mcpSse, {
148
+ forcedMode,
149
+ forcedAddOns,
150
+ appName,
151
+ })
152
+ } else {
153
+ try {
154
+ const cliOptions = {
155
+ projectName,
156
+ ...options,
157
+ } as CliOptions
158
+
159
+ if (forcedMode) {
160
+ cliOptions.template = forcedMode
161
+ }
162
+
163
+ let finalOptions = await normalizeOptions(cliOptions, forcedAddOns)
164
+ if (finalOptions) {
165
+ intro(`Creating a new ${appName} app in ${projectName}...`)
166
+ } else {
167
+ intro(`Let's configure your ${appName} application`)
168
+ finalOptions = await promptForOptions(cliOptions, {
169
+ forcedMode,
170
+ forcedAddOns,
150
171
  })
151
- } catch (error) {
152
- log.error(
153
- error instanceof Error
154
- ? error.message
155
- : 'An unknown error occurred',
156
- )
157
- process.exit(1)
158
172
  }
173
+ await createApp(finalOptions, {
174
+ environment: createDefaultEnvironment(),
175
+ cwd: options.targetDir || undefined,
176
+ name,
177
+ appName,
178
+ })
179
+ } catch (error) {
180
+ log.error(
181
+ error instanceof Error ? error.message : 'An unknown error occurred',
182
+ )
183
+ process.exit(1)
159
184
  }
160
- })
185
+ }
186
+ })
161
187
 
162
188
  program.parse()
163
189
  }
package/src/create-app.ts CHANGED
@@ -9,6 +9,7 @@ import { CODE_ROUTER, FILE_ROUTER } from './constants.js'
9
9
  import { sortObject } from './utils.js'
10
10
  import { writeConfigFile } from './config-file.js'
11
11
  import { packageManagerExecute } from './package-manager.js'
12
+ import { getBinaryFile } from './file-helper.js'
12
13
 
13
14
  import type { AddOn, Environment, Options } from './types.js'
14
15
 
@@ -287,6 +288,15 @@ async function copyAddOnFile(
287
288
 
288
289
  const finalTargetPath = resolve(dirname(targetPath), targetFile)
289
290
 
291
+ const binaryContent = getBinaryFile(content)
292
+ if (binaryContent) {
293
+ await environment.writeFile(
294
+ finalTargetPath,
295
+ binaryContent as unknown as string,
296
+ )
297
+ return
298
+ }
299
+
290
300
  if (isTemplate) {
291
301
  await templateFile(content, finalTargetPath)
292
302
  } else {
@@ -304,10 +314,13 @@ export async function createApp(
304
314
  silent = false,
305
315
  environment,
306
316
  cwd,
317
+ appName = 'TanStack',
307
318
  }: {
308
319
  silent?: boolean
309
320
  environment: Environment
310
321
  cwd?: string
322
+ name?: string
323
+ appName?: string
311
324
  },
312
325
  ) {
313
326
  environment.startRun()
@@ -496,7 +509,7 @@ export async function createApp(
496
509
  // Copy all the asset files from the addons
497
510
  const s = silent ? null : spinner()
498
511
  for (const type of ['add-on', 'example']) {
499
- for (const phase of ['setup', 'add-on']) {
512
+ for (const phase of ['setup', 'add-on', 'example']) {
500
513
  for (const addOn of options.chosenAddOns.filter(
501
514
  (addOn) => addOn.phase === phase && addOn.type === type,
502
515
  )) {
@@ -780,7 +793,7 @@ ${environment.getErrors().join('\n')}`
780
793
  startCommand = `deno ${isAddOnEnabled('start') ? 'task dev' : 'start'}`
781
794
  }
782
795
 
783
- outro(`Your TanStack app is ready in '${basename(targetDir)}'.
796
+ outro(`Your ${appName} app is ready in '${basename(targetDir)}'.
784
797
 
785
798
  Use the following commands to start your app:
786
799
  % cd ${options.projectName}
@@ -7,8 +7,9 @@ import { createMemoryEnvironment } from './environment.js'
7
7
  import { createApp } from './create-app.js'
8
8
  import { readConfigFile } from './config-file.js'
9
9
  import { finalizeAddOns } from './add-ons.js'
10
+ import { readFileHelper } from './file-helper.js'
10
11
 
11
- import type { Options } from './types.js'
12
+ import type { Framework, Options } from './types.js'
12
13
  import type { PersistedOptions } from './config-file.js'
13
14
 
14
15
  type AddOnMode = 'add-on' | 'overlay'
@@ -106,9 +107,11 @@ async function createOptions(
106
107
  ): Promise<Required<Options>> {
107
108
  return {
108
109
  ...json,
109
- chosenAddOns: await finalizeAddOns(json.framework!, json.mode!, [
110
- ...json.existingAddOns,
111
- ]),
110
+ chosenAddOns: await finalizeAddOns(
111
+ json.framework as Framework,
112
+ json.mode as string,
113
+ [...json.existingAddOns],
114
+ ),
112
115
  } as Required<Options>
113
116
  }
114
117
 
@@ -118,6 +121,7 @@ async function runCreateApp(options: Required<Options>) {
118
121
  silent: true,
119
122
  environment,
120
123
  cwd: process.cwd(),
124
+ name: 'create-tsrouter-app',
121
125
  })
122
126
  return output
123
127
  }
@@ -131,9 +135,7 @@ async function recursivelyGatherFiles(
131
135
  if (file.isDirectory()) {
132
136
  await recursivelyGatherFiles(resolve(path, file.name), files)
133
137
  } else {
134
- files[resolve(path, file.name)] = (
135
- await readFile(resolve(path, file.name))
136
- ).toString()
138
+ files[resolve(path, file.name)] = readFileHelper(resolve(path, file.name))
137
139
  }
138
140
  }
139
141
  }
@@ -0,0 +1,20 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { extname } from 'node:path'
3
+
4
+ const BINARY_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico']
5
+
6
+ export function readFileHelper(path: string): string {
7
+ if (BINARY_EXTENSIONS.includes(extname(path))) {
8
+ return `base64::${readFileSync(path).toString('base64')}`
9
+ } else {
10
+ return readFileSync(path, 'utf-8').toString()
11
+ }
12
+ }
13
+
14
+ export function getBinaryFile(content: string): string | null {
15
+ if (content.startsWith('base64::')) {
16
+ const binaryContent = Buffer.from(content.replace('base64::', ''), 'base64')
17
+ return binaryContent as unknown as string
18
+ }
19
+ return null
20
+ }