Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions apps/cli/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { Frontend, ProjectConfig } from "./types";
import type { Addons, Frontend, ProjectConfig } from "./types";
import { getUserPkgManager } from "./utils/get-package-manager";

const __filename = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -55,10 +55,11 @@ export const dependencyVersionMap = {

"@tauri-apps/cli": "^2.4.0",

"@biomejs/biome": "^2.0.0",
"@biomejs/biome": "^2.1.2",
oxlint: "^1.8.0",

husky: "^9.1.7",
"lint-staged": "^15.5.0",
"lint-staged": "^16.1.2",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify dependency version updates are current and secure

The version updates for @biomejs/biome (^2.0.0 → ^2.1.2), lint-staged (^15.5.0 → ^16.1.2), and the new oxlint (^1.8.0) should be verified to ensure they represent the latest stable versions and don't introduce security vulnerabilities.


🌐 Web query:

What are the latest stable versions of @biomejs/biome, lint-staged, and oxlint packages, and are there any known security issues with these versions?

💡 Result:

The latest stable versions for the requested packages as of July 27, 2025, and their known security status, are as follows:

@biomejs/biome

  • The latest stable version is v2.0, officially released in June 2025[9].
  • This release is notable for introducing type-aware linting without relying on the TypeScript compiler[9].
  • There are no currently known or reported security issues affecting this latest version as of the available sources[3][9]. Security sources classify the project as having a “healthy version release cadence and project activity”[3].

lint-staged

  • The latest stable version is v16.1.0, released on May 27, 2025[2].
  • This update includes new command-line flags and minor improvements[2].
  • There are no security advisories or reported vulnerabilities affecting v16.1.0 according to security analysis tools and the project’s changelog[2][6].

oxlint

  • There is no recent release information or security advisory for oxlint within the provided search results.
  • If you require the precise latest version and security information for oxlint, please consult the npm registry or the official Oxlint repository directly, as up-to-date release and advisory details were not returned by the provided search results.

Summary Table

Package Latest Stable Version Release Date Known Security Issues
@biomejs/biome v2.0 2025-06-17 None reported[3][9]
lint-staged v16.1.0 2025-05-27 None reported[2][6]
oxlint Not specified Not specified Not available in search results

Caveats & Limitations

  • Security status is based on current public information. For mission-critical applications, always verify against centralized advisories such as npm, Socket.dev, GitHub Security Advisories, or CVE databases.
  • Precise version and security details for oxlint were not found in the returned sources; external verification is recommended for the most accurate and up-to-date info.

Citations:


Action Required: Validate and Correct Dependency Versions

It looks like the versions specified in apps/cli/src/constants.ts (lines 58–62) don’t align with what’s currently published on npm:

• @biomejs/biome: "^2.1.2" – npm’s latest stable is 2.0.0 (Jun 2025). Please confirm ^2.1.2 actually exists or roll back to "^2.0.0".
• lint-staged: "^16.1.2" – npm’s latest stable is 16.1.0. Update to "^16.1.0" or verify the 16.1.2 release.
• oxlint: "^1.8.0" – no release information found. Verify that v1.8.0 is published and free of advisories.

• If you intended to pin pre-releases or unpublished packages, ensure your registry configuration supports them.
• Otherwise, update these entries to the latest valid versions and rerun your vulnerability scans.

🤖 Prompt for AI Agents
In apps/cli/src/constants.ts between lines 58 and 62, verify the versions of
@biomejs/biome, lint-staged, and oxlint against the official npm registry. If
the specified versions (^2.1.2 for @biomejs/biome, ^16.1.2 for lint-staged, and
^1.8.0 for oxlint) do not exist or are unpublished, update them to the latest
stable versions available (^2.0.0 for @biomejs/biome, ^16.1.0 for lint-staged)
or confirm the existence and validity of oxlint v1.8.0. Adjust the version
strings accordingly and ensure your registry configuration supports any
pre-release or unpublished packages if those are intentional.


tsx: "^4.19.2",
"@types/node": "^22.13.11",
Expand Down Expand Up @@ -119,13 +120,16 @@ export const dependencyVersionMap = {

export type AvailableDependencies = keyof typeof dependencyVersionMap;

export const ADDON_COMPATIBILITY = {
export const ADDON_COMPATIBILITY: Record<Addons, readonly Frontend[]> = {
pwa: ["tanstack-router", "react-router", "solid", "next"],
tauri: ["tanstack-router", "react-router", "nuxt", "svelte", "solid"],
biome: [],
husky: [],
turborepo: [],
starlight: [],
ultracite: [],
oxlint: [],
fumadocs: [],
none: [],
} as const;

Expand Down
4 changes: 4 additions & 0 deletions apps/cli/src/helpers/project-generation/post-installation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ export async function displayPostInstallInstructions(
output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
}

if (addons?.includes("fumadocs")) {
output += `${pc.cyan("•")} Fumadocs: http://localhost:4000\n`;
}

if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
Expand Down
8 changes: 8 additions & 0 deletions apps/cli/src/helpers/project-generation/template-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,14 @@ export async function handleExtras(
}
}

if (context.packageManager === "bun") {
const bunfigSrc = path.join(extrasDir, "bunfig.toml");
const bunfigDest = path.join(projectDir, "bunfig.toml");
if (await fs.pathExists(bunfigSrc)) {
await fs.copy(bunfigSrc, bunfigDest);
}
}

if (
context.packageManager === "pnpm" &&
(hasNative || context.frontend.includes("nuxt"))
Expand Down
96 changes: 83 additions & 13 deletions apps/cli/src/helpers/setup/addons-setup.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import path from "node:path";
import { log } from "@clack/prompts";
import { execa } from "execa";
import fs from "fs-extra";
import pc from "picocolors";
import type { Frontend, ProjectConfig } from "../../types";
import type { Frontend, PackageManager, ProjectConfig } from "../../types";
import { addPackageDependency } from "../../utils/add-package-deps";
import { getPackageExecutionCommand } from "../../utils/package-runner";
import { setupFumadocs } from "./fumadocs-setup";
import { setupStarlight } from "./starlight-setup";
import { setupTauri } from "./tauri-setup";
import { setupUltracite } from "./ultracite-setup";
import { addPwaToViteConfig } from "./vite-pwa-setup";

export async function setupAddons(config: ProjectConfig, isAddCommand = false) {
const { addons, frontend, projectDir } = config;
const { addons, frontend, projectDir, packageManager } = config;
const hasReactWebFrontend =
frontend.includes("react-router") ||
frontend.includes("tanstack-router") ||
Expand Down Expand Up @@ -53,15 +57,38 @@ ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
) {
await setupTauri(config);
}
if (addons.includes("biome")) {
await setupBiome(projectDir);
const hasUltracite = addons.includes("ultracite");
const hasBiome = addons.includes("biome");
const hasHusky = addons.includes("husky");
const hasOxlint = addons.includes("oxlint");

if (hasUltracite) {
await setupUltracite(config, hasHusky);
} else {
if (hasBiome) {
await setupBiome(projectDir);
}
if (hasHusky) {
// Determine which linter to use for lint-staged
let linter: "biome" | "oxlint" | undefined;
if (hasOxlint) {
linter = "oxlint";
} else if (hasBiome) {
linter = "biome";
}
await setupHusky(projectDir, linter);
}
}
if (addons.includes("husky")) {
await setupHusky(projectDir);

if (addons.includes("oxlint")) {
await setupOxlint(projectDir, packageManager);
}
if (addons.includes("starlight")) {
await setupStarlight(config);
}
if (addons.includes("fumadocs")) {
await setupFumadocs(config);
}
}

function getWebAppDir(projectDir: string, frontends: Frontend[]): string {
Expand All @@ -77,7 +104,7 @@ function getWebAppDir(projectDir: string, frontends: Frontend[]): string {
return path.join(projectDir, "apps/web");
}

async function setupBiome(projectDir: string) {
export async function setupBiome(projectDir: string) {
await addPackageDependency({
devDependencies: ["@biomejs/biome"],
projectDir,
Expand All @@ -96,7 +123,10 @@ async function setupBiome(projectDir: string) {
}
}

async function setupHusky(projectDir: string) {
export async function setupHusky(
projectDir: string,
linter?: "biome" | "oxlint",
) {
await addPackageDependency({
devDependencies: ["husky", "lint-staged"],
projectDir,
Expand All @@ -111,11 +141,21 @@ async function setupHusky(projectDir: string) {
prepare: "husky",
};

packageJson["lint-staged"] = {
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
"biome check --write .",
],
};
if (linter === "oxlint") {
packageJson["lint-staged"] = {
"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint",
};
} else if (linter === "biome") {
packageJson["lint-staged"] = {
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
"biome check --write .",
],
};
} else {
packageJson["lint-staged"] = {
"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "",
};
}
Comment on lines +153 to +157
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider handling the no-linter case more gracefully.

Setting an empty command for lint-staged when no linter is specified may cause issues. Consider either omitting the lint-staged configuration entirely or providing a meaningful default command.

-		} else {
-			packageJson["lint-staged"] = {
-				"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "",
-			};
-		}
+		}
+		// Note: If no linter is specified, we don't configure lint-staged
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else {
packageJson["lint-staged"] = {
"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "",
};
}
}
// Note: If no linter is specified, we don't configure lint-staged
🤖 Prompt for AI Agents
In apps/cli/src/helpers/setup/addons-setup.ts around lines 154 to 158, the
current code sets an empty string as the lint-staged command when no linter is
specified, which can cause issues. Instead, modify the logic to either omit the
lint-staged property from packageJson entirely in this case or assign a
meaningful default command that safely handles staged files without errors.


await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
}
Expand Down Expand Up @@ -157,3 +197,33 @@ async function setupPwa(projectDir: string, frontends: Frontend[]) {
await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
}
}

async function setupOxlint(projectDir: string, packageManager: PackageManager) {
await addPackageDependency({
devDependencies: ["oxlint"],
projectDir,
});

const packageJsonPath = path.join(projectDir, "package.json");
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);

packageJson.scripts = {
...packageJson.scripts,
check: "oxlint",
};
Comment on lines +210 to +213
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential script name conflict with other linters.

The hardcoded "check" script name may conflict with biome's check script if both addons are installed. Consider using a more specific name like "oxlint-check" or implementing logic to handle script name conflicts.

-		packageJson.scripts = {
-			...packageJson.scripts,
-			check: "oxlint",
-		};
+		packageJson.scripts = {
+			...packageJson.scripts,
+			"oxlint-check": "oxlint",
+		};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
packageJson.scripts = {
...packageJson.scripts,
check: "oxlint",
};
packageJson.scripts = {
...packageJson.scripts,
"oxlint-check": "oxlint",
};
🤖 Prompt for AI Agents
In apps/cli/src/helpers/setup/addons-setup.ts around lines 211 to 214, the
script name "check" is hardcoded and may conflict with other linters like biome.
To fix this, rename the script to a more specific name such as "oxlint-check" to
avoid conflicts. Alternatively, implement logic to detect existing script names
and adjust accordingly to prevent overwriting or conflicts.


await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
}

const oxlintInitCommand = getPackageExecutionCommand(
packageManager,
"oxlint@latest --init",
);

await execa(oxlintInitCommand, {
cwd: projectDir,
env: { CI: "true" },
shell: true,
});
}
Comment on lines +223 to +228
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for the oxlint initialization command.

The execa command should include error handling to provide meaningful feedback if the initialization fails.

-	await execa(oxlintInitCommand, {
-		cwd: projectDir,
-		env: { CI: "true" },
-		shell: true,
-	});
+	try {
+		await execa(oxlintInitCommand, {
+			cwd: projectDir,
+			env: { CI: "true" },
+			shell: true,
+		});
+	} catch (error) {
+		console.warn("Failed to initialize oxlint configuration:", error);
+		// Continue execution as this is not critical for the setup
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await execa(oxlintInitCommand, {
cwd: projectDir,
env: { CI: "true" },
shell: true,
});
}
try {
await execa(oxlintInitCommand, {
cwd: projectDir,
env: { CI: "true" },
shell: true,
});
} catch (error) {
console.warn("Failed to initialize oxlint configuration:", error);
// Continue execution as this is not critical for the setup
}
}
🤖 Prompt for AI Agents
In apps/cli/src/helpers/setup/addons-setup.ts around lines 224 to 229, the execa
call running the oxlint initialization command lacks error handling. Wrap the
execa call in a try-catch block to catch any errors thrown during execution, and
in the catch block, log or throw a meaningful error message that includes
details about the failure to help with debugging.

96 changes: 96 additions & 0 deletions apps/cli/src/helpers/setup/fumadocs-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import path from "node:path";
import { cancel, isCancel, log, select } from "@clack/prompts";
import consola from "consola";
import { execa } from "execa";
import fs from "fs-extra";
import pc from "picocolors";
import type { ProjectConfig } from "../../types";
import { getPackageExecutionCommand } from "../../utils/package-runner";

type FumadocsTemplate =
| "next-mdx"
| "next-content-collections"
| "react-router-mdx-remote"
| "tanstack-start-mdx-remote";

const TEMPLATES = {
"next-mdx": {
label: "Next.js: Fumadocs MDX",
hint: "Recommended template with MDX support",
value: "+next+fuma-docs-mdx",
},
"next-content-collections": {
label: "Next.js: Content Collections",
hint: "Template using Next.js content collections",
value: "+next+content-collections",
},
"react-router-mdx-remote": {
label: "React Router: MDX Remote",
hint: "Template for React Router with MDX remote",
value: "react-router",
},
"tanstack-start-mdx-remote": {
label: "Tanstack Start: MDX Remote",
hint: "Template for Tanstack Start with MDX remote",
value: "tanstack-start",
},
} as const;

export async function setupFumadocs(config: ProjectConfig) {
const { packageManager, projectDir } = config;

try {
log.info("Setting up Fumadocs...");

const template = await select<FumadocsTemplate>({
message: "Choose a template",
options: Object.entries(TEMPLATES).map(([key, template]) => ({
value: key as FumadocsTemplate,
label: template.label,
hint: template.hint,
})),
initialValue: "next-mdx",
});

if (isCancel(template)) {
cancel(pc.red("Operation cancelled"));
process.exit(0);
}

const templateArg = TEMPLATES[template].value;

const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${templateArg} --src --no-install --pm bun --no-eslint`;

const fumadocsInitCommand = getPackageExecutionCommand(
packageManager,
commandWithArgs,
);

await execa(fumadocsInitCommand, {
cwd: path.join(projectDir, "apps"),
env: { CI: "true" },
shell: true,
});

const fumadocsDir = path.join(projectDir, "apps", "fumadocs");
const packageJsonPath = path.join(fumadocsDir, "package.json");

if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);
packageJson.name = "fumadocs";

if (packageJson.scripts?.dev) {
packageJson.scripts.dev = `${packageJson.scripts.dev} --port=4000`;
}

await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
}

log.success("Fumadocs setup successfully!");
} catch (error) {
log.error(pc.red("Failed to set up Fumadocs"));
if (error instanceof Error) {
consola.error(pc.red(error.message));
}
}
}
Loading