@tanstack/cta-engine 0.28.0 → 0.29.1

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/add-ons.js CHANGED
@@ -33,3 +33,16 @@ export async function finalizeAddOns(framework, mode, chosenAddOnIDs) {
33
33
  function loadAddOn(addOn) {
34
34
  return addOn;
35
35
  }
36
+ export function populateAddOnOptionsDefaults(chosenAddOns) {
37
+ const addOnOptions = {};
38
+ for (const addOn of chosenAddOns) {
39
+ if (addOn.options) {
40
+ const defaults = {};
41
+ for (const [optionKey, optionDef] of Object.entries(addOn.options)) {
42
+ defaults[optionKey] = optionDef.default;
43
+ }
44
+ addOnOptions[addOn.id] = defaults;
45
+ }
46
+ }
47
+ return addOnOptions;
48
+ }
@@ -108,7 +108,9 @@ export async function runNewCommands(environment, originalOptions, cwd, output)
108
108
  type: 'command',
109
109
  message: `Running ${formatCommand({ command: command.command, args: command.args })}...`,
110
110
  });
111
- await environment.execute(command.command, command.args, cwd);
111
+ await environment.execute(command.command, command.args, cwd, {
112
+ inherit: true,
113
+ });
112
114
  environment.finishStep('run-commands', 'Setup commands complete');
113
115
  }
114
116
  }
@@ -158,6 +160,18 @@ export async function addToApp(environment, addOns, cwd, options) {
158
160
  await packageManagerInstall(environment, newOptions.targetDir, newOptions.packageManager);
159
161
  s.stop(`Installed dependencies`);
160
162
  environment.finishStep('install-dependencies', 'Dependencies installed');
163
+ // Run any post-init special steps for the new add-ons
164
+ const postInitSpecialSteps = new Set([]);
165
+ for (const addOn of newOptions.chosenAddOns) {
166
+ for (const step of addOn.postInitSpecialSteps || []) {
167
+ if (addOns.includes(addOn.id)) {
168
+ postInitSpecialSteps.add(step);
169
+ }
170
+ }
171
+ }
172
+ if (postInitSpecialSteps.size) {
173
+ await runSpecialSteps(environment, newOptions, Array.from(postInitSpecialSteps));
174
+ }
161
175
  // Handle new commands
162
176
  await runNewCommands(environment, persistedOptions, cwd, output);
163
177
  environment.startStep({
@@ -109,6 +109,16 @@ async function runCommandsAndInstallDependencies(environment, options) {
109
109
  await packageManagerInstall(environment, options.targetDir, options.packageManager);
110
110
  environment.finishStep('install-dependencies', 'Installed dependencies');
111
111
  s.stop(`Installed dependencies`);
112
+ // Run any post-init special steps for the new add-ons
113
+ const postInitSpecialSteps = new Set([]);
114
+ for (const addOn of options.chosenAddOns) {
115
+ for (const step of addOn.postInitSpecialSteps || []) {
116
+ postInitSpecialSteps.add(step);
117
+ }
118
+ }
119
+ if (postInitSpecialSteps.size) {
120
+ await runSpecialSteps(environment, options, Array.from(postInitSpecialSteps));
121
+ }
112
122
  for (const phase of ['setup', 'add-on', 'example']) {
113
123
  for (const addOn of options.chosenAddOns.filter((addOn) => addOn.phase === phase && addOn.command && addOn.command.command)) {
114
124
  s.start(`Running commands for ${addOn.name}...`);
@@ -121,7 +131,7 @@ async function runCommandsAndInstallDependencies(environment, options) {
121
131
  type: 'command',
122
132
  message: cmd,
123
133
  });
124
- await environment.execute(addOn.command.command, addOn.command.args || [], options.targetDir);
134
+ await environment.execute(addOn.command.command, addOn.command.args || [], options.targetDir, { inherit: true });
125
135
  environment.finishStep('run-commands', 'Setup commands complete');
126
136
  s.stop(`${addOn.name} commands complete`);
127
137
  }
@@ -140,7 +150,7 @@ async function runCommandsAndInstallDependencies(environment, options) {
140
150
  type: 'command',
141
151
  message: cmd,
142
152
  });
143
- await environment.execute(options.starter.command.command, options.starter.command.args || [], options.targetDir);
153
+ await environment.execute(options.starter.command.command, options.starter.command.args || [], options.targetDir, { inherit: true });
144
154
  environment.finishStep('run-starter-command', 'Starter command complete');
145
155
  s.stop(`${options.starter.name} commands complete`);
146
156
  }
@@ -172,7 +182,7 @@ Use the following commands to start your app:
172
182
  % cd ${options.projectName}
173
183
  % ${formatCommand(getPackageManagerScriptCommand(options.packageManager, ['dev']))}
174
184
 
175
- Please read the README.md for information on testing, styling, adding routes, etc.${errorStatement}`);
185
+ Please check the README.md for information on testing, styling, adding routes, etc.${errorStatement}`);
176
186
  }
177
187
  export async function createApp(environment, options) {
178
188
  environment.startRun();
@@ -2,7 +2,7 @@ import { readdir } from 'node:fs/promises';
2
2
  import { resolve } from 'node:path';
3
3
  import { createApp } from '../create-app.js';
4
4
  import { createMemoryEnvironment } from '../environment.js';
5
- import { finalizeAddOns } from '../add-ons.js';
5
+ import { finalizeAddOns, populateAddOnOptionsDefaults } from '../add-ons.js';
6
6
  import { getFrameworkById } from '../frameworks.js';
7
7
  import { readConfigFileFromEnvironment } from '../config-file.js';
8
8
  import { readFileHelper } from '../file-helpers.js';
@@ -44,6 +44,9 @@ export async function createAppOptionsFromPersisted(json) {
44
44
  const { version, ...rest } = json;
45
45
  /* eslint-enable unused-imports/no-unused-vars */
46
46
  const framework = getFrameworkById(rest.framework);
47
+ const chosenAddOns = await finalizeAddOns(framework, json.mode, [
48
+ ...json.chosenAddOns,
49
+ ]);
47
50
  return {
48
51
  ...rest,
49
52
  mode: json.mode,
@@ -55,9 +58,8 @@ export async function createAppOptionsFromPersisted(json) {
55
58
  targetDir: '',
56
59
  framework: framework,
57
60
  starter: json.starter ? await loadStarter(json.starter) : undefined,
58
- chosenAddOns: await finalizeAddOns(framework, json.mode, [
59
- ...json.chosenAddOns,
60
- ]),
61
+ chosenAddOns,
62
+ addOnOptions: populateAddOnOptionsDefaults(chosenAddOns),
61
63
  };
62
64
  }
63
65
  export function createSerializedOptionsFromPersisted(json) {
@@ -75,6 +77,7 @@ export function createSerializedOptionsFromPersisted(json) {
75
77
  targetDir: '',
76
78
  framework: json.framework,
77
79
  starter: json.starter,
80
+ addOnOptions: {},
78
81
  };
79
82
  }
80
83
  export async function runCreateApp(options) {
@@ -29,12 +29,23 @@ export function createDefaultEnvironment() {
29
29
  await mkdir(dirname(path), { recursive: true });
30
30
  return writeFile(path, getBinaryFile(base64Contents));
31
31
  },
32
- execute: async (command, args, cwd) => {
32
+ execute: async (command, args, cwd, options) => {
33
33
  try {
34
- const result = await execa(command, args, {
35
- cwd,
36
- });
37
- return { stdout: result.stdout };
34
+ if (options?.inherit) {
35
+ // For commands that should show output directly to the user
36
+ await execa(command, args, {
37
+ cwd,
38
+ stdio: 'inherit',
39
+ });
40
+ return { stdout: '' };
41
+ }
42
+ else {
43
+ // For commands where we need to capture output
44
+ const result = await execa(command, args, {
45
+ cwd,
46
+ });
47
+ return { stdout: result.stdout };
48
+ }
38
49
  }
39
50
  catch {
40
51
  errors.push(`Command "${command} ${args.join(' ')}" did not run successfully. Please run this manually in your project.`);
@@ -29,9 +29,13 @@ export function scanAddOnDirectories(addOnsDirectories) {
29
29
  const fileContent = readFileSync(filePath, 'utf-8');
30
30
  const info = JSON.parse(fileContent);
31
31
  let packageAdditions = {};
32
+ let packageTemplate = undefined;
32
33
  if (existsSync(resolve(addOnsBase, dir, 'package.json'))) {
33
34
  packageAdditions = JSON.parse(readFileSync(resolve(addOnsBase, dir, 'package.json'), 'utf-8'));
34
35
  }
36
+ else if (existsSync(resolve(addOnsBase, dir, 'package.json.ejs'))) {
37
+ packageTemplate = readFileSync(resolve(addOnsBase, dir, 'package.json.ejs'), 'utf-8');
38
+ }
35
39
  let readme;
36
40
  if (existsSync(resolve(addOnsBase, dir, 'README.md'))) {
37
41
  readme = readFileSync(resolve(addOnsBase, dir, 'README.md'), 'utf-8');
@@ -59,6 +63,7 @@ export function scanAddOnDirectories(addOnsDirectories) {
59
63
  ...info,
60
64
  id: dir,
61
65
  packageAdditions,
66
+ packageTemplate,
62
67
  readme,
63
68
  files,
64
69
  smallLogo,
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { createApp } from './create-app.js';
2
2
  export { addToApp } from './add-to-app.js';
3
- export { finalizeAddOns, getAllAddOns } from './add-ons.js';
3
+ export { finalizeAddOns, getAllAddOns, populateAddOnOptionsDefaults } from './add-ons.js';
4
4
  export { loadRemoteAddOn } from './custom-add-ons/add-on.js';
5
5
  export { loadStarter } from './custom-add-ons/starter.js';
6
6
  export { createMemoryEnvironment, createDefaultEnvironment, } from './environment.js';
@@ -1,7 +1,9 @@
1
+ import { render } from 'ejs';
1
2
  import { sortObject } from './utils.js';
2
3
  export function mergePackageJSON(packageJSON, overlayPackageJSON) {
3
4
  return {
4
5
  ...packageJSON,
6
+ ...(overlayPackageJSON || {}),
5
7
  dependencies: {
6
8
  ...packageJSON.dependencies,
7
9
  ...(overlayPackageJSON?.dependencies || {}),
@@ -33,8 +35,36 @@ export function createPackageJSON(options) {
33
35
  for (const addition of additions.filter(Boolean)) {
34
36
  packageJSON = mergePackageJSON(packageJSON, addition);
35
37
  }
36
- for (const addOn of options.chosenAddOns.map((addOn) => addOn.packageAdditions)) {
37
- packageJSON = mergePackageJSON(packageJSON, addOn);
38
+ for (const addOn of options.chosenAddOns) {
39
+ let addOnPackageJSON = addOn.packageAdditions;
40
+ // Process EJS template if present
41
+ if (addOn.packageTemplate) {
42
+ const templateValues = {
43
+ packageManager: options.packageManager,
44
+ projectName: options.projectName,
45
+ typescript: options.typescript,
46
+ tailwind: options.tailwind,
47
+ js: options.typescript ? 'ts' : 'js',
48
+ jsx: options.typescript ? 'tsx' : 'jsx',
49
+ fileRouter: options.mode === 'file-router',
50
+ codeRouter: options.mode === 'code-router',
51
+ addOnEnabled: options.chosenAddOns.reduce((acc, addon) => {
52
+ acc[addon.id] = true;
53
+ return acc;
54
+ }, {}),
55
+ addOnOption: options.addOnOptions,
56
+ addOns: options.chosenAddOns,
57
+ };
58
+ try {
59
+ const renderedTemplate = render(addOn.packageTemplate, templateValues);
60
+ addOnPackageJSON = JSON.parse(renderedTemplate);
61
+ }
62
+ catch (error) {
63
+ console.error(`Error processing package.json.ejs for add-on ${addOn.id}:`, error);
64
+ // Fall back to packageAdditions if template processing fails
65
+ }
66
+ }
67
+ packageJSON = mergePackageJSON(packageJSON, addOnPackageJSON);
38
68
  }
39
69
  if (options.starter) {
40
70
  packageJSON = mergePackageJSON(packageJSON, options.starter.packageAdditions);
@@ -1,13 +1,15 @@
1
1
  import { rimrafNodeModules } from './rimraf-node-modules.js';
2
+ import { postInitScript } from './post-init-script.js';
2
3
  const specialStepsLookup = {
3
4
  'rimraf-node-modules': rimrafNodeModules,
5
+ 'post-init-script': postInitScript,
4
6
  };
5
7
  export async function runSpecialSteps(environment, options, specialSteps) {
6
8
  if (specialSteps.length) {
7
9
  environment.startStep({
8
10
  id: 'special-steps',
9
11
  type: 'command',
10
- message: 'Removing node_modules...',
12
+ message: 'Running special steps...',
11
13
  });
12
14
  for (const step of specialSteps) {
13
15
  const stepFunction = specialStepsLookup[step];
@@ -0,0 +1,31 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { getPackageManagerScriptCommand } from '../package-manager.js';
4
+ export async function postInitScript(environment, options) {
5
+ const packageJsonPath = resolve(options.targetDir, 'package.json');
6
+ if (!environment.exists(packageJsonPath)) {
7
+ environment.warn('Warning', 'No package.json found, skipping post-cta-init script');
8
+ return;
9
+ }
10
+ try {
11
+ const packageJsonContent = readFileSync(packageJsonPath, 'utf-8');
12
+ const packageJson = JSON.parse(packageJsonContent);
13
+ if (!packageJson.scripts || !packageJson.scripts['post-cta-init']) {
14
+ // No post-cta-init script found, skip silently
15
+ return;
16
+ }
17
+ environment.startStep({
18
+ id: 'post-init-script',
19
+ type: 'command',
20
+ message: 'Running post-cta-init script...',
21
+ });
22
+ const { command, args } = getPackageManagerScriptCommand(options.packageManager, ['post-cta-init']);
23
+ await environment.execute(command, args, options.targetDir, {
24
+ inherit: true,
25
+ });
26
+ environment.finishStep('post-init-script', 'Post-cta-init script complete');
27
+ }
28
+ catch (error) {
29
+ environment.error(`Failed to run post-cta-init script: ${error instanceof Error ? error.message : String(error)}`);
30
+ }
31
+ }
@@ -59,6 +59,7 @@ export function createTemplateFile(environment, options) {
59
59
  fileRouter: options.mode === 'file-router',
60
60
  codeRouter: options.mode === 'code-router',
61
61
  addOnEnabled,
62
+ addOnOption: options.addOnOptions,
62
63
  addOns: options.chosenAddOns,
63
64
  integrations,
64
65
  routes,
@@ -91,6 +92,12 @@ export function createTemplateFile(environment, options) {
91
92
  return;
92
93
  }
93
94
  let target = convertDotFilesAndPaths(file.replace('.ejs', ''));
95
+ // Strip option prefixes from filename (e.g., __postgres__schema.prisma -> schema.prisma)
96
+ const prefixMatch = target.match(/^(.+\/)?__([^_]+)__(.+)$/);
97
+ if (prefixMatch) {
98
+ const [, directory, , filename] = prefixMatch;
99
+ target = (directory || '') + filename;
100
+ }
94
101
  let append = false;
95
102
  if (target.endsWith('.append')) {
96
103
  append = true;
@@ -1,3 +1,4 @@
1
1
  import type { AddOn, Framework } from './types.js';
2
2
  export declare function getAllAddOns(framework: Framework, mode: string): Array<AddOn>;
3
3
  export declare function finalizeAddOns(framework: Framework, mode: string, chosenAddOnIDs: Array<string>): Promise<Array<AddOn>>;
4
+ export declare function populateAddOnOptionsDefaults(chosenAddOns: Array<AddOn>): Record<string, Record<string, any>>;
@@ -12,16 +12,27 @@ export declare function validateAddOnSetup(environment: Environment): Promise<vo
12
12
  export declare function readOrGenerateAddOnInfo(options: PersistedOptions): Promise<AddOnInfo>;
13
13
  export declare function generateProject(persistedOptions: PersistedOptions): Promise<{
14
14
  info: {
15
+ type: "add-on" | "example" | "starter" | "toolchain" | "host";
16
+ description: string;
15
17
  id: string;
16
18
  name: string;
17
- description: string;
18
- type: "add-on" | "example" | "starter" | "toolchain" | "host";
19
19
  modes: string[];
20
20
  phase: "add-on" | "setup";
21
21
  command?: {
22
22
  command: string;
23
23
  args?: string[] | undefined;
24
24
  } | undefined;
25
+ options?: Record<string, {
26
+ type: "select";
27
+ options: {
28
+ value: string;
29
+ label: string;
30
+ }[];
31
+ label: string;
32
+ default: string;
33
+ description?: string | undefined;
34
+ }> | undefined;
35
+ default?: boolean | undefined;
25
36
  author?: string | undefined;
26
37
  version?: string | undefined;
27
38
  link?: string | undefined;
@@ -44,7 +55,7 @@ export declare function generateProject(persistedOptions: PersistedOptions): Pro
44
55
  logo?: string | undefined;
45
56
  addOnSpecialSteps?: string[] | undefined;
46
57
  createSpecialSteps?: string[] | undefined;
47
- default?: boolean | undefined;
58
+ postInitSpecialSteps?: string[] | undefined;
48
59
  integrations?: {
49
60
  type?: string | undefined;
50
61
  code?: string | undefined;
@@ -1,6 +1,6 @@
1
1
  export { createApp } from './create-app.js';
2
2
  export { addToApp } from './add-to-app.js';
3
- export { finalizeAddOns, getAllAddOns } from './add-ons.js';
3
+ export { finalizeAddOns, getAllAddOns, populateAddOnOptionsDefaults } from './add-ons.js';
4
4
  export { loadRemoteAddOn } from './custom-add-ons/add-on.js';
5
5
  export { loadStarter } from './custom-add-ons/starter.js';
6
6
  export { createMemoryEnvironment, createDefaultEnvironment, } from './environment.js';
@@ -16,6 +16,6 @@ export { createAppOptionsFromPersisted, createSerializedOptionsFromPersisted, }
16
16
  export { createSerializedOptions } from './options.js';
17
17
  export { getRawRegistry, getRegistry, getRegistryAddOns, getRegistryStarters, } from './registry.js';
18
18
  export { StarterCompiledSchema, StatusEvent, StatusStepType, StopEvent, AddOnCompiledSchema, AddOnInfoSchema, IntegrationSchema, } from './types.js';
19
- export type { AddOn, Environment, FileBundleHandler, Framework, FrameworkDefinition, Options, SerializedOptions, Starter, StarterCompiled, } from './types.js';
19
+ export type { AddOn, AddOnOption, AddOnOptions, AddOnSelectOption, AddOnSelection, Environment, FileBundleHandler, Framework, FrameworkDefinition, Options, SerializedOptions, Starter, StarterCompiled, } from './types.js';
20
20
  export type { PersistedOptions } from './config-file.js';
21
21
  export type { PackageManager } from './package-manager.js';
@@ -9,15 +9,15 @@ declare const registrySchema: z.ZodObject<{
9
9
  mode: z.ZodString;
10
10
  framework: z.ZodString;
11
11
  }, "strip", z.ZodTypeAny, {
12
- name: string;
13
12
  description: string;
13
+ name: string;
14
14
  url: string;
15
15
  framework: string;
16
16
  mode: string;
17
17
  banner?: string | undefined;
18
18
  }, {
19
- name: string;
20
19
  description: string;
20
+ name: string;
21
21
  url: string;
22
22
  framework: string;
23
23
  mode: string;
@@ -30,46 +30,46 @@ declare const registrySchema: z.ZodObject<{
30
30
  modes: z.ZodArray<z.ZodString, "many">;
31
31
  framework: z.ZodString;
32
32
  }, "strip", z.ZodTypeAny, {
33
- name: string;
34
33
  description: string;
34
+ name: string;
35
35
  url: string;
36
36
  framework: string;
37
37
  modes: string[];
38
38
  }, {
39
- name: string;
40
39
  description: string;
40
+ name: string;
41
41
  url: string;
42
42
  framework: string;
43
43
  modes: string[];
44
44
  }>, "many">>;
45
45
  }, "strip", z.ZodTypeAny, {
46
46
  starters?: {
47
- name: string;
48
47
  description: string;
48
+ name: string;
49
49
  url: string;
50
50
  framework: string;
51
51
  mode: string;
52
52
  banner?: string | undefined;
53
53
  }[] | undefined;
54
54
  'add-ons'?: {
55
- name: string;
56
55
  description: string;
56
+ name: string;
57
57
  url: string;
58
58
  framework: string;
59
59
  modes: string[];
60
60
  }[] | undefined;
61
61
  }, {
62
62
  starters?: {
63
- name: string;
64
63
  description: string;
64
+ name: string;
65
65
  url: string;
66
66
  framework: string;
67
67
  mode: string;
68
68
  banner?: string | undefined;
69
69
  }[] | undefined;
70
70
  'add-ons'?: {
71
- name: string;
72
71
  description: string;
72
+ name: string;
73
73
  url: string;
74
74
  framework: string;
75
75
  modes: string[];
@@ -0,0 +1,2 @@
1
+ import type { Environment, Options } from '../types.js';
2
+ export declare function postInitScript(environment: Environment, options: Options): Promise<void>;