spfn
Version:
Superfunction CLI - Add SPFN to your Next.js project
294 lines (263 loc) • 8.82 kB
JavaScript
import {
detectPackageManager,
logger
} from "./chunk-QH74KQEW.js";
// src/commands/setup.ts
import { Command } from "commander";
import { existsSync } from "fs";
import { join } from "path";
import ora from "ora";
import { execa } from "execa";
import fse from "fs-extra";
import chalk from "chalk";
var { ensureDirSync, writeFileSync, readFileSync } = fse;
async function setupIcons() {
const cwd = process.cwd();
logger.info("Setting up SVGR for SVG icon management...\n");
const packageJsonPath = join(cwd, "package.json");
if (!existsSync(packageJsonPath)) {
logger.error("No package.json found. Please run this in a Next.js project.");
process.exit(1);
}
const packageJson = JSON.parse(
readFileSync(packageJsonPath, "utf-8")
);
const hasNext = packageJson.dependencies?.next || packageJson.devDependencies?.next;
if (!hasNext) {
logger.error("Next.js not detected in dependencies. This setup is for Next.js projects only.");
process.exit(1);
}
const hasSvgr = packageJson.devDependencies?.["@svgr/webpack"];
if (hasSvgr) {
logger.warn("@svgr/webpack is already installed.");
logger.info("Skipping installation, but will create directory structure...\n");
}
if (!hasSvgr) {
const pm = detectPackageManager(cwd);
logger.step(`Detected package manager: ${pm}`);
const spinner2 = ora("Installing @svgr/webpack...").start();
try {
await execa(
pm,
pm === "npm" ? ["install", "--save-dev", "@svgr/webpack"] : ["add", "-D", "@svgr/webpack"],
{ cwd }
);
spinner2.succeed("@svgr/webpack installed");
} catch (error) {
spinner2.fail("Failed to install @svgr/webpack");
logger.error(String(error));
process.exit(1);
}
}
const spinner = ora("Updating next.config...").start();
try {
const possibleConfigs = [
"next.config.ts",
"next.config.js",
"next.config.mjs"
];
let configPath = null;
for (const config of possibleConfigs) {
const path = join(cwd, config);
if (existsSync(path)) {
configPath = path;
break;
}
}
if (!configPath) {
spinner.warn("next.config not found, creating next.config.ts...");
configPath = join(cwd, "next.config.ts");
const newConfig = `import type { NextConfig } from "next";
const nextConfig: NextConfig = {
webpack(config) {
// SVGR: Import SVG as React components
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fileLoaderRule = (config.module.rules as any[])
.find((rule: any) => Array.isArray(rule.oneOf))
?.oneOf.find((rule: any) => rule.test?.test?.('.svg'));
if (fileLoaderRule) {
fileLoaderRule.exclude = /\\.svg$/i;
}
config.module.rules.unshift({
test: /\\.svg$/i,
use: ['@svgr/webpack'],
});
return config;
},
turbopack: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
};
export default nextConfig;
`;
writeFileSync(configPath, newConfig);
spinner.succeed("Created next.config.ts with SVGR support");
} else {
let configContent = readFileSync(configPath, "utf-8");
if (configContent.includes("@svgr/webpack")) {
spinner.warn("SVGR already configured in next.config");
} else {
const hasWebpack = configContent.includes("webpack(");
const hasTurbopack = configContent.includes("turbopack:");
if (hasWebpack || hasTurbopack) {
spinner.info("Manual update required for next.config");
logger.warn("\nYou need to manually add SVGR configuration to your next.config file.");
logger.info("See: https://react-svgr.com/docs/next/");
logger.info("\nAdd this to your next.config:\n");
console.log(chalk.gray(`
webpack(config) {
const fileLoaderRule = config.module.rules
.find(rule => rule.oneOf)
?.oneOf.find(rule => rule.test?.test?.('.svg'));
if (fileLoaderRule) {
fileLoaderRule.exclude = /\\.svg$/i;
}
config.module.rules.unshift({
test: /\\.svg$/i,
use: ['@svgr/webpack'],
});
return config;
},
turbopack: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
`));
} else {
const webpackConfig = ` webpack(config) {
// SVGR: Import SVG as React components
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fileLoaderRule = (config.module.rules as any[])
.find((rule: any) => Array.isArray(rule.oneOf))
?.oneOf.find((rule: any) => rule.test?.test?.('.svg'));
if (fileLoaderRule) {
fileLoaderRule.exclude = /\\.svg$/i;
}
config.module.rules.unshift({
test: /\\.svg$/i,
use: ['@svgr/webpack'],
});
return config;
},`;
const turbopackConfig = ` turbopack: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},`;
const emptyConfigPattern = /const\s+\w+:\s*NextConfig\s*=\s*\{\s*\};/;
if (emptyConfigPattern.test(configContent)) {
configContent = configContent.replace(
emptyConfigPattern,
`const nextConfig: NextConfig = {
${webpackConfig}
${turbopackConfig}
};`
);
} else {
const configObjectPattern = /(const\s+\w+:\s*NextConfig\s*=\s*\{)([^}]*?)(\};)/s;
if (configObjectPattern.test(configContent)) {
configContent = configContent.replace(
configObjectPattern,
(_match, opening, content, closing) => {
const trimmedContent = content.trim();
if (trimmedContent) {
return `${opening}${content}
${webpackConfig}
${turbopackConfig}
${closing}`;
} else {
return `${opening}
${webpackConfig}
${turbopackConfig}
${closing}`;
}
}
);
}
}
writeFileSync(configPath, configContent);
spinner.succeed("Added SVGR configuration to next.config");
}
}
}
} catch (error) {
spinner.fail("Failed to update next.config");
logger.error(String(error));
}
const iconsSpinner = ora("Creating src/assets/icons/ directory...").start();
try {
const iconsDir = join(cwd, "src", "assets", "icons");
ensureDirSync(iconsDir);
const readmePath = join(iconsDir, "README.md");
const readmeContent = `# Icons
This directory manages SVG icons for the project.
## Usage
Import SVG files as React components using SVGR:
\`\`\`tsx
import Logo from '@/assets/icons/logo.svg';
function MyComponent() {
return (
<Logo className="size-8 text-gray-900 dark:text-white" />
);
}
\`\`\`
## Color Control
Use \`fill="currentColor"\` in your SVG files to control colors via Tailwind CSS:
\`\`\`tsx
// Light mode: gray-900, Dark mode: white
<Logo className="size-8 text-gray-900 dark:text-white" />
// Custom colors
<Icon className="size-6 text-blue-600" />
\`\`\`
## Adding New Icons
1. Add SVG file to this directory
2. Set \`fill="currentColor"\` for color control (optional)
3. Remove \`width\` and \`height\` attributes for flexible sizing
4. Import and use as React component
\`\`\`tsx
import NewIcon from '@/assets/icons/new-icon.svg';
\`\`\`
## Configuration
- **next.config.ts**: SVGR webpack loader configuration
- **Turbopack**: SVG loader rules for fast refresh
## Example SVG Structure
\`\`\`xml
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="..." fill="currentColor"/>
</svg>
\`\`\`
Note: Remove \`width\` and \`height\` for flexible sizing with Tailwind utilities.
`;
writeFileSync(readmePath, readmeContent);
iconsSpinner.succeed("Created src/assets/icons/ directory with README.md");
} catch (error) {
iconsSpinner.fail("Failed to create directory structure");
logger.error(String(error));
}
console.log("\n" + chalk.green.bold("\u2713 SVGR setup completed!\n"));
console.log("Next steps:");
console.log(" 1. Add SVG files to " + chalk.cyan("src/assets/icons/"));
console.log(" 2. Import them as React components:");
console.log(" " + chalk.gray("import Logo from '@/assets/icons/logo.svg';"));
console.log(" 3. Use with Tailwind classes:");
console.log(" " + chalk.gray('<Logo className="size-8 text-gray-900 dark:text-white" />'));
console.log("\nDocumentation: " + chalk.cyan("src/assets/icons/README.md"));
}
var setupCommand = new Command("setup").description("Setup additional features for your project");
setupCommand.command("icons").description("Setup SVGR for SVG icon management").action(setupIcons);
export {
setupIcons,
setupCommand
};