UNPKG

gluestack-ui

Version:

A CLI tool for easily adding components from gluestack to your projects.

379 lines (378 loc) 19.1 kB
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; (function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "fs-extra", "path", "@clack/prompts", "../../config", "../config"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.modifyLayoutFile = modifyLayoutFile; exports.modifyLayoutFilesAutomatically = modifyLayoutFilesAutomatically; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const prompts_1 = require("@clack/prompts"); const config_1 = require("../../config"); const config_2 = require("../config"); /** * Automatically modifies layout files to add GluestackUIProvider wrapper and CSS imports */ function modifyLayoutFile(options) { return __awaiter(this, void 0, void 0, function* () { const { layoutPath, projectType, cssPath, componentsPath, isNextjs15 } = options; try { if (!fs.existsSync(layoutPath)) { prompts_1.log.warning(`Layout file not found: ${layoutPath}`); return; } const content = yield fs.readFile(layoutPath, 'utf8'); // Check if already modified if (isLayoutAlreadyWrapped(content)) { prompts_1.log.info(`Layout file already contains GluestackUIProvider setup: ${path.basename(layoutPath)}`); return; } const isTypeScript = layoutPath.endsWith('.tsx') || layoutPath.endsWith('.ts'); let modifiedContent; if (projectType === 'nextjs') { modifiedContent = yield modifyNextJsLayout(content, cssPath, componentsPath, isTypeScript, isNextjs15); } else if (projectType === 'expo') { modifiedContent = yield modifyExpoLayout(content, cssPath, componentsPath, isTypeScript); } else if (projectType === 'react-native-cli') { modifiedContent = yield modifyReactNativeLayout(content, cssPath, componentsPath, isTypeScript); } else { prompts_1.log.warning(`Unsupported project type for layout modification: ${projectType}`); return; } if (modifiedContent !== content) { yield fs.writeFile(layoutPath, modifiedContent, 'utf8'); prompts_1.log.success(`✅ Modified layout file: ${path.basename(layoutPath)}`); } } catch (error) { prompts_1.log.error(`Error modifying layout file: ${error.message}`); throw error; } }); } /** * Check if layout is already wrapped with GluestackUIProvider */ function isLayoutAlreadyWrapped(content) { return (content.includes('GluestackUIProvider') && (content.includes('<GluestackUIProvider') || content.includes('GluestackUIProvider>')) && content.includes('gluestack-ui-provider')); } /** * Modify Next.js layout files (app router or pages router) */ function modifyNextJsLayout(content, cssPath, componentsPath, isTypeScript, isNextjs15) { return __awaiter(this, void 0, void 0, function* () { const cssImportPath = getCssImportPath(cssPath); const providerImportPath = `@/${componentsPath}/gluestack-ui-provider`; // Determine if this is App Router or Pages Router const isAppRouter = content.includes('export default function RootLayout') || content.includes('export default function Layout'); const isPagesRouter = content.includes('export default function App'); // For Next.js 15, only import the registry for App Router const registryImport = isNextjs15 && isAppRouter ? `import StyledJsxRegistry from './registry';` : ''; let imports = [ `import '${cssImportPath}';`, `import { GluestackUIProvider } from '${providerImportPath}';`, registryImport, ] .filter(Boolean) .join('\n'); // Add imports at the top (after existing imports) let modifiedContent = addImportsToFile(content, imports); // Wrap the children with provider (only if not already wrapped) if (isAppRouter) { // App Router layout const providerWrapper = isNextjs15 ? `<StyledJsxRegistry>\n <GluestackUIProvider mode="dark">\n {children}\n </GluestackUIProvider>\n </StyledJsxRegistry>` : `<GluestackUIProvider mode="dark">\n {children}\n </GluestackUIProvider>`; modifiedContent = modifiedContent.replace(/(\s*{children}\s*)/g, `\n ${providerWrapper}\n `); } else if (isPagesRouter) { // Pages Router _app.tsx - no StyledJsxRegistry needed here modifiedContent = modifiedContent.replace(/(<Component\s+{\.\.\.pageProps}\s*\/>)/g, `<GluestackUIProvider mode="dark">\n $1\n </GluestackUIProvider>`); } return modifiedContent; }); } /** * Modify Expo layout files (_layout.tsx) */ function modifyExpoLayout(content, cssPath, componentsPath, isTypeScript) { return __awaiter(this, void 0, void 0, function* () { const cssImportPath = getCssImportPath(cssPath); const providerImportPath = `@/${componentsPath}/gluestack-ui-provider`; const imports = [ `import { GluestackUIProvider } from '${providerImportPath}';`, `import '${cssImportPath}';`, ].join('\n'); // Add imports at the top let modifiedContent = addImportsToFile(content, imports); // Wrap the return content with provider if (content.includes('<Slot />')) { modifiedContent = modifiedContent.replace(/(\s*<Slot\s*\/>\s*)/g, `\n <GluestackUIProvider mode="dark">\n <Slot />\n </GluestackUIProvider>\n `); } else if (content.includes('return (')) { // Find the main return statement and wrap its content modifiedContent = modifiedContent.replace(/(return\s*\(\s*)([\s\S]*?)(\s*\);?\s*})/, (match, returnPart, content, closingPart) => { if (content.trim().startsWith('<GluestackUIProvider')) { return match; // Already wrapped } return `${returnPart}\n <GluestackUIProvider mode="dark">\n ${content.trim()}\n </GluestackUIProvider>\n ${closingPart}`; }); } return modifiedContent; }); } /** * Modify React Native CLI layout files (App.tsx) */ function modifyReactNativeLayout(content, cssPath, componentsPath, isTypeScript) { return __awaiter(this, void 0, void 0, function* () { const cssImportPath = getCssImportPath(cssPath); const providerImportPath = `@/${componentsPath}/gluestack-ui-provider`; const imports = [ `import { GluestackUIProvider } from '${providerImportPath}';`, `import '${cssImportPath}';`, ].join('\n'); // Add imports at the top let modifiedContent = addImportsToFile(content, imports); // Wrap the main App component return if (content.includes('function App') || content.includes('const App')) { modifiedContent = modifiedContent.replace(/(return\s*\(\s*)([\s\S]*?)(\s*\);?\s*})/, (match, returnPart, content, closingPart) => { if (content.trim().startsWith('<GluestackUIProvider')) { return match; // Already wrapped } return `${returnPart}\n <GluestackUIProvider mode="dark">\n ${content.trim()}\n </GluestackUIProvider>\n ${closingPart}`; }); } return modifiedContent; }); } /** * Enhanced addImportsToFile function to prevent duplicate imports */ function addImportsToFile(content, newImports) { // Split new imports into individual import statements const newImportLines = newImports.split('\n').filter(line => line.trim()); // Find existing imports const importRegex = /^import\s+.*?;?\s*$/gm; const existingImports = content.match(importRegex) || []; // Check which imports already exist to avoid duplicates const importsToAdd = []; newImportLines.forEach(newImport => { const newImportTrimmed = newImport.trim(); // Skip empty lines if (!newImportTrimmed) return; // Extract the import path for comparison const importPathMatch = newImportTrimmed.match(/from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]/); const importPath = importPathMatch ? (importPathMatch[1] || importPathMatch[2]) : ''; // For CSS imports, check the import path directly - FIXED REGEX const isCssImport = newImportTrimmed.match(/^import\s+['"][^'"]*\.(css|scss|sass|less)['"];?\s*$/); // Extract imported items for more precise matching (only for non-CSS imports) const importItemsMatch = newImportTrimmed.match(/import\s+(.+?)\s+from/); const importItems = importItemsMatch ? importItemsMatch[1].trim() : ''; // Check if this import already exists const isDuplicate = existingImports.some(existingImport => { const existingPathMatch = existingImport.match(/from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]/); const existingPath = existingPathMatch ? (existingPathMatch[1] || existingPathMatch[2]) : ''; // For CSS imports, just compare paths if (isCssImport) { const existingIsCssImport = existingImport.match(/^import\s+['"][^'"]*\.(css|scss|sass|less)['"];?\s*$/); return existingIsCssImport && existingPath === importPath; } const existingItemsMatch = existingImport.match(/import\s+(.+?)\s+from/); const existingItems = existingItemsMatch ? existingItemsMatch[1].trim() : ''; // Check if same path and similar import structure return existingPath === importPath && (existingItems === importItems || (importItems.includes('GluestackUIProvider') && existingItems.includes('GluestackUIProvider')) || (importItems.includes('StyledJsxRegistry') && existingItems.includes('StyledJsxRegistry'))); }); if (!isDuplicate) { importsToAdd.push(newImportTrimmed); } }); // If no new imports to add, return original content if (importsToAdd.length === 0) { return content; } if (existingImports.length > 0) { // Find the position after the last import const lastImport = existingImports[existingImports.length - 1]; const lastImportIndex = content.indexOf(lastImport) + lastImport.length; // Insert new imports after the last existing import return (content.slice(0, lastImportIndex) + '\n' + importsToAdd.join('\n') + '\n' + content.slice(lastImportIndex)); } else { // No existing imports, add at the top return importsToAdd.join('\n') + '\n' + content; } } /** * Get the correct CSS import path based on the CSS file location */ function getCssImportPath(cssPath) { if (cssPath.includes('global')) { return '@/' + cssPath; } else if (cssPath.startsWith('src/')) { return '@/' + cssPath; } else { return '@/' + cssPath; } } /** * Automatically modifies layout files during init process */ function modifyLayoutFilesAutomatically(projectType, resolvedConfig, permission) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; if (!permission) { prompts_1.log.info('Skipping layout file modification due to user preference.'); return; } try { // Use the resolved config to get the actual entry path const entryPath = (_a = resolvedConfig === null || resolvedConfig === void 0 ? void 0 : resolvedConfig.app) === null || _a === void 0 ? void 0 : _a.entry; const componentsPath = ((_b = resolvedConfig === null || resolvedConfig === void 0 ? void 0 : resolvedConfig.app) === null || _b === void 0 ? void 0 : _b.componentsPath) || ((_c = resolvedConfig === null || resolvedConfig === void 0 ? void 0 : resolvedConfig.app) === null || _c === void 0 ? void 0 : _c.components) || 'components/ui'; if (!entryPath) { prompts_1.log.warning('No entry path found in resolved config. Skipping layout modification.'); return; } // Determine CSS path based on project type const cssPath = yield getCSSPathForProject(projectType, resolvedConfig); // Check if this is Next.js 15 const isNextjs15 = projectType === 'nextjs' && (yield checkForNextjs15()); const options = { layoutPath: entryPath, projectType, cssPath, componentsPath, isNextjs15, }; prompts_1.log.info(`🔄 Automatically modifying layout file: ${path.basename(entryPath)}`); yield modifyLayoutFile(options); } catch (error) { prompts_1.log.error(`Failed to automatically modify layout files: ${error.message}`); prompts_1.log.info('You can manually add the GluestackUIProvider wrapper to your layout file.'); } }); } /** * Get CSS path based on project type and resolved config */ function getCSSPathForProject(projectType, resolvedConfig) { return __awaiter(this, void 0, void 0, function* () { var _a; if ((_a = resolvedConfig === null || resolvedConfig === void 0 ? void 0 : resolvedConfig.app) === null || _a === void 0 ? void 0 : _a.globalCssPath) { return resolvedConfig.app.globalCssPath; } // Try to detect existing CSS files in the project const detectedCssPath = yield (0, config_2.getFilePath)([ '**/*globals.css', '**/*global.css', ]); if (detectedCssPath) { return detectedCssPath; } // Fallback to default CSS paths for different project types switch (projectType) { case config_1.config.nextJsProject: return 'app/globals.css'; case config_1.config.expoProject: return 'global.css'; case config_1.config.reactNativeCLIProject: return 'global.css'; default: return 'global.css'; } }); } /** * Check if the project is using Next.js 15 */ function checkForNextjs15() { return __awaiter(this, void 0, void 0, function* () { var _a, _b; try { const packageJsonPath = path.join(process.cwd(), 'package.json'); if (!fs.existsSync(packageJsonPath)) { return false; } const packageJson = JSON.parse(yield fs.readFile(packageJsonPath, 'utf8')); const nextVersion = ((_a = packageJson.dependencies) === null || _a === void 0 ? void 0 : _a.next) || ((_b = packageJson.devDependencies) === null || _b === void 0 ? void 0 : _b.next); if (nextVersion) { // Check if version starts with 15 or ^15 or ~15 return /^[\^~]?15\./.test(nextVersion); } return false; } catch (error) { return false; } }); } });