Create template files for react-router 93eb8a8e
Maximilian Leodolter · 2025-08-07 13:58 13 file(s) · +286 −284
scripts/check-template-combinations.ts +286 −284
7 7
8 8
// Define the possible boolean options from ProjectOptions type
9 9
const BOOLEAN_OPTIONS = [
10 -
  "tailwind",
11 -
  "shadcn",
12 -
  "rpc",
13 -
  "tanstackQuery",
14 -
  "reactRouter",
10 +
	"tailwind",
11 +
	"shadcn",
12 +
	"rpc",
13 +
	"tanstackQuery",
14 +
	"reactRouter",
15 +
	"tanstackRouter",
15 16
] as const;
16 17
17 18
// Package dependency rules
18 19
const PACKAGE_DEPENDENCIES: Record<string, string[]> = {
19 -
  shadcn: ["tailwind"], // shadcn requires tailwind
20 -
  // Add more dependencies here as needed
21 -
  // example: somePackage: ["requiredPackage1", "requiredPackage2"]
20 +
	shadcn: ["tailwind"], // shadcn requires tailwind
21 +
	// Add more dependencies here as needed
22 +
	// example: somePackage: ["requiredPackage1", "requiredPackage2"]
22 23
};
23 24
24 25
// Mutually exclusive groups (only one option from each group can be selected)
25 26
const MUTUALLY_EXCLUSIVE_GROUPS: string[][] = [
26 -
  // Add mutually exclusive groups here as needed
27 -
  // example: ["option1", "option2", "option3"]
27 +
	// Add mutually exclusive groups here as needed
28 +
	// example: ["option1", "option2", "option3"]
29 +
	["reactRouter", "tanstackRouter"],
28 30
];
29 31
30 32
// Check if a combination is valid based on dependencies and mutual exclusivity
31 33
function isValidCombination(combination: Record<string, boolean>): boolean {
32 -
  // Skip combinations with no packages selected
33 -
  const hasAnyPackage = Object.values(combination).some((value) => value);
34 -
  if (!hasAnyPackage) {
35 -
    return false;
36 -
  }
34 +
	// Skip combinations with no packages selected
35 +
	const hasAnyPackage = Object.values(combination).some((value) => value);
36 +
	if (!hasAnyPackage) {
37 +
		return false;
38 +
	}
37 39
38 -
  // Skip combinations that only contain shadcn and/or tailwind (they're cloned from repo)
39 -
  const enabledPackages = Object.keys(combination).filter(
40 -
    (key) => combination[key],
41 -
  );
42 -
  const onlyShadcnTailwind = enabledPackages.every(
43 -
    (pkg) => pkg === "shadcn" || pkg === "tailwind",
44 -
  );
45 -
  if (onlyShadcnTailwind) {
46 -
    return false;
47 -
  }
40 +
	// Skip combinations that only contain shadcn and/or tailwind (they're cloned from repo)
41 +
	const enabledPackages = Object.keys(combination).filter(
42 +
		(key) => combination[key],
43 +
	);
44 +
	const onlyShadcnTailwind = enabledPackages.every(
45 +
		(pkg) => pkg === "shadcn" || pkg === "tailwind",
46 +
	);
47 +
	if (onlyShadcnTailwind) {
48 +
		return false;
49 +
	}
48 50
49 -
  // Check package dependencies
50 -
  for (const [packageName, dependencies] of Object.entries(
51 -
    PACKAGE_DEPENDENCIES,
52 -
  )) {
53 -
    if (combination[packageName]) {
54 -
      // If this package is enabled, all its dependencies must also be enabled
55 -
      for (const dependency of dependencies) {
56 -
        if (!combination[dependency]) {
57 -
          return false;
58 -
        }
59 -
      }
60 -
    }
61 -
  }
51 +
	// Check package dependencies
52 +
	for (const [packageName, dependencies] of Object.entries(
53 +
		PACKAGE_DEPENDENCIES,
54 +
	)) {
55 +
		if (combination[packageName]) {
56 +
			// If this package is enabled, all its dependencies must also be enabled
57 +
			for (const dependency of dependencies) {
58 +
				if (!combination[dependency]) {
59 +
					return false;
60 +
				}
61 +
			}
62 +
		}
63 +
	}
62 64
63 -
  // Check mutual exclusivity
64 -
  for (const group of MUTUALLY_EXCLUSIVE_GROUPS) {
65 -
    const selectedInGroup = group.filter((option) => combination[option]);
66 -
    if (selectedInGroup.length > 1) {
67 -
      return false; // More than one option selected in mutually exclusive group
68 -
    }
69 -
  }
65 +
	// Check mutual exclusivity
66 +
	for (const group of MUTUALLY_EXCLUSIVE_GROUPS) {
67 +
		const selectedInGroup = group.filter((option) => combination[option]);
68 +
		if (selectedInGroup.length > 1) {
69 +
			return false; // More than one option selected in mutually exclusive group
70 +
		}
71 +
	}
70 72
71 -
  return true;
73 +
	return true;
72 74
}
73 75
74 76
// Generate all possible combinations of boolean options with filtering
75 77
function generateAllCombinations(
76 -
  options: readonly string[],
78 +
	options: readonly string[],
77 79
): Array<Record<string, boolean>> {
78 -
  const combinations: Array<Record<string, boolean>> = [];
79 -
  const totalCombinations = Math.pow(2, options.length);
80 +
	const combinations: Array<Record<string, boolean>> = [];
81 +
	const totalCombinations = Math.pow(2, options.length);
80 82
81 -
  for (let i = 0; i < totalCombinations; i++) {
82 -
    const combination: Record<string, boolean> = {};
83 -
    for (let j = 0; j < options.length; j++) {
84 -
      combination[options[j]] = Boolean(i & (1 << j));
85 -
    }
83 +
	for (let i = 0; i < totalCombinations; i++) {
84 +
		const combination: Record<string, boolean> = {};
85 +
		for (let j = 0; j < options.length; j++) {
86 +
			combination[options[j]] = Boolean(i & (1 << j));
87 +
		}
86 88
87 -
    // Only include valid combinations
88 -
    if (isValidCombination(combination)) {
89 -
      combinations.push(combination);
90 -
    }
91 -
  }
89 +
		// Only include valid combinations
90 +
		if (isValidCombination(combination)) {
91 +
			combinations.push(combination);
92 +
		}
93 +
	}
92 94
93 -
  return combinations;
95 +
	return combinations;
94 96
}
95 97
96 98
// Simulate nameGenerator function locally
97 99
const nameGenerator = (
98 -
  basename: string,
99 -
  possibleOptions: Record<string, boolean>,
100 +
	basename: string,
101 +
	possibleOptions: Record<string, boolean>,
100 102
) => {
101 -
  const dotIndex = basename.lastIndexOf(".");
102 -
  const filename = dotIndex === -1 ? basename : basename.substring(0, dotIndex);
103 -
  const extension = dotIndex === -1 ? "" : basename.substring(dotIndex + 1);
103 +
	const dotIndex = basename.lastIndexOf(".");
104 +
	const filename = dotIndex === -1 ? basename : basename.substring(0, dotIndex);
105 +
	const extension = dotIndex === -1 ? "" : basename.substring(dotIndex + 1);
104 106
105 -
  const selectedOptions = Object.keys(possibleOptions)
106 -
    .filter((opt) => possibleOptions[opt])
107 -
    .map((opt) => opt.toLowerCase())
108 -
    .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }));
