diff --git a/packages/gluestack-cli/package.json b/packages/gluestack-cli/package.json index f7f2bb7f..18f23177 100644 --- a/packages/gluestack-cli/package.json +++ b/packages/gluestack-cli/package.json @@ -46,7 +46,7 @@ "@antfu/ni": "^0.21.12", "@clack/prompts": "^0.6.3", "@gluestack/ui-project-detector": "^0.1.1", - "chalk": "^5.3.0", + "chalk": "4.1.2", "commander": "^12.0.0", "fast-glob": "^3.3.2", "find-package-json": "^1.2.0", diff --git a/packages/gluestack-cli/src/dependencies.ts b/packages/gluestack-cli/src/dependencies.ts index 06e244bb..bbaddeed 100644 --- a/packages/gluestack-cli/src/dependencies.ts +++ b/packages/gluestack-cli/src/dependencies.ts @@ -1,11 +1,15 @@ export interface Dependency { [key: string]: string; } +export interface ComponentConfig { + dependencies: Dependency; + devDependencies?: Dependency; + additionalComponents?: string[]; + hooks?: string[]; +} + export interface Dependencies { - [key: string]: { - dependencies: Dependency; - devDependencies?: Dependency; - }; + [key: string]: ComponentConfig; } const projectBasedDependencies: Dependencies = { @@ -42,6 +46,7 @@ const dependenciesConfig: Dependencies = { '@gluestack-ui/toast': 'latest', '@gluestack-ui/nativewind-utils': 'latest', 'react-native-svg': '13.4.0', + nativewind: '4.1.10', }, devDependencies: { jscodeshift: '0.15.2', @@ -229,6 +234,35 @@ const dependenciesConfig: Dependencies = { view: { dependencies: {} }, 'virtualized-list': { dependencies: {} }, vstack: { dependencies: {} }, + grid: { + dependencies: {}, + hooks: ['useBreakpointValue'], + }, }; -export { dependenciesConfig, projectBasedDependencies }; +// Ignore components that are in development or not in supported list +const IgnoredComponents = ['bottomsheet']; + +const getComponentDependencies = (componentName: string): ComponentConfig => { + const config = dependenciesConfig[componentName]; + if (!config) { + return { + dependencies: {}, + devDependencies: {}, + additionalComponents: [], + hooks: [], + }; + } + return { + dependencies: config.dependencies || {}, + devDependencies: config.devDependencies || {}, + additionalComponents: config.additionalComponents || [], + hooks: config.hooks || [], + }; +}; +export { + dependenciesConfig, + projectBasedDependencies, + IgnoredComponents, + getComponentDependencies, +}; diff --git a/packages/gluestack-cli/src/util/add/index.ts b/packages/gluestack-cli/src/util/add/index.ts index 430051ef..cda15041 100644 --- a/packages/gluestack-cli/src/util/add/index.ts +++ b/packages/gluestack-cli/src/util/add/index.ts @@ -4,6 +4,7 @@ import { basename, join, parse } from 'path'; import { log, confirm } from '@clack/prompts'; import { config } from '../../config'; import { + checkAdditionalDependencies, cloneRepositoryAtRoot, getAllComponents, installDependencies, @@ -21,13 +22,14 @@ const componentAdder = async ({ try { console.log(`\n\x1b[1mAdding new component...\x1b[0m\n`); await cloneRepositoryAtRoot(join(_homeDir, config.gluestackDir)); + let hooksToAdd: string[] = []; if ( requestedComponent && requestedComponent !== '--all' && !(await checkIfComponentIsValid(requestedComponent)) ) { log.error( - `\x1b[31mThe ${requestedComponent} does not exist. Kindly choose a valid component name.\x1b[0m ` + `The ${requestedComponent} does not exist. Kindly choose a valid component name.` ); return; } @@ -36,6 +38,9 @@ const componentAdder = async ({ ? getAllComponents() : [requestedComponent]; + const { hooks } = checkAdditionalDependencies(requestedComponents); + hooksToAdd = Array.from(hooks); + const updatedComponents = !existingComponentsChecked && showWarning && requestedComponent ? await isComponentInConfig(requestedComponents) @@ -49,6 +54,7 @@ const componentAdder = async ({ ); await writeComponent(component, targetPath); + await hookAdder({ requestedHook: hooksToAdd }); }) ) .then(async () => { @@ -158,7 +164,11 @@ const confirmOverride = async ( return shouldContinue; }; -const hookAdder = async ({ requestedHook }: { requestedHook: string }) => { +const hookAdder = async ({ + requestedHook, +}: { + requestedHook: string | string[]; +}) => { try { console.log(`\n\x1b[1mAdding new hook...\x1b[0m\n`); await cloneRepositoryAtRoot(join(_homeDir, config.gluestackDir)); @@ -192,32 +202,35 @@ const hookFileName = async (hook: string): Promise => { }); return fileName; }; -const writeHook = async (hook: string) => { - const fileName = await hookFileName(hook); - const utilsPath = join( - projectRootPath, - config.writableComponentsPath, - 'utils', - fileName - ); - const sourceFilePath = join( - _homeDir, - config.gluestackDir, - config.hooksResourcePath, - fileName - ); - if (fs.existsSync(utilsPath)) { - const confirm = await confirmHookOverride(hook); - if (confirm === false) { - processTerminate('Installation aborted'); +const writeHook = async (hooks: string | string[]) => { + const hooksArray = Array.isArray(hooks) ? hooks : [hooks]; + for (const hook of hooksArray) { + const fileName = await hookFileName(hook); + const utilsPath = join( + projectRootPath, + config.writableComponentsPath, + 'utils', + fileName + ); + const sourceFilePath = join( + _homeDir, + config.gluestackDir, + config.hooksResourcePath, + fileName + ); + if (fs.existsSync(utilsPath)) { + const shouldOverride = await confirmHookOverride(hook); + if (!shouldOverride) { + processTerminate('Installation aborted'); + } } - } - try { - await fs.ensureFile(utilsPath); - fs.copyFileSync(sourceFilePath, utilsPath); - } catch (error) { - log.error(`\x1b[31mError: ${(error as Error).message}\x1b[0m`); + try { + await fs.ensureFile(utilsPath); + await fs.copy(sourceFilePath, utilsPath); + } catch (error) { + log.error(`Error adding hook ${hook}: ${(error as Error).message}`); + } } }; diff --git a/packages/gluestack-cli/src/util/config/expo-config-helper.ts b/packages/gluestack-cli/src/util/config/expo-config-helper.ts index e57d4760..e9fa0a0f 100644 --- a/packages/gluestack-cli/src/util/config/expo-config-helper.ts +++ b/packages/gluestack-cli/src/util/config/expo-config-helper.ts @@ -124,7 +124,7 @@ async function initNatiwindExpoApp( } --cssPath='${cssPath}' --config='${JSON.stringify(resolvedConfig)}'` ); execSync( - `npx jscodeshift -t ${BabeltransformerPath} ${resolvedConfig.config.babelConfig} --isSDK50='${resolvedConfig.app.sdk50}'` + `npx jscodeshift -t ${BabeltransformerPath} ${resolvedConfig.config.babelConfig} --config='${JSON.stringify(resolvedConfig)}'` ); execSync( `npx jscodeshift -t ${addProviderTransformerPath} ${resolvedConfig.app.entry} --cssImportPath='${cssImportPath}' --componentsPath='${config.writableComponentsPath}'` diff --git a/packages/gluestack-cli/src/util/config/next-config-helper.ts b/packages/gluestack-cli/src/util/config/next-config-helper.ts index 54fda1f6..feef2ef1 100644 --- a/packages/gluestack-cli/src/util/config/next-config-helper.ts +++ b/packages/gluestack-cli/src/util/config/next-config-helper.ts @@ -96,7 +96,7 @@ async function initNatiwindNextApp( ) { // if app router add registry file to root const registryContent = await readFile( - join(__dirname, `${config.templatesDir}/common/registry.tsx`), + join(__dirname, config.templatesDir, 'common', 'registry.tsx'), 'utf8' ); await writeFile(resolvedConfig.app.registry, registryContent, 'utf8'); diff --git a/packages/gluestack-cli/src/util/config/react-native-config-helper.ts b/packages/gluestack-cli/src/util/config/react-native-config-helper.ts index d7e7a49a..86218bad 100644 --- a/packages/gluestack-cli/src/util/config/react-native-config-helper.ts +++ b/packages/gluestack-cli/src/util/config/react-native-config-helper.ts @@ -66,7 +66,7 @@ async function initNatiwindRNApp( ); execSync( - `npx jscodeshift -t ${BabelTransformerPath} ${resolvedConfig.config.babelConfig}` + `npx jscodeshift -t ${BabelTransformerPath} ${resolvedConfig.config.babelConfig} --config='${JSON.stringify(resolvedConfig)}'` ); execSync( `npx jscodeshift -t ${metroTransformerPath} ${resolvedConfig.config.metroConfig}` diff --git a/packages/gluestack-cli/src/util/index.ts b/packages/gluestack-cli/src/util/index.ts index b447cac6..8d9c5555 100644 --- a/packages/gluestack-cli/src/util/index.ts +++ b/packages/gluestack-cli/src/util/index.ts @@ -16,10 +16,11 @@ import { import { Dependencies, Dependency, + IgnoredComponents, dependenciesConfig, + getComponentDependencies, projectBasedDependencies, } from '../dependencies'; -import { installNativeWind } from './init'; const homeDir = os.homedir(); const currDir = process.cwd(); @@ -46,11 +47,43 @@ const getAllComponents = (): string[] => { (file) => !['.tsx', '.ts', '.jsx', '.js', '.json'].includes( extname(file).toLowerCase() - ) && file !== config.providerComponent + ) && + file !== config.providerComponent && + !IgnoredComponents.includes(file) ); return componentList; }; +interface AdditionalDependencies { + components: string[]; + hooks: string[]; +} + +function checkAdditionalDependencies( + components: string[] +): AdditionalDependencies { + const additionalDependencies: AdditionalDependencies = { + components: [], + hooks: [], + }; + + components.forEach((component) => { + const config = getComponentDependencies(component); + + // Add additional components + config.additionalComponents?.forEach((additionalComponent) => { + additionalDependencies.components.push(additionalComponent); + }); + + // Add hooks + config.hooks?.forEach((hook) => { + additionalDependencies.hooks.push(hook); + }); + }); + + return additionalDependencies; +} + const cloneRepositoryAtRoot = async (rootPath: string) => { try { const clonedRepoExists = await checkIfFolderExists(rootPath); @@ -285,7 +318,6 @@ const installDependencies = async ( ); try { - await installNativeWind(versionManager); let depResult; let devDepResult; @@ -572,4 +604,5 @@ export { removeHyphen, getRelativePath, ensureFilesPromise, + checkAdditionalDependencies, }; diff --git a/packages/gluestack-cli/src/util/init/index.ts b/packages/gluestack-cli/src/util/init/index.ts index 276833ac..8cf1446b 100644 --- a/packages/gluestack-cli/src/util/init/index.ts +++ b/packages/gluestack-cli/src/util/init/index.ts @@ -97,17 +97,7 @@ async function addProvider() { } } -function createDefaultTSConfig() { - return { - compilerOptions: { - paths: { - '@/*': ['./*'], - }, - }, - exclude: ['node_modules'], - }; -} - +//update tailwind.config.js and codemod async function updateTailwindConfig( resolvedConfig: RawConfig, projectType: string @@ -135,52 +125,67 @@ async function updateTailwindConfig( } } -async function updateTSConfig( - projectType: string, - configPath: string -): Promise { +//updateConfig helper, create default tsconfig.json +function createDefaultTSConfig() { + return { + compilerOptions: { + paths: { + '@/*': ['./*'], + }, + }, + exclude: ['node_modules'], + }; +} +// updateConfig helper, read tsconfig.json +async function readTSConfig(configPath: string): Promise { try { - let tsConfig: TSConfig = {}; - try { - tsConfig = JSON.parse(await readFileAsync(configPath, 'utf8')); - } catch { - //write another function if file is empty - tsConfig = createDefaultTSConfig(); - } + return JSON.parse(await readFileAsync(configPath, 'utf8')); + } catch { + return createDefaultTSConfig(); + } +} +// updateConfig helper, update paths in tsconfig.json +function updatePaths( + paths: Record, + key: string, + newValues: string[] +): void { + paths[key] = Array.from(new Set([...(paths[key] || []), ...newValues])); +} +//update tsconfig.json +async function updateTSConfig(projectType: string, config: any): Promise { + try { + const configPath = config.config.tsConfig; + let tsConfig: TSConfig = await readTSConfig(configPath); + let tailwindConfig = config.tailwind.config; + const tailwindConfigFileName = path.basename(tailwindConfig); + tsConfig.compilerOptions = tsConfig.compilerOptions || {}; + tsConfig.compilerOptions.paths = tsConfig.compilerOptions.paths || {}; // Next.js project specific configuration if (projectType === config.nextJsProject) { tsConfig.compilerOptions.jsxImportSource = 'nativewind'; } - if (!tsConfig.compilerOptions.paths) { - // Case 1: Paths do not exist, add new paths - tsConfig.compilerOptions.paths = { - '@/*': ['./*'], - }; - } else { - // Case 2 & 3: Paths exist, update them without undoing previous values - const paths = tsConfig.compilerOptions.paths['@/*']; - if (!paths.includes('./*')) { - // If './*' is not included, add it - paths.push('./*'); - } - } + updatePaths(tsConfig.compilerOptions.paths, '@/*', ['./*']); + updatePaths(tsConfig.compilerOptions.paths, 'tailwind.config', [ + `./${tailwindConfigFileName}`, + ]); + await writeFileAsync(configPath, JSON.stringify(tsConfig, null, 2), 'utf8'); } catch (err) { log.error( - `\x1b[31mError occured while installing dependencies (${ - err as Error - })\x1b[0m` + `\x1b[31mError occurred while updating tsconfig.json: ${(err as Error).message}\x1b[0m` ); } } +//update global.css async function updateGlobalCss(resolvedConfig: RawConfig): Promise { try { const globalCSSPath = resolvedConfig.tailwind.css; const globalCSSContent = await fs.readFile( - join(__dirname, config.templatesDir, 'common/global.css'), + join(__dirname, config.templatesDir, 'common', 'global.css'), 'utf8' ); const existingContent = await fs.readFile(globalCSSPath, 'utf8'); @@ -232,11 +237,10 @@ async function commonInitialization( //add nativewind-env.d.ts contents await fs.copy( - join(__dirname, `${config.templatesDir}/common/nativewind-env.d.ts`), + join(__dirname, config.templatesDir, 'common', 'nativewind-env.d.ts'), join(_currDir, 'nativewind-env.d.ts') ); - permission && - (await updateTSConfig(projectType, resolvedConfig.config.tsConfig)); + permission && (await updateTSConfig(projectType, resolvedConfig)); permission && (await updateGlobalCss(resolvedConfig)); await updateTailwindConfig(resolvedConfig, projectType); @@ -387,4 +391,4 @@ ${files return confirmInput; } -export { InitializeGlueStack, commonInitialization, installNativeWind }; +export { InitializeGlueStack, commonInitialization }; diff --git a/packages/gluestack-cli/template/codemods/expo/babel-config-transform.ts b/packages/gluestack-cli/template/codemods/expo/babel-config-transform.ts index 62b2e8fc..11fb06c7 100644 --- a/packages/gluestack-cli/template/codemods/expo/babel-config-transform.ts +++ b/packages/gluestack-cli/template/codemods/expo/babel-config-transform.ts @@ -4,12 +4,15 @@ import { ObjectExpression, Property, } from 'jscodeshift'; +import { ExpoResolvedConfig } from '../../../src/util/config/config-types'; const transform: Transform = (file, api, options): string => { try { const j = api.jscodeshift; const root = j(file.source); - const isSDK50 = options.isSDK50; + const config: ExpoResolvedConfig = options.config; + const isSDK50 = config.app.sdk50; + const tailwindConfig = config.tailwind.config; root.find(j.ReturnStatement).forEach((path) => { const returnObject = path.node.argument as ObjectExpression | null; @@ -73,6 +76,10 @@ const transform: Transform = (file, api, options): string => { } } + // fetch tailwind config filenName from resolved path of tailwind.config.js + const parts = tailwindConfig.split(/[/\\]/); + const tailwindConfigFileName = parts[parts.length - 1]; + //plugin code modification const moduleResolverPlugin = j.arrayExpression([ j.stringLiteral('module-resolver'), @@ -85,6 +92,10 @@ const transform: Transform = (file, api, options): string => { j.identifier('alias'), j.objectExpression([ j.objectProperty(j.stringLiteral('@'), j.stringLiteral('./')), + j.objectProperty( + j.stringLiteral('tailwindConfig'), + j.stringLiteral(`./` + tailwindConfigFileName) + ), ]) ), ]), diff --git a/packages/gluestack-cli/template/codemods/react-native-cli/babel-config-transform.ts b/packages/gluestack-cli/template/codemods/react-native-cli/babel-config-transform.ts index 85b819ea..09af75a0 100644 --- a/packages/gluestack-cli/template/codemods/react-native-cli/babel-config-transform.ts +++ b/packages/gluestack-cli/template/codemods/react-native-cli/babel-config-transform.ts @@ -1,9 +1,16 @@ import { Transform } from 'jscodeshift'; +import { ReactNativeResolvedConfig } from '../../../src/util/config/config-types'; -const transform: Transform = (file, api) => { +const transform: Transform = (file, api, options) => { try { const j = api.jscodeshift; const root = j(file.source); + const config: ReactNativeResolvedConfig = options.config; + const tailwindConfig = config.tailwind.config; + + // fetch tailwind config filenName from resolved path of tailwind.config.js + const parts = tailwindConfig.split(/[/\\]/); + const tailwindConfigFileName = parts[parts.length - 1]; // Find the module.exports assignment const moduleExports = root.find(j.AssignmentExpression, { @@ -97,6 +104,11 @@ const transform: Transform = (file, api) => { j.identifier('alias'), j.objectExpression([ j.property('init', j.stringLiteral('@'), j.stringLiteral('./')), + j.property( + 'init', + j.stringLiteral('tailwind.config'), + j.stringLiteral('./' + tailwindConfig) + ), ]) ), ]), @@ -155,6 +167,11 @@ const transform: Transform = (file, api) => { j.identifier('alias'), j.objectExpression([ j.property('init', j.stringLiteral('@'), j.stringLiteral('./')), + j.property( + 'init', + j.stringLiteral('tailwind.config'), + j.stringLiteral('./' + tailwindConfigFileName) + ), ]) ); moduleResolverConfig.properties.push(aliasProp); @@ -165,6 +182,19 @@ const transform: Transform = (file, api) => { aliasProp.value.properties.push( j.property('init', j.stringLiteral('@'), j.stringLiteral('./')) ); + } else if ( + aliasProp.value.type === 'ObjectExpression' && + !aliasProp.value.properties.some( + (p) => p.key.value === 'tailwind.config' + ) + ) { + aliasProp.value.properties.push( + j.property( + 'init', + j.stringLiteral('tailwind.config'), + j.stringLiteral('./' + tailwindConfigFileName) + ) + ); } } } diff --git a/packages/gluestack-cli/yarn.lock b/packages/gluestack-cli/yarn.lock index 31102c30..d04f7df9 100644 --- a/packages/gluestack-cli/yarn.lock +++ b/packages/gluestack-cli/yarn.lock @@ -3167,6 +3167,14 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== +chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -3184,14 +3192,6 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"