diff --git a/package.json b/package.json index 7b2d8ff..40cbeea 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ }, "dependencies": { "@clack/prompts": "0.7.0", - "@winches/prompts": "0.0.6", + "@winches/prompts": "0.0.7", "async-retry": "1.3.3", "chalk": "5.3.0", "commander": "11.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12d0161..270c5f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: 0.7.0 version: 0.7.0 '@winches/prompts': - specifier: 0.0.6 - version: 0.0.6 + specifier: 0.0.7 + version: 0.0.7 async-retry: specifier: 1.3.3 version: 1.3.3 @@ -665,8 +665,8 @@ packages: resolution: {integrity: sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ==} engines: {node: ^16.0.0 || >=18.0.0} - '@winches/prompts@0.0.6': - resolution: {integrity: sha512-8fdmmbad8L2ldIaRSkoCTBeDxmc20CDcmA1d1ut2xFKZoHZlV/VTSffuDChQbHzFhdeYFefjDZn3MJ+06RtYQg==} + '@winches/prompts@0.0.7': + resolution: {integrity: sha512-H/UlFlm83C6zkYn3CK4GF2VMpAlyjO3o623yamOyHwSaehhnj7oAJmklvr73UVMqu6Q+rl4Bzs3PBmxGB8cJVg==} engines: {node: '>= 14'} JSONStream@1.3.5: @@ -3327,7 +3327,7 @@ snapshots: '@typescript-eslint/types': 6.7.2 eslint-visitor-keys: 3.4.3 - '@winches/prompts@0.0.6': + '@winches/prompts@0.0.7': dependencies: kleur: 4.1.5 sisteransi: 1.0.5 diff --git a/src/actions/add-action.ts b/src/actions/add-action.ts index 0a56047..74048ef 100644 --- a/src/actions/add-action.ts +++ b/src/actions/add-action.ts @@ -6,6 +6,7 @@ import {existsSync, writeFileSync} from 'node:fs'; import chalk from 'chalk'; import {getBetaComponents} from '@helpers/beta'; +import {getCanaryComponents} from '@helpers/canary'; import { checkApp, checkIllegalComponents, @@ -38,6 +39,7 @@ interface AddActionOptions { appPath?: string; addApp?: boolean; beta?: boolean; + canary?: boolean; } export async function addAction(components: string[], options: AddActionOptions) { @@ -46,6 +48,7 @@ export async function addAction(components: string[], options: AddActionOptions) all = false, appPath = findFiles('**/App.(j|t)sx')[0], beta = false, + canary = false, packagePath = resolver('package.json'), tailwindPath = findFiles('**/tailwind.config.(j|t)s')[0] } = options; @@ -122,7 +125,7 @@ export async function addAction(components: string[], options: AddActionOptions) const [, ...missingDependencies] = await checkRequiredContentInstalled( 'all', allDependenciesKeys, - {beta} + {beta, canary} ); if (missingDependencies.length) { @@ -141,11 +144,13 @@ export async function addAction(components: string[], options: AddActionOptions) const [, ..._missingDependencies] = await checkRequiredContentInstalled( 'partial', allDependenciesKeys, - {beta} + {beta, canary} ); const mergedComponents = beta ? await getBetaComponents(components) - : components.map((c) => store.nextUIComponentsMap[c]!.package); + : canary + ? await getCanaryComponents(components) + : components.map((c) => store.nextUIComponentsMap[c]!.package); const missingDependencies = [..._missingDependencies, ...mergedComponents]; Logger.info( diff --git a/src/actions/upgrade-action.ts b/src/actions/upgrade-action.ts index 92e9da0..9feea0d 100644 --- a/src/actions/upgrade-action.ts +++ b/src/actions/upgrade-action.ts @@ -3,7 +3,8 @@ import type {AppendKeyValue} from '@helpers/type'; import fs from 'node:fs'; import {getBetaVersion} from '@helpers/beta'; -import {checkIllegalComponents} from '@helpers/check'; +import {getCanaryVersion} from '@helpers/canary'; +import {checkIllegalComponents, getConditionLatestVersion} from '@helpers/check'; import {detect} from '@helpers/detect'; import {exec} from '@helpers/exec'; import {Logger} from '@helpers/logger'; @@ -27,13 +28,19 @@ interface UpgradeActionOptions { patch?: boolean; write?: boolean; beta?: boolean; + canary?: boolean; } type TransformComponent = Required< AppendKeyValue & {isLatest: boolean} >; -function betaCompareVersions(version: string, latestVersion: string, beta: boolean) { +function extraCompareVersions( + version: string, + latestVersion: string, + beta: boolean, + canary: boolean +) { // compareResult(beta, 2.1.0) = 0 // So we need to check if it is autoChangeTag like `beta` or `canary` and latestVersion is not match `beta` or `canary` then return false // Example: `beta` Compare `2.1.0` (not latest), `beta` Compare `2.1.0-beta.0` (latest) @@ -48,13 +55,18 @@ function betaCompareVersions(version: string, latestVersion: string, beta: boole // Beta version is greater than latest version if beta is true // compareResult(2.1.0, 2.1.0-beta.0) = 1 // Example: 2.1.0 < 2.1.0-beta.0 - return beta && compareResult === 1 && !version.includes('beta') ? false : compareResult >= 0; + return beta && compareResult === 1 && !version.includes('beta') + ? false + : canary && compareResult === 1 && !version.includes('canary') + ? false + : compareResult >= 0; } export async function upgradeAction(components: string[], options: UpgradeActionOptions) { const { all = false, beta = false, + canary = false, packagePath = resolver('package.json'), write = false } = options; @@ -70,8 +82,12 @@ export async function upgradeAction(components: string[], options: UpgradeAction const latestVersion = store.nextUIComponentsMap[component.name]?.version || (await getLatestVersion(component.package)); - const mergedVersion = beta ? await getBetaVersion(component.package) : latestVersion; - const compareResult = betaCompareVersions(component.version, mergedVersion, beta); + const mergedVersion = beta + ? await getBetaVersion(component.package) + : canary + ? await getCanaryVersion(component.package) + : latestVersion; + const compareResult = extraCompareVersions(component.version, mergedVersion, beta, canary); transformComponents.push({ ...component, @@ -93,12 +109,13 @@ export async function upgradeAction(components: string[], options: UpgradeAction } else if (!components.length) { // If have the main nextui then add if (isNextUIAll) { + const version = transformPeerVersion(allDependencies[NEXT_UI]); + const latestVersion = getConditionLatestVersion(store.beta, store.canary); const nextuiData = { - isLatest: - compareVersions(store.latestVersion, transformPeerVersion(allDependencies[NEXT_UI])) <= 0, - latestVersion: store.latestVersion, + isLatest: extraCompareVersions(version, latestVersion, store.beta, store.canary), + latestVersion, package: NEXT_UI, - version: transformPeerVersion(allDependencies[NEXT_UI]) + version } as TransformComponent; transformComponents.push(nextuiData); @@ -113,7 +130,12 @@ export async function upgradeAction(components: string[], options: UpgradeAction components = await getAutocompleteMultiselect( 'Select the components to upgrade', transformComponents.map((component) => { - const isUpToDate = betaCompareVersions(component.version, component.latestVersion, beta); + const isUpToDate = extraCompareVersions( + component.version, + component.latestVersion, + beta, + canary + ); return { disabled: isUpToDate, diff --git a/src/constants/component.ts b/src/constants/component.ts index 89c06b7..6594cab 100644 --- a/src/constants/component.ts +++ b/src/constants/component.ts @@ -29,10 +29,12 @@ export function getNextuiComponentsData(nextUIComponents: Components) { } export function initStoreComponentsData({ - beta, + beta = false, + canary = false, nextUIComponents }: { - beta: boolean; + beta?: boolean; + canary?: boolean; nextUIComponents: Components; }) { const { @@ -50,6 +52,13 @@ export function initStoreComponentsData({ store.betaNextUIComponentsMap = nextUIComponentsMap; store.betaNextUIComponentsPackageMap = nextUIComponentsPackageMap; store.betaNextUIcomponentsPackages = nextUIcomponentsPackages; + } else if (canary) { + store.canaryNextUIComponents = nextUIComponents; + store.canaryNextUIComponentsKeys = nextUIComponentsKeys; + store.canaryNextUIComponentsKeysSet = nextUIComponentsKeysSet; + store.canaryNextUIComponentsMap = nextUIComponentsMap; + store.canaryNextUIComponentsPackageMap = nextUIComponentsPackageMap; + store.canaryNextUIcomponentsPackages = nextUIcomponentsPackages; } else { store.nextUIComponents = nextUIComponents; store.nextUIComponentsKeys = nextUIComponentsKeys; diff --git a/src/constants/components.json b/src/constants/components.json index eb3d5d9..28ff109 100644 --- a/src/constants/components.json +++ b/src/constants/components.json @@ -1430,7 +1430,756 @@ "@nextui-org/system": ">=2.4.0", "tailwindcss": ">=3.4.0" } + }, + { + "name": "form", + "package": "@nextui-org/form", + "version": "2.0.1", + "docs": "https://nextui.org/docs/components/form", + "description": "A form is a group of inputs that allows users submit data to a server and supports field validation errors.", + "status": "new", + "style": "", + "peerDependencies": { + "@nextui-org/system": ">=2.3.0-beta.0", + "@nextui-org/theme": ">=2.3.0-beta.0", + "react": ">=18", + "react-dom": ">=18", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "input-otp", + "package": "@nextui-org/input-otp", + "version": "2.0.1", + "docs": "https://nextui.org/docs/components/input-otp", + "description": "", + "status": "new", + "style": "", + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18", + "@nextui-org/theme": ">=2.3.0-beta.0", + "@nextui-org/system": ">=2.3.0-beta.0", + "tailwindcss": ">=3.4.0" + } + } + ], + "version": "2.6.5", + "canaryComponents": [ + { + "name": "accordion", + "package": "@nextui-org/accordion", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/accordion", + "description": "Collapse display a list of high-level options that can expand/collapse to reveal more information.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "alert", + "package": "@nextui-org/alert", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/alert", + "description": "Alerts are temporary notifications that provide concise feedback about an action or event.", + "status": "new", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "autocomplete", + "package": "@nextui-org/autocomplete", + "version": "2.3.4", + "docs": "https://nextui.org/docs/components/autocomplete", + "description": "An autocomplete combines a text input with a listbox, allowing users to filter a list of options to items matching a query.", + "status": "updated", + "style": "", + "peerDependencies": { + "@nextui-org/system": ">=2.4.0", + "@nextui-org/theme": ">=2.4.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "avatar", + "package": "@nextui-org/avatar", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/avatar", + "description": "The Avatar component is used to represent a user, and displays the profile picture, initials or fallback icon.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "badge", + "package": "@nextui-org/badge", + "version": "2.2.3", + "docs": "https://nextui.org/docs/components/badge", + "description": "Badges are used as a small numerical value or status descriptor for UI elements.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "breadcrumbs", + "package": "@nextui-org/breadcrumbs", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/breadcrumbs", + "description": "Breadcrumbs display a hierarchy of links to the current page or resource in an application.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "button", + "package": "@nextui-org/button", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/button", + "description": "Buttons allow users to perform actions and choose with a single tap.", + "status": "updated", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "calendar", + "package": "@nextui-org/calendar", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/calendar", + "description": "A calendar displays one or more date grids and allows users to select a single date.", + "status": "stable", + "style": "", + "peerDependencies": { + "@nextui-org/system": ">=2.4.0", + "@nextui-org/theme": ">=2.4.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "card", + "package": "@nextui-org/card", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/card", + "description": "Card is a container for text, photos, and actions in the context of a single subject.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "checkbox", + "package": "@nextui-org/checkbox", + "version": "2.3.4", + "docs": "https://nextui.org/docs/components/checkbox", + "description": "Checkboxes allow users to select multiple items from a list of individual items, or to mark one individual item as selected.", + "status": "updated", + "style": "", + "peerDependencies": { + "@nextui-org/system": ">=2.4.0", + "@nextui-org/theme": ">=2.4.0", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "chip", + "package": "@nextui-org/chip", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/chip", + "description": "Chips help people enter information, make selections, filter content, or trigger actions.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "code", + "package": "@nextui-org/code", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/code", + "description": "Code is a component used to display inline code.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "date-input", + "package": "@nextui-org/date-input", + "version": "2.3.4", + "docs": "https://nextui.org/docs/components/date-input", + "description": "A date input allows users to enter and edit date and time values using a keyboard.", + "status": "stable", + "style": "", + "peerDependencies": { + "@nextui-org/system": ">=2.4.0", + "@nextui-org/theme": ">=2.4.0", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "date-picker", + "package": "@nextui-org/date-picker", + "version": "2.3.4", + "docs": "https://nextui.org/docs/components/date-picker", + "description": "A date picker combines a DateInput and a Calendar popover to allow users to enter or select a date and time value.", + "status": "stable", + "style": "", + "peerDependencies": { + "@nextui-org/system": ">=2.4.0", + "@nextui-org/theme": ">=2.4.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "divider", + "package": "@nextui-org/divider", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/divider", + "description": ". A separator is a visual divider between two groups of content", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "drawer", + "package": "@nextui-org/drawer", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/drawer", + "description": "Used to render a content that slides in from the side of the screen.", + "status": "new", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "dropdown", + "package": "@nextui-org/dropdown", + "version": "2.3.4", + "docs": "https://nextui.org/docs/components/dropdown", + "description": "A dropdown displays a list of actions or options that a user can choose.", + "status": "stable", + "style": "", + "peerDependencies": { + "@nextui-org/system": ">=2.4.0", + "@nextui-org/theme": ">=2.4.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "form", + "package": "@nextui-org/form", + "version": "2.1.4", + "docs": "https://nextui.org/docs/components/form", + "description": "A form is a group of inputs that allows users submit data to a server and supports field validation errors.", + "status": "new", + "style": "", + "peerDependencies": { + "@nextui-org/system": ">=2.4.0", + "@nextui-org/theme": ">=2.4.0", + "react": ">=18", + "react-dom": ">=18", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "image", + "package": "@nextui-org/image", + "version": "2.2.3", + "docs": "https://nextui.org/docs/components/image", + "description": "A simple image component", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "input", + "package": "@nextui-org/input", + "version": "2.4.4", + "docs": "https://nextui.org/docs/components/input", + "description": "The input component is designed for capturing user input within a text field.", + "status": "updated", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "input-otp", + "package": "@nextui-org/input-otp", + "version": "2.1.4", + "docs": "https://nextui.org/docs/components/input-otp", + "description": "", + "status": "new", + "style": "", + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "kbd", + "package": "@nextui-org/kbd", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/kbd", + "description": "The keyboard key components indicates which key or set of keys used to execute a specificv action", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "link", + "package": "@nextui-org/link", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/link", + "description": "Links allow users to click their way from page to page. This component is styled to resemble a hyperlink and semantically renders an <a>", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "listbox", + "package": "@nextui-org/listbox", + "version": "2.3.4", + "docs": "https://nextui.org/docs/components/listbox", + "description": "A listbox displays a list of options and allows a user to select one or more of them.", + "status": "updated", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "menu", + "package": "@nextui-org/menu", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/menu", + "description": "A menu displays a list of options and allows a user to select one or more of them.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "modal", + "package": "@nextui-org/modal", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/modal", + "description": "Displays a dialog with a custom content that requires attention or provides additional information.", + "status": "updated", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "navbar", + "package": "@nextui-org/navbar", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/navbar", + "description": "A responsive navigation header positioned on top side of your page that includes support for branding, links, navigation, collapse and more.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "pagination", + "package": "@nextui-org/pagination", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/pagination", + "description": "The Pagination component allows you to display active page and navigate between multiple pages.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "popover", + "package": "@nextui-org/popover", + "version": "2.3.4", + "docs": "https://nextui.org/docs/components/popover", + "description": "A popover is an overlay element positioned relative to a trigger.", + "status": "stable", + "style": "", + "peerDependencies": { + "@nextui-org/system": ">=2.4.0", + "@nextui-org/theme": ">=2.4.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "progress", + "package": "@nextui-org/progress", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/progress", + "description": "Progress bars show either determinate or indeterminate progress of an operation over time.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "radio", + "package": "@nextui-org/radio", + "version": "2.3.4", + "docs": "https://nextui.org/docs/components/radio", + "description": "Radios allow users to select a single option from a list of mutually exclusive options.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "ripple", + "package": "@nextui-org/ripple", + "version": "2.2.3", + "docs": "https://nextui.org/docs/components/ripple", + "description": "A simple implementation to display a ripple animation when the source component is clicked", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "scroll-shadow", + "package": "@nextui-org/scroll-shadow", + "version": "2.3.3", + "docs": "https://nextui.org/docs/components/scroll-shadow", + "description": "A component that applies top and bottom shadows when content overflows on scroll.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "select", + "package": "@nextui-org/select", + "version": "2.4.4", + "docs": "https://nextui.org/docs/components/select", + "description": "A select displays a collapsible list of options and allows a user to select one of them.", + "status": "updated", + "style": "", + "peerDependencies": { + "@nextui-org/system": ">=2.4.0", + "@nextui-org/theme": ">=2.4.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "skeleton", + "package": "@nextui-org/skeleton", + "version": "2.2.3", + "docs": "https://nextui.org/docs/components/skeleton", + "description": "Skeleton is used to display the loading state of some component.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "slider", + "package": "@nextui-org/slider", + "version": "2.4.4", + "docs": "https://nextui.org/docs/components/slider", + "description": "A slider allows a user to select one or more values within a range.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "snippet", + "package": "@nextui-org/snippet", + "version": "2.2.5", + "docs": "https://nextui.org/docs/components/snippet", + "description": "Display a snippet of copyable code for the command line.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "spacer", + "package": "@nextui-org/spacer", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/spacer", + "description": "A flexible spacer component designed to create consistent spacing and maintain alignment in your layout.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "spinner", + "package": "@nextui-org/spinner", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/spinner", + "description": "Loaders express an unspecified wait time or display the length of a process.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "switch", + "package": "@nextui-org/switch", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/switch", + "description": "A switch is similar to a checkbox, but represents on/off values as opposed to selection.", + "status": "updated", + "style": "toggle", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "table", + "package": "@nextui-org/table", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/table", + "description": "Tables are used to display tabular data using rows and columns. ", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "tabs", + "package": "@nextui-org/tabs", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/tabs", + "description": "Tabs organize content into multiple sections and allow users to navigate between them.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "tooltip", + "package": "@nextui-org/tooltip", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/tooltip", + "description": "A React Component for rendering dynamically positioned Tooltips", + "status": "stable", + "style": "popover", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } + }, + { + "name": "user", + "package": "@nextui-org/user", + "version": "2.2.4", + "docs": "https://nextui.org/docs/components/user", + "description": "Flexible User Profile Component.", + "status": "stable", + "style": "", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0", + "@nextui-org/theme": ">=2.4.0", + "@nextui-org/system": ">=2.4.0", + "tailwindcss": ">=3.4.0" + } } ], - "version": "2.6.4" + "canaryVersion": "0.0.0-canary-20241209160218" } diff --git a/src/constants/store.ts b/src/constants/store.ts index 6dfd692..c6a0f0b 100644 --- a/src/constants/store.ts +++ b/src/constants/store.ts @@ -1,6 +1,7 @@ import type {ExtractStoreData, SAFE_ANY} from '@helpers/type'; import {getBetaVersion} from '@helpers/beta'; +import {getCanaryVersion} from '@helpers/canary'; import {type Components, getLatestVersion} from 'src/scripts/helpers'; import {NEXTUI_CLI, NEXT_UI} from './required'; @@ -10,9 +11,12 @@ export type NextUIComponentsMap = Record; export type Store = { debug: boolean; beta: boolean; + canary: boolean; + cliLatestVersion: string; latestVersion: string; betaVersion: string; + canaryVersion: string; // NextUI nextUIComponents: Components; @@ -29,15 +33,26 @@ export type Store = { betaNextUIComponentsKeysSet: Set; betaNextUIComponentsMap: NextUIComponentsMap; betaNextUIComponentsPackageMap: NextUIComponentsMap; + + // Canary NextUI + canaryNextUIComponents: Components; + canaryNextUIComponentsKeys: string[]; + canaryNextUIcomponentsPackages: string[]; + canaryNextUIComponentsKeysSet: Set; + canaryNextUIComponentsMap: NextUIComponentsMap; + canaryNextUIComponentsPackageMap: NextUIComponentsMap; }; /* eslint-disable sort-keys-fix/sort-keys-fix, sort-keys */ export const store = { debug: false, beta: false, + canary: false, + cliLatestVersion: '', latestVersion: '', betaVersion: '', + canaryVersion: '', betaNextUIComponents: [], betaNextUIComponentsKeys: [], @@ -51,7 +66,14 @@ export const store = { nextUIComponentsKeysSet: new Set(), nextUIComponentsMap: {}, nextUIComponentsPackageMap: {}, - nextUIcomponentsPackages: [] + nextUIcomponentsPackages: [], + + canaryNextUIComponents: [], + canaryNextUIComponentsKeys: [], + canaryNextUIcomponentsPackages: [], + canaryNextUIComponentsKeysSet: new Set(), + canaryNextUIComponentsMap: {}, + canaryNextUIComponentsPackageMap: {} } as Store; /* eslint-enable sort-keys-fix/sort-keys-fix, sort-keys */ @@ -74,6 +96,10 @@ export async function getStore( } else if (key === 'betaVersion') { data = (await getBetaVersion(NEXT_UI)) as SAFE_ANY; + store[key] = data; + } else if (key === 'canaryVersion') { + data = (await getCanaryVersion(NEXT_UI)) as SAFE_ANY; + store[key] = data; } } diff --git a/src/helpers/beta.ts b/src/helpers/beta.ts index c5c5adb..ca6994f 100644 --- a/src/helpers/beta.ts +++ b/src/helpers/beta.ts @@ -3,7 +3,7 @@ import {getCacheExecData} from 'src/scripts/cache/cache'; import {Logger} from './logger'; -export async function getBetaVersionData(component: string) { +export async function getPackageVersionData(component: string) { const data = await getCacheExecData( `npm view ${component} dist-tags --json`, `Fetching ${component} tags` @@ -21,7 +21,7 @@ export async function getBetaVersion(componentName: string) { return store.betaNextUIComponentsPackageMap[componentName]!.version; } - const data = await getBetaVersionData(componentName); + const data = await getPackageVersionData(componentName); try { return JSON.parse(data).beta; diff --git a/src/helpers/canary.ts b/src/helpers/canary.ts new file mode 100644 index 0000000..860f149 --- /dev/null +++ b/src/helpers/canary.ts @@ -0,0 +1,37 @@ +import {store} from 'src/constants/store'; + +import {getPackageVersionData, getPrefixComponent} from './beta'; +import {Logger} from './logger'; + +export async function getCanaryVersion(componentName: string) { + if (store.canaryNextUIComponentsPackageMap[componentName]) { + return store.canaryNextUIComponentsPackageMap[componentName]!.version; + } + + const data = await getPackageVersionData(componentName); + + try { + return JSON.parse(data).canary; + } catch (error) { + Logger.error(`Get canary version error: ${error}`); + process.exit(1); + } +} + +/** + * @example Input: ["drawer"] + * + * Return: + * ["@nextui-org/drawer@canary"] + */ +export async function getCanaryComponents(components: string[]) { + const componentsVersionList = await Promise.all( + components.map(getPrefixComponent).map(async (c) => { + const version = await getCanaryVersion(c); + + return `${getPrefixComponent(c)}@${version}`; + }) + ); + + return componentsVersionList; +} diff --git a/src/helpers/check.ts b/src/helpers/check.ts index aa83c45..1a69c83 100644 --- a/src/helpers/check.ts +++ b/src/helpers/check.ts @@ -22,7 +22,7 @@ import { import {store} from 'src/constants/store'; import {compareVersions} from 'src/scripts/helpers'; -import {getBetaVersionData} from './beta'; +import {getPackageVersionData} from './beta'; import {Logger} from './logger'; import {getMatchArray, getMatchImport} from './match'; import {findMostMatchText} from './math-diff'; @@ -107,6 +107,19 @@ interface CheckPeerDependenciesConfig { allDependencies?: Record; packageNames?: string[]; beta?: boolean; + canary?: boolean; +} + +export function getConditionData(beta: boolean, canary: boolean) { + return beta + ? {tag: 'beta', version: store.betaVersion} + : canary + ? {tag: 'canary', version: store.canaryVersion} + : null; +} + +export function getConditionLatestVersion(beta: boolean, canary: boolean) { + return beta ? store.betaVersion : canary ? store.canaryVersion : store.latestVersion; } /** @@ -124,14 +137,15 @@ export async function checkRequiredContentInstalled< dependenciesKeys: Set, checkPeerDependenciesConfig?: T extends {peerDependencies: infer P} ? P extends true - ? Required> + ? Required> : T : T ): Promise { const result = [] as unknown as CheckResult; - const {allDependencies, beta, packageNames, peerDependencies} = (checkPeerDependenciesConfig ?? - {}) as Required; + const {allDependencies, beta, canary, packageNames, peerDependencies} = + (checkPeerDependenciesConfig ?? {}) as Required; const peerDependenciesList: string[] = []; + const conditionData = getConditionData(beta, canary); if (peerDependencies) { const peerDepList = await checkPeerDependencies({allDependencies, packageNames}); @@ -147,7 +161,8 @@ export async function checkRequiredContentInstalled< if (hasAllComponents && hasFramerMotion && !peerDependenciesList.length) { return [true]; } - !hasAllComponents && result.push(beta ? `${NEXT_UI}@${store.betaVersion}` : NEXT_UI); + !hasAllComponents && + result.push(conditionData ? `${NEXT_UI}@${conditionData.version}` : NEXT_UI); !hasFramerMotion && result.push(FRAMER_MOTION); !hasTailwind && result.push(TAILWINDCSS); } else if (type === 'partial') { @@ -160,11 +175,15 @@ export async function checkRequiredContentInstalled< return [true]; } !hasFramerMotion && result.push(FRAMER_MOTION); - const betaSystemUI = await getBetaVersionData(SYSTEM_UI); - const betaThemeUI = await getBetaVersionData(THEME_UI); + const conditionSystemUI = JSON.parse(await getPackageVersionData(SYSTEM_UI)); + const conditionThemeUI = JSON.parse(await getPackageVersionData(THEME_UI)); - !hasSystemUI && result.push(beta ? `${SYSTEM_UI}@${betaSystemUI}` : SYSTEM_UI); - !hasThemeUI && result.push(beta ? `${THEME_UI}@${betaThemeUI}` : THEME_UI); + !hasSystemUI && + result.push( + conditionData ? `${SYSTEM_UI}@${conditionSystemUI[conditionData.tag]}` : SYSTEM_UI + ); + !hasThemeUI && + result.push(conditionData ? `${THEME_UI}@${conditionThemeUI[conditionData.tag]}` : THEME_UI); !hasTailwind && result.push(TAILWINDCSS); } diff --git a/src/helpers/upgrade.ts b/src/helpers/upgrade.ts index 83f2955..3a7f5c2 100644 --- a/src/helpers/upgrade.ts +++ b/src/helpers/upgrade.ts @@ -3,10 +3,13 @@ import type {RequiredKey, SAFE_ANY} from './type'; import chalk from 'chalk'; import {NEXT_UI, THEME_UI} from 'src/constants/required'; -import {store} from 'src/constants/store'; +import {getStoreSync, store} from 'src/constants/store'; import {getCacheExecData} from 'src/scripts/cache/cache'; import {type Dependencies, compareVersions, getLatestVersion} from 'src/scripts/helpers'; +import {getBetaVersion} from './beta'; +import {getCanaryVersion} from './canary'; +import {getConditionLatestVersion} from './check'; import {Logger} from './logger'; import {colorMatchRegex, outputBox} from './output-info'; import { @@ -195,13 +198,19 @@ export async function getPackagePeerDep( } const currentVersion = allDependencies[peerPackage]; - let formatPeerVersion = transformPeerVersion(peerVersion); + let formatPeerVersion = getStoreSync('beta') + ? await getBetaVersion(peerPackage) + : getStoreSync('canary') + ? await getCanaryVersion(peerPackage) + : transformPeerVersion(peerVersion); if (!currentVersion) { + // If the peer package is not installed, then add minimum version to the missingDepList missingDepList.add({name: peerPackage, version: formatPeerVersion}); continue; } const {versionMode} = getVersionAndMode(allDependencies, peerPackage); + const isLatest = compareVersions(currentVersion, formatPeerVersion) >= 0; if (isLatest) { @@ -270,7 +279,7 @@ export async function getAllOutputData( }; } - const latestVersion = store.latestVersion; + const latestVersion = getConditionLatestVersion(store.beta, store.canary); const {currentVersion, versionMode} = getVersionAndMode(allDependencies, NEXT_UI); const colorVersion = getColorVersion(currentVersion, latestVersion); diff --git a/src/index.ts b/src/index.ts index e588f07..f5e7007 100644 --- a/src/index.ts +++ b/src/index.ts @@ -100,6 +100,7 @@ nextui .option('--prettier [boolean]', 'Apply Prettier formatting to the added content') .option('--addApp [boolean]', 'Include App.tsx file content that requires a provider', false) .option('-b --beta [boolean]', 'Add beta components', false) + .option('-c --canary [boolean]', 'Add canary components', false) .action(addAction); nextui @@ -110,6 +111,7 @@ nextui .option('-a --all [boolean]', 'Upgrade all components', false) .option('-w --write [boolean]', 'Write the upgrade version to package.json file', false) .option('-b --beta [boolean]', 'Upgrade beta components', false) + .option('-c --canary [boolean]', 'Upgrade canary components', false) .action(upgradeAction); nextui @@ -150,21 +152,27 @@ nextui.hook('preAction', async (command) => { const options = (command as SAFE_ANY).rawArgs.slice(2); const noCache = options.includes('--no-cache'); const debug = options.includes('--debug') || options.includes('-d'); - // const componentsArgs = command.args?.slice(1); + const beta = options.includes('--beta') || options.includes('-b'); + const canary = options.includes('--canary') || options.includes('-c'); // Init cache initCache(noCache); // Init debug store.debug = debug; - store.beta = options.includes('-b') || options.includes('--beta'); + store.beta = beta; + store.canary = canary; if (args && commandList.includes(args as CommandName)) { // Before run the command init the components.json - const nextUIComponents = (await getComponents()).components; - const nextUIComponentsBeta = (await getComponents()).betaComponents; + const components = await getComponents(); + const nextUIComponents = components.components; + const nextUIComponentsBeta = components.betaComponents; + const nextUIComponentsCanary = components.canaryComponents; - initStoreComponentsData({beta: false, nextUIComponents}); + initStoreComponentsData({nextUIComponents}); store.beta && initStoreComponentsData({beta: true, nextUIComponents: nextUIComponentsBeta}); + store.canary && + initStoreComponentsData({canary: true, nextUIComponents: nextUIComponentsCanary}); } const [cliLatestVersion, latestVersion] = await Promise.all([ diff --git a/src/prompts/get-beta-version-select.ts b/src/prompts/get-beta-version-select.ts index 3c29ecd..10177e1 100644 --- a/src/prompts/get-beta-version-select.ts +++ b/src/prompts/get-beta-version-select.ts @@ -1,4 +1,4 @@ -import {getBetaVersionData} from '@helpers/beta'; +import {getPackageVersionData} from '@helpers/beta'; import {getSelect} from './index'; @@ -6,7 +6,7 @@ export async function getBetaVersionSelect(components: string[]) { const result: string[] = []; for (const component of components) { - const betaVersionData = JSON.parse(await getBetaVersionData(component)); + const betaVersionData = JSON.parse(await getPackageVersionData(component)); const selectedResult = await getSelect( `Select beta version of ${component}`, diff --git a/src/scripts/helpers.ts b/src/scripts/helpers.ts index d49d4ec..d5612b1 100644 --- a/src/scripts/helpers.ts +++ b/src/scripts/helpers.ts @@ -10,7 +10,7 @@ import ora, {oraPromise} from 'ora'; import {Logger} from '@helpers/logger'; import {COMPONENTS_PATH} from 'src/constants/path'; -import {getStore, getStoreSync, store} from 'src/constants/store'; +import {getStore, store} from 'src/constants/store'; import {getPackageVersion} from './cache/cache'; @@ -29,11 +29,19 @@ export type Components = { export type ComponentsJson = { version: string; + betaVersion: string; + canaryVersion: string; components: Components; betaComponents: Components; - betaVersion: string; + canaryComponents: Components; }; +interface UpdateComponentsOptions { + beta?: boolean; + canary?: boolean; + fetchBasic?: boolean; +} + /** * Compare two versions * @example compareVersions('1.0.0', '1.0.1') // -1 @@ -51,10 +59,12 @@ export function compareVersions(version1 = '', version2 = '') { } } -export async function updateComponents() { +export async function updateComponents(options?: UpdateComponentsOptions) { + const {beta = store.beta, canary = store.canary, fetchBasic = false} = options ?? {}; + if (!existsSync(COMPONENTS_PATH)) { // First time download the latest date from net - await autoUpdateComponents(); + await autoUpdateComponents({beta, canary, fetchBasic}); return; } @@ -62,22 +72,34 @@ export async function updateComponents() { const components = JSON.parse(readFileSync(COMPONENTS_PATH, 'utf-8')) as ComponentsJson; const currentVersion = components.version; const betaVersion = components.betaVersion; - const latestVersion = await getStore('latestVersion'); - const latestBetaVersion = await getStore('betaVersion'); + const canaryVersion = components.canaryVersion; + const [latestVersion, latestBetaVersion, latestCanaryVersion] = await Promise.all([ + getStore('latestVersion'), + getStore('betaVersion'), + getStore('canaryVersion') + ]); if ( compareVersions(currentVersion, latestVersion) === -1 || - compareVersions(betaVersion, latestBetaVersion) === -1 + (beta && (compareVersions(betaVersion, latestBetaVersion) === -1 || !betaVersion)) || + (canary && (compareVersions(canaryVersion, latestCanaryVersion) === -1 || !canaryVersion)) ) { // After the first time, check the version and update - await autoUpdateComponents(latestVersion, latestBetaVersion); + await autoUpdateComponents({ + beta, + betaVersion: latestBetaVersion, + canary, + canaryVersion: latestCanaryVersion, + fetchBasic, + latestVersion + }); } } -export async function getComponents() { +export async function getComponents(options?: UpdateComponentsOptions) { let components: ComponentsJson = {} as ComponentsJson; - await updateComponents(); + await updateComponents(options); try { components = JSON.parse(readFileSync(COMPONENTS_PATH, 'utf-8')) as ComponentsJson; @@ -141,31 +163,61 @@ export async function getLatestVersion(packageName: string): Promise { const getUnpkgUrl = (version: string) => `https://unpkg.com/@nextui-org/react@${version}/dist/components.json`; -export async function autoUpdateComponents(latestVersion?: string, betaVersion?: string) { - [latestVersion, betaVersion] = await Promise.all([ - latestVersion || getStore('latestVersion'), - betaVersion || getStore('betaVersion') - ]); - - const url = getUnpkgUrl(latestVersion); - - const [components, betaComponents] = await Promise.all([ - downloadFile(url), - getStoreSync('beta') ? downloadFile(getUnpkgUrl(betaVersion), false) : Promise.resolve([]) +export async function autoUpdateComponents( + options: { + latestVersion?: string; + betaVersion?: string; + canaryVersion?: string; + } & UpdateComponentsOptions +) { + let {betaVersion, canaryVersion, latestVersion} = options; + const {beta, canary, fetchBasic} = options; + const existComponentsPath = existsSync(COMPONENTS_PATH); + + [latestVersion, betaVersion, canaryVersion] = await Promise.all([ + // If the components.json is not exist, then we need to download the latest version + ((!beta && !canary) || !existComponentsPath || fetchBasic) && + (latestVersion || getStore('latestVersion')), + beta && (betaVersion || getStore('betaVersion')), + canary && (canaryVersion || getStore('canaryVersion')) + ] as string[]); + + const originalComponentsJson = ( + existComponentsPath ? JSON.parse(readFileSync(COMPONENTS_PATH, 'utf-8')) : {components: []} + ) as ComponentsJson; + + const [components, betaComponents, canaryComponents] = await Promise.all([ + latestVersion + ? downloadFile({url: getUnpkgUrl(latestVersion)}) + : Promise.resolve(originalComponentsJson.components), + betaVersion + ? downloadFile({ + successText: 'Beta components updated successfully!', + url: getUnpkgUrl(betaVersion) + }) + : Promise.resolve([]), + canaryVersion + ? downloadFile({ + successText: 'Canary components updated successfully!', + url: getUnpkgUrl(canaryVersion) + }) + : Promise.resolve([]) ]); - const filterMissingComponents = betaComponents.filter( - (component) => !components.find((c) => c.name === component.name) + const conditionalComponents = beta ? betaComponents : canary ? canaryComponents : []; + const filterMissingComponents = conditionalComponents.filter( + (component) => !components?.find((c) => c.name === component.name) ); - // Add missing beta components to components - components.push(...filterMissingComponents); + // Add missing beta/canary components to components + components.concat(filterMissingComponents ?? []); const componentsJson: ComponentsJson = { - betaComponents, - betaVersion, - components, - version: latestVersion + ...originalComponentsJson, + ...(beta ? {betaComponents, betaVersion} : {}), + ...(canary ? {canaryComponents, canaryVersion} : {}), + ...(latestVersion ? {version: latestVersion} : {}), + components }; writeFileSync(COMPONENTS_PATH, JSON.stringify(componentsJson, null, 2), 'utf-8'); @@ -173,7 +225,15 @@ export async function autoUpdateComponents(latestVersion?: string, betaVersion?: return componentsJson; } -export async function downloadFile(url: string, log = true): Promise { +export async function downloadFile({ + log = true, + successText, + url +}: { + url: string; + log?: boolean; + successText?: string; +}): Promise { let data; await oraPromise( @@ -205,7 +265,9 @@ export async function downloadFile(url: string, log = true): Promise log && Logger.prefix('error', `Update components data error: ${error}`); process.exit(1); }, - ...(log ? {successText: chalk.greenBright('Components data updated successfully!\n')} : {}), + ...(log + ? {successText: chalk.greenBright(successText ?? 'Components data updated successfully!\n')} + : {}), text: 'Fetching components data...' } ); diff --git a/src/scripts/update/update-components.ts b/src/scripts/update/update-components.ts index 3ad9bf5..d30c49d 100644 --- a/src/scripts/update/update-components.ts +++ b/src/scripts/update/update-components.ts @@ -3,10 +3,13 @@ import {store} from 'src/constants/store'; import {initCache} from '../cache/cache'; import {isGithubAction, updateComponents} from '../helpers'; +// Won't run on GitHub Actions if (!isGithubAction) { - // Won't run on GitHub Actions + // Force to update cache initCache(true); // Update beta components store.beta = true; - updateComponents(); + // Update canary components + store.canary = true; + updateComponents({fetchBasic: true}); }