107 +
	const selectedOptions = Object.keys(possibleOptions)
108 +
		.filter((opt) => possibleOptions[opt])
109 +
		.map((opt) => opt.toLowerCase())
110 +
		.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }));
109 111
110 -
  if (selectedOptions.length > 0) {
111 -
    const suffix = ["-with", ...selectedOptions].join("-");
112 -
    return extension
113 -
      ? `${filename}${suffix}.${extension}`
114 -
      : `${filename}${suffix}`;
115 -
  }
116 -
  return basename;
112 +
	if (selectedOptions.length > 0) {
113 +
		const suffix = ["-with", ...selectedOptions].join("-");
114 +
		return extension
115 +
			? `${filename}${suffix}.${extension}`
116 +
			: `${filename}${suffix}`;
117 +
	}
118 +
	return basename;
117 119
};
118 120
119 121
// Parse installer files to find nameGenerator calls and hardcoded template patterns
120 122
// Recursively find all .ts files in installers directory
121 123
async function findInstallerFiles(dir: string): Promise<string[]> {
122 -
  const files: string[] = [];
123 -
  const entries = await fs.readdir(dir, { withFileTypes: true });
124 +
	const files: string[] = [];
125 +
	const entries = await fs.readdir(dir, { withFileTypes: true });
124 126
125 -
  for (const entry of entries) {
126 -
    const fullPath = path.join(dir, entry.name);
127 -
    if (entry.isDirectory()) {
128 -
      files.push(...(await findInstallerFiles(fullPath)));
129 -
    } else if (entry.isFile() && entry.name.endsWith(".ts")) {
130 -
      files.push(fullPath);
131 -
    }
132 -
  }
127 +
	for (const entry of entries) {
128 +
		const fullPath = path.join(dir, entry.name);
129 +
		if (entry.isDirectory()) {
130 +
			files.push(...(await findInstallerFiles(fullPath)));
131 +
		} else if (entry.isFile() && entry.name.endsWith(".ts")) {
132 +
			files.push(fullPath);
133 +
		}
134 +
	}
133 135
134 -
  return files;
136 +
	return files;
135 137
}
136 138
137 139
async function parseInstallerFiles(): Promise<
138 -
  Array<{
139 -
    file: string;
140 -
    basename: string;
141 -
    usedOptions: string[];
142 -
    templatePath: string;
143 -
    type: "nameGenerator" | "hardcoded";
144 -
  }>
140 +
	Array<{
141 +
		file: string;
142 +
		basename: string;
143 +
		usedOptions: string[];
144 +
		templatePath: string;
145 +
		type: "nameGenerator" | "hardcoded";
146 +
	}>
145 147
> {
146 -
  const installerFiles = await findInstallerFiles("src/installers");
147 -
  const templateCalls: Array<{
148 -
    file: string;
149 -
    basename: string;
150 -
    usedOptions: string[];
151 -
    templatePath: string;
152 -
    type: "nameGenerator" | "hardcoded";
153 -
  }> = [];
148 +
	const installerFiles = await findInstallerFiles("src/installers");
149 +
	const templateCalls: Array<{
150 +
		file: string;
151 +
		basename: string;
152 +
		usedOptions: string[];
153 +
		templatePath: string;
154 +
		type: "nameGenerator" | "hardcoded";
155 +
	}> = [];
154 156
155 -
  for (const file of installerFiles) {
156 -
    const content = await fs.readFile(file, "utf-8");
157 +
	for (const file of installerFiles) {
158 +
		const content = await fs.readFile(file, "utf-8");
157 159
158 -
    // Find nameGenerator calls with regex
159 -
    const nameGeneratorRegex =
160 -
      /nameGenerator\s*\(\s*["']([^"']+)["']\s*,\s*\{([^}]+)\}/g;
161 -
    let match;
160 +
		// Find nameGenerator calls with regex
161 +
		const nameGeneratorRegex =
162 +
			/nameGenerator\s*\(\s*["']([^"']+)["']\s*,\s*\{([^}]+)\}/g;
163 +
		let match;
162 164
163 -
    while ((match = nameGeneratorRegex.exec(content)) !== null) {
164 -
      const basename = match[1];
165 -
      const optionsStr = match[2];
165 +
		while ((match = nameGeneratorRegex.exec(content)) !== null) {
166 +
			const basename = match[1];
167 +
			const optionsStr = match[2];
166 168
167 -
      // Extract the options used in this call
168 -
      const usedOptions = BOOLEAN_OPTIONS.filter((option) =>
169 -
        optionsStr.includes(option),
170 -
      );
169 +
			// Extract the options used in this call
170 +
			const usedOptions = BOOLEAN_OPTIONS.filter((option) =>
171 +
				optionsStr.includes(option),
172 +
			);
171 173
172 -
      // Try to find the template path from the surrounding context
173 -
      const lines = content.split("\n");
174 -
      const matchLine =
175 -
        content.substring(0, match.index).split("\n").length - 1;
174 +
			// Try to find the template path from the surrounding context
175 +
			const lines = content.split("\n");
176 +
			const matchLine =
177 +
				content.substring(0, match.index).split("\n").length - 1;
176 178
177 -
      let templatePath = "client/src"; // default based on common pattern
179 +
			let templatePath = "client/src"; // default based on common pattern
178 180
179 -
      // Look for path.join with EXTRAS_DIR in nearby lines (could be multi-line)
180 -
      for (
181 -
        let i = Math.max(0, matchLine - 10);
182 -
        i <= Math.min(lines.length - 1, matchLine + 10);
183 -
        i++
184 -
      ) {
185 -
        const line = lines[i].trim();
186 -
        if (line.includes("path.join") && line.includes("EXTRAS_DIR")) {
187 -
          // Find the complete path.join statement (might span multiple lines)
188 -
          let pathJoinContent = "";
189 -
          let j = i;
190 -
          let parenCount = 0;
191 -
          let foundStart = false;
181 +
			// Look for path.join with EXTRAS_DIR in nearby lines (could be multi-line)
182 +
			for (
183 +
				let i = Math.max(0, matchLine - 10);
184 +
				i <= Math.min(lines.length - 1, matchLine + 10);
185 +
				i++
186 +
			) {
187 +
				const line = lines[i].trim();
188 +
				if (line.includes("path.join") && line.includes("EXTRAS_DIR")) {
189 +
					// Find the complete path.join statement (might span multiple lines)
190 +
					let pathJoinContent = "";
191 +
					let j = i;
192 +
					let parenCount = 0;
193 +
					let foundStart = false;
192 194
193 -
          while (j < lines.length) {
194 -
            const currentLine = lines[j].trim();
195 -
            pathJoinContent += currentLine + " ";
195 +
					while (j < lines.length) {
196 +
						const currentLine = lines[j].trim();
197 +
						pathJoinContent += currentLine + " ";
196 198
197 -
            if (currentLine.includes("path.join")) {
198 -
              foundStart = true;
199 -
            }
199 +
						if (currentLine.includes("path.join")) {
200 +
							foundStart = true;
201 +
						}
200 202
201 -
            if (foundStart) {
202 -
              parenCount += (currentLine.match(/\(/g) || []).length;
203 -
              parenCount -= (currentLine.match(/\)/g) || []).length;
203 +
						if (foundStart) {
204 +
							parenCount += (currentLine.match(/\(/g) || []).length;
205 +
							parenCount -= (currentLine.match(/\)/g) || []).length;
204 206
205 -
              if (parenCount === 0) {
206 -
                break;
207 -
              }
208 -
            }
209 -
            j++;
210 -
          }
207 +
							if (parenCount === 0) {
208 +
								break;
209 +
							}
210 +
						}
211 +
						j++;
212 +
					}
211 213
212 -
          // Extract path components from the complete path.join
213 -
          const pathMatch = pathJoinContent.match(/EXTRAS_DIR[^,]*,([^)]+)\)/);
214 -
          if (pathMatch) {
215 -
            templatePath = pathMatch[1]
216 -
              .split(",")
217 -
              .map((s) =>
218 -
                s
219 -
                  .trim()
220 -
                  .replace(/['"]/g, "")
221 -
                  .replace(/nameGenerator[^,]*/, basename),
222 -
              )
223 -
              .filter((s) => s && s !== basename)
224 -
              .join("/");
225 -
          }
226 -
          break;
227 -
        }
228 -
      }
214 +
					// Extract path components from the complete path.join
215 +
					const pathMatch = pathJoinContent.match(/EXTRAS_DIR[^,]*,([^)]+)\)/);
216 +
					if (pathMatch) {
217 +
						templatePath = pathMatch[1]
218 +
							.split(",")
219 +
							.map((s) =>
220 +
								s
221 +
									.trim()
222 +
									.replace(/['"]/g, "")
223 +
									.replace(/nameGenerator[^,]*/, basename),
224 +
							)
225 +
							.filter((s) => s && s !== basename)
226 +
							.join("/");
227 +
					}
228 +
					break;
229 +
				}
230 +
			}
229 231
230 -
      templateCalls.push({
231 -
        file,
232 -
        basename,
233 -
        usedOptions,
234 -
        templatePath,
235 -
        type: "nameGenerator",
236 -
      });
237 -
    }
232 +
			templateCalls.push({
233 +
				file,
234 +
				basename,
235 +
				usedOptions,
236 +
				templatePath,
237 +
				type: "nameGenerator",
238 +
			});
239 +
		}
238 240
239 -
    // Find hardcoded template patterns like: `App-with${tailwind ? "-tailwind" : ""}${shadcn ? "-shadcn" : ""}${rpc ? "-rpc" : ""}.tsx`
240 -
    const hardcodedRegex = /`([^`]*)-with\${[^`]+\$\{[^`]+}\.[^`]+`/g;
241 -
    let hardcodedMatch;
241 +
		// Find hardcoded template patterns like: `App-with${tailwind ? "-tailwind" : ""}${shadcn ? "-shadcn" : ""}${rpc ? "-rpc" : ""}.tsx`
242 +
		const hardcodedRegex = /`([^`]*)-with\${[^`]+\$\{[^`]+}\.[^`]+`/g;
243 +
		let hardcodedMatch;
242 244
243 -
    while ((hardcodedMatch = hardcodedRegex.exec(content)) !== null) {
244 -
      const templatePattern = hardcodedMatch[1];
245 +
		while ((hardcodedMatch = hardcodedRegex.exec(content)) !== null) {
246 +
			const templatePattern = hardcodedMatch[1];
245 247
246 -
      // Extract basename from pattern (everything before "-with")
247 -
      const basenameMatch = templatePattern.match(/^([^-]+)/);
248 -
      if (basenameMatch) {
249 -
        const basename = `${basenameMatch[1]}.tsx`; // Add .tsx extension
248 +
			// Extract basename from pattern (everything before "-with")
249 +
			const basenameMatch = templatePattern.match(/^([^-]+)/);
250 +
			if (basenameMatch) {
251 +
				const basename = `${basenameMatch[1]}.tsx`; // Add .tsx extension
250 252
251 -
        // Extract options from the template pattern
252 -
        const usedOptions = BOOLEAN_OPTIONS.filter((option) =>
253 -
          hardcodedMatch[0].includes(option),
254 -
        );
253 +
				// Extract options from the template pattern
254 +
				const usedOptions = BOOLEAN_OPTIONS.filter((option) =>
255 +
					hardcodedMatch[0].includes(option),
256 +
				);
255 257
256 -
        // Find template path similar to nameGenerator
257 -
        const lines = content.split("\n");
258 -
        const matchLine =
259 -
          content.substring(0, hardcodedMatch.index).split("\n").length - 1;
258 +
				// Find template path similar to nameGenerator
259 +
				const lines = content.split("\n");
260 +
				const matchLine =
261 +
					content.substring(0, hardcodedMatch.index).split("\n").length - 1;
260 262
261 -
        let templatePath = "client/src";
263 +
				let templatePath = "client/src";
262 264
263 -
        for (
264 -
          let i = Math.max(0, matchLine - 10);
265 -
          i <= Math.min(lines.length - 1, matchLine + 10);
266 -
          i++
267 -
        ) {
268 -
          const line = lines[i].trim();
269 -
          if (line.includes("path.join") && line.includes("EXTRAS_DIR")) {
270 -
            let pathJoinContent = "";
271 -
            let j = i;
272 -
            let parenCount = 0;
273 -
            let foundStart = false;
265 +
				for (
266 +
					let i = Math.max(0, matchLine - 10);
267 +
					i <= Math.min(lines.length - 1, matchLine + 10);
268 +
					i++
269 +
				) {
270 +
					const line = lines[i].trim();
271 +
					if (line.includes("path.join") && line.includes("EXTRAS_DIR")) {
272 +
						let pathJoinContent = "";
273 +
						let j = i;
274 +
						let parenCount = 0;
275 +
						let foundStart = false;
274 276
275 -
            while (j < lines.length) {
276 -
              const currentLine = lines[j].trim();
277 -
              pathJoinContent += currentLine + " ";
277 +
						while (j < lines.length) {
278 +
							const currentLine = lines[j].trim();
279 +
							pathJoinContent += currentLine + " ";
278 280
279 -
              if (currentLine.includes("path.join")) {
280 -
                foundStart = true;
281 -
              }
281 +
							if (currentLine.includes("path.join")) {
282 +
								foundStart = true;
283 +
							}
282 284
283 -
              if (foundStart) {
284 -
                parenCount += (currentLine.match(/\(/g) || []).length;
285 -
                parenCount -= (currentLine.match(/\)/g) || []).length;
285 +
							if (foundStart) {
286 +
								parenCount += (currentLine.match(/\(/g) || []).length;
287 +
								parenCount -= (currentLine.match(/\)/g) || []).length;
286 288
287 -
                if (parenCount === 0) {
288 -
                  break;
289 -
                }
290 -
              }
291 -
              j++;
292 -
            }
289 +
								if (parenCount === 0) {
290 +
									break;
291 +
								}
292 +
							}
293 +
							j++;
294 +
						}
293 295
294 -
            const pathMatch = pathJoinContent.match(
295 -
              /EXTRAS_DIR[^,]*,([^)]+)\)/,
296 -
            );
297 -
            if (pathMatch) {
298 -
              templatePath = pathMatch[1]
299 -
                .split(",")
300 -
                .map((s) =>
301 -
                  s
302 -
                    .trim()
303 -
                    .replace(/['"]/g, "")
304 -
                    .replace(/selectedTemplate[^,]*/, basename),
305 -
                )
306 -
                .filter(
307 -
                  (s) => s && s !== basename && !s.includes("selectedTemplate"),
308 -
                )
309 -
                .join("/");
310 -
            }
311 -
            break;
312 -
          }
313 -
        }
296 +
						const pathMatch = pathJoinContent.match(
297 +
							/EXTRAS_DIR[^,]*,([^)]+)\)/,
298 +
						);
299 +
						if (pathMatch) {
300 +
							templatePath = pathMatch[1]
301 +
								.split(",")
302 +
								.map((s) =>
303 +
									s
304 +
										.trim()
305 +
										.replace(/['"]/g, "")
306 +
										.replace(/selectedTemplate[^,]*/, basename),
307 +
								)
308 +
								.filter(
309 +
									(s) => s && s !== basename && !s.includes("selectedTemplate"),
310 +
								)
311 +
								.join("/");
312 +
						}
313 +
						break;
314 +
					}
315 +
				}
314 316
315 -
        templateCalls.push({
316 -
          file,
317 -
          basename,
318 -
          usedOptions,
319 -
          templatePath,
320 -
          type: "hardcoded",
321 -
        });
322 -
      }
323 -
    }
324 -
  }
317 +
				templateCalls.push({
318 +
					file,
319 +
					basename,
320 +
					usedOptions,
321 +
					templatePath,
322 +
					type: "hardcoded",
323 +
				});
324 +
			}
325 +
		}
326 +
	}
325 327
326 -
  return templateCalls;
328 +
	return templateCalls;
327 329
}
328 330
329 331
// Simulate hardcoded template naming (like in RPC installer)
330 332
const hardcodedGenerator = (
331 -
  basename: string,
332 -
  possibleOptions: Record<string, boolean>,
333 +
	basename: string,
334 +
	possibleOptions: Record<string, boolean>,
333 335
) => {
334 -
  const dotIndex = basename.lastIndexOf(".");
335 -
  const filename = dotIndex === -1 ? basename : basename.substring(0, dotIndex);
336 -
  const extension = dotIndex === -1 ? "" : basename.substring(dotIndex + 1);
336 +
	const dotIndex = basename.lastIndexOf(".");
337 +
	const filename = dotIndex === -1 ? basename : basename.substring(0, dotIndex);
338 +
	const extension = dotIndex === -1 ? "" : basename.substring(dotIndex + 1);
337 339
338 -
  let result = filename + "-with";
340 +
	let result = filename + "-with";
339 341
340 -
  // Hardcoded follows specific order: tailwind, shadcn, rpc, tanstackQuery, then routers
341 -
  if (possibleOptions.tailwind) result += "-tailwind";
342 -
  if (possibleOptions.shadcn) result += "-shadcn";
343 -
  if (possibleOptions.rpc) result += "-rpc";
344 -
  if (possibleOptions.tanstackQuery) result += "-tanstackquery";
345 -
  if (possibleOptions.reactRouter) result += "-reactrouter";
342 +
	// Hardcoded follows specific order: tailwind, shadcn, rpc, tanstackQuery, then routers
343 +
	if (possibleOptions.tailwind) result += "-tailwind";
344 +
	if (possibleOptions.shadcn) result += "-shadcn";
345 +
	if (possibleOptions.rpc) result += "-rpc";
346 +
	if (possibleOptions.tanstackQuery) result += "-tanstackquery";
347 +
	if (possibleOptions.reactRouter) result += "-reactrouter";
346 348
347 -
  return extension ? `${result}.${extension}` : result;
349 +
	return extension ? `${result}.${extension}` : result;
348 350
};
349 351
350 352
// Check if template files exist for all combinations
351 353
async function checkTemplateFiles() {
352 -
  const templateCalls = await parseInstallerFiles();
354 +
	const templateCalls = await parseInstallerFiles();
353 355
354 -
  if (templateCalls.length === 0) {
355 -
    return;
356 -
  }
356 +
	if (templateCalls.length === 0) {
357 +
		return;
358 +
	}
357 359
358 -
  for (const call of templateCalls) {
359 -
    const allCombinations = generateAllCombinations(call.usedOptions);
360 -
    const templateDir = path.join(
361 -
      path.resolve("src/templates/extras"),
362 -
      call.templatePath.replace(/["']/g, ""),
363 -
      call.basename,
364 -
    );
360 +
	for (const call of templateCalls) {
361 +
		const allCombinations = generateAllCombinations(call.usedOptions);
362 +
		const templateDir = path.join(
363 +
			path.resolve("src/templates/extras"),
364 +
			call.templatePath.replace(/["']/g, ""),
365 +
			call.basename,
366 +
		);
365 367
366 -
    for (const combination of allCombinations) {
367 -
      const templateName =
368 -
        call.type === "nameGenerator"
369 -
          ? nameGenerator(call.basename, combination)
370 -
          : hardcodedGenerator(call.basename, combination);
371 -
      const fullTemplatePath = path.join(templateDir, templateName);
368 +
		for (const combination of allCombinations) {
369 +
			const templateName =
370 +
				call.type === "nameGenerator"
371 +
					? nameGenerator(call.basename, combination)
372 +
					: hardcodedGenerator(call.basename, combination);
373 +
			const fullTemplatePath = path.join(templateDir, templateName);
372 374
373 -
      const exists = await fs.pathExists(fullTemplatePath);
374 -
      if (!exists) {
375 -
        console.log(`touch "${fullTemplatePath}"`);
376 -
      }
377 -
    }
378 -
  }
375 +
			const exists = await fs.pathExists(fullTemplatePath);
376 +
			if (!exists) {
377 +
				console.log(`touch "${fullTemplatePath}"`);
378 +
			}
379 +
		}
380 +
	}
379 381
}
380 382
381 383
// Run the analysis
src/templates/extras/client/src/App.tsx/App-with-reactrouter-rpc-shadcn-tailwind-tanstackquery.tsx (added) +0 −0

Binary file — no preview.

src/templates/extras/client/src/App.tsx/App-with-reactrouter-rpc-shadcn-tailwind.tsx (added) +0 −0

Binary file — no preview.

src/templates/extras/client/src/App.tsx/App-with-reactrouter-rpc-tailwind-tanstackquery.tsx (added) +0 −0

Binary file — no preview.

src/templates/extras/client/src/App.tsx/App-with-reactrouter-rpc-tailwind.tsx (added) +0 −0

Binary file — no preview.

src/templates/extras/client/src/App.tsx/App-with-reactrouter-rpc-tanstackquery.tsx (added) +0 −0

Binary file — no preview.

src/templates/extras/client/src/App.tsx/App-with-reactrouter-rpc.tsx (added) +0 −0

Binary file — no preview.

src/templates/extras/client/src/App.tsx/App-with-reactrouter-shadcn-tailwind-tanstackquery.tsx (added) +0 −0

Binary file — no preview.

src/templates/extras/client/src/App.tsx/App-with-reactrouter-shadcn-tailwind.tsx (added) +0 −0

Binary file — no preview.

src/templates/extras/client/src/App.tsx/App-with-reactrouter-tailwind-tanstackquery.tsx (added) +0 −0

Binary file — no preview.

src/templates/extras/client/src/App.tsx/App-with-reactrouter-tailwind.tsx (added) +0 −0

Binary file — no preview.

src/templates/extras/client/src/App.tsx/App-with-reactrouter-tanstackquery.tsx (added) +0 −0

Binary file — no preview.

src/templates/extras/client/src/App.tsx/App-with-reactrouter.tsx (added) +0 −0

Binary file — no preview.