From 9969f0ba18a8588e9aaf8cc8df2bc1d4af30d5ea Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Fri, 14 Jul 2023 11:11:49 -0700 Subject: [PATCH] [cli] Use `detectFrameworks()` during `vc link --repo` (#10203) Allows for multiple frameworks to be detectable within the same root directory. This is basically specifically for Storybook. Screenshot 2023-07-12 at 6 04 37 PM --- .changeset/weak-days-yawn.md | 5 ++ packages/cli/src/util/link/repo.ts | 70 ++++++++++++------- .../cli/src/util/projects/detect-projects.ts | 14 ++-- .../util/projects/detect-projects.test.ts | 31 +++++--- 4 files changed, 78 insertions(+), 42 deletions(-) create mode 100644 .changeset/weak-days-yawn.md diff --git a/.changeset/weak-days-yawn.md b/.changeset/weak-days-yawn.md new file mode 100644 index 000000000..4e01566d7 --- /dev/null +++ b/.changeset/weak-days-yawn.md @@ -0,0 +1,5 @@ +--- +'vercel': patch +--- + +Detect multiple frameworks within the same root directory during `vc link --repo` diff --git a/packages/cli/src/util/link/repo.ts b/packages/cli/src/util/link/repo.ts index 074a11b30..d8bba6993 100644 --- a/packages/cli/src/util/link/repo.ts +++ b/packages/cli/src/util/link/repo.ts @@ -15,6 +15,7 @@ import { emoji, prependEmoji } from '../emoji'; import selectOrg from '../input/select-org'; import { addToGitIgnore } from './add-to-gitignore'; import type Client from '../client'; +import type { Framework } from '@vercel/frameworks'; import type { Project } from '@vercel-internals/types'; import createProject from '../projects/create-project'; import { detectProjects } from '../projects/detect-projects'; @@ -90,7 +91,7 @@ export async function ensureRepoLink( // they will be ready by the time the projects are listed const detectedProjectsPromise = detectProjects(rootPath).catch(err => { output.debug(`Failed to detect local projects: ${err}`); - return new Map(); + return new Map(); }); // Not yet linked, so prompt user to begin linking @@ -187,17 +188,21 @@ export async function ensureRepoLink( detectedProjects.delete(project.rootDirectory ?? ''); } - if (detectedProjects.size > 0) { + const detectedProjectsCount = Array.from(detectedProjects.values()).reduce( + (o, f) => o + f.length, + 0 + ); + if (detectedProjectsCount > 0) { output.log( `Detected ${pluralize( 'new Project', - detectedProjects.size, + detectedProjectsCount, true )} that may be created.` ); } - const addSeparators = projects.length > 0 && detectedProjects.size > 0; + const addSeparators = projects.length > 0 && detectedProjectsCount > 0; const { selected } = await client.prompt({ type: 'checkbox', name: 'selected', @@ -218,23 +223,30 @@ export async function ensureRepoLink( ...(addSeparators ? [new inquirer.Separator('----- New Projects to be created -----')] : []), - ...Array.from(detectedProjects.entries()).map( - ([rootDirectory, framework]) => { - const name = slugify( - [basename(rootPath), basename(rootDirectory)] - .filter(Boolean) - .join('-') - ); - return { - name: `${org.slug}/${name} (${framework})`, - value: { - newProject: true, - rootDirectory, - name, - framework, - }, - }; - } + ...Array.from(detectedProjects.entries()).flatMap( + ([rootDirectory, frameworks]) => + frameworks.map((framework, i) => { + const name = slugify( + [ + basename(rootPath), + basename(rootDirectory), + i > 0 ? framework.slug : '', + ] + .filter(Boolean) + .join('-') + ); + return { + name: `${org.slug}/${name} (${framework.name})`, + value: { + newProject: true, + rootDirectory, + name, + framework, + }, + // Checked by default when there are no other existing Projects + checked: projects.length === 0, + }; + }) ), ], }); @@ -251,7 +263,10 @@ export async function ensureRepoLink( output.spinner(`Creating new Project: ${orgAndName}`); delete selection.newProject; if (!selection.rootDirectory) delete selection.rootDirectory; - selected[i] = await createProject(client, selection); + selected[i] = await createProject(client, { + ...selection, + framework: selection.framework.slug, + }); await connectGitProvider( client, org, @@ -262,7 +277,8 @@ export async function ensureRepoLink( output.log( `Created new Project: ${output.link( orgAndName, - `https://vercel.com/${orgAndName}` + `https://vercel.com/${orgAndName}`, + { fallback: false } )}` ); } @@ -287,9 +303,11 @@ export async function ensureRepoLink( output.print( prependEmoji( - `Linked to ${repoUrlLink} under ${chalk.bold( - org.slug - )} (created ${VERCEL_DIR}${ + `Linked to ${pluralize( + 'Project', + selected.length, + true + )} under ${chalk.bold(org.slug)} (created ${VERCEL_DIR}${ isGitIgnoreUpdated ? ' and added it to .gitignore' : '' })`, emoji('link') diff --git a/packages/cli/src/util/projects/detect-projects.ts b/packages/cli/src/util/projects/detect-projects.ts index 0ac2cc585..68b8d0d46 100644 --- a/packages/cli/src/util/projects/detect-projects.ts +++ b/packages/cli/src/util/projects/detect-projects.ts @@ -1,7 +1,7 @@ import { join } from 'path'; -import frameworks from '@vercel/frameworks'; +import frameworkList, { Framework } from '@vercel/frameworks'; import { - detectFramework, + detectFrameworks, getWorkspacePackagePaths, getWorkspaces, LocalFileSystemDetector, @@ -10,7 +10,7 @@ import { export async function detectProjects(cwd: string) { const fs = new LocalFileSystemDetector(cwd); const workspaces = await getWorkspaces({ fs }); - const detectedProjects = new Map(); + const detectedProjects = new Map(); const packagePaths = ( await Promise.all( workspaces.map(workspace => @@ -26,12 +26,12 @@ export async function detectProjects(cwd: string) { } await Promise.all( packagePaths.map(async p => { - const framework = await detectFramework({ + const frameworks = await detectFrameworks({ fs: fs.chdir(join('.', p)), - frameworkList: frameworks, + frameworkList, }); - if (!framework) return; - detectedProjects.set(p.slice(1), framework); + if (frameworks.length === 0) return; + detectedProjects.set(p.slice(1), frameworks); }) ); return detectedProjects; diff --git a/packages/cli/test/unit/util/projects/detect-projects.test.ts b/packages/cli/test/unit/util/projects/detect-projects.test.ts index d861adefe..388d2dba5 100644 --- a/packages/cli/test/unit/util/projects/detect-projects.test.ts +++ b/packages/cli/test/unit/util/projects/detect-projects.test.ts @@ -1,4 +1,5 @@ import { join } from 'path'; +import type { Framework } from '@vercel/frameworks'; import { detectProjects } from '../../../../src/util/projects/detect-projects'; const REPO_ROOT = join(__dirname, '../../../../../..'); @@ -8,23 +9,35 @@ const FS_DETECTORS_FIXTURES = join( 'packages/fs-detectors/test/fixtures' ); +function mapDetected( + detected: Map +): Array<[string, string[]]> { + return [...detected.entries()] + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([dir, frameworks]) => [dir, frameworks.map(f => f.slug as string)]); +} + describe('detectProjects()', () => { - it('should match "nextjs" example', async () => { + it('should match 1 Project in "nextjs" example', async () => { const dir = join(EXAMPLES_DIR, 'nextjs'); const detected = await detectProjects(dir); - expect([...detected.entries()]).toEqual([['', 'nextjs']]); + expect(mapDetected(detected)).toEqual([['', ['nextjs']]]); + }); + + it('should match 2 Projects in "storybook" example', async () => { + const dir = join(EXAMPLES_DIR, 'storybook'); + const detected = await detectProjects(dir); + expect(mapDetected(detected)).toEqual([['', ['nextjs', 'storybook']]]); }); it('should match "30-double-nested-workspaces"', async () => { const dir = join(FS_DETECTORS_FIXTURES, '30-double-nested-workspaces'); const detected = await detectProjects(dir); - expect( - [...detected.entries()].sort((a, b) => a[0].localeCompare(b[0])) - ).toEqual([ - ['packages/backend/c', 'remix'], - ['packages/backend/d', 'nextjs'], - ['packages/frontend/a', 'hexo'], - ['packages/frontend/b', 'ember'], + expect(mapDetected(detected)).toEqual([ + ['packages/backend/c', ['remix']], + ['packages/backend/d', ['nextjs']], + ['packages/frontend/a', ['hexo']], + ['packages/frontend/b', ['ember']], ]); }); });