[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.

<img width="700" alt="Screenshot 2023-07-12 at 6 04 37 PM" src="https://github.com/vercel/vercel/assets/71256/5a240f1e-b000-42ad-b36f-3c151d3cd449">
This commit is contained in:
Nathan Rajlich
2023-07-14 11:11:49 -07:00
committed by GitHub
parent 24e1e3c3be
commit 9969f0ba18
4 changed files with 78 additions and 42 deletions

View File

@@ -0,0 +1,5 @@
---
'vercel': patch
---
Detect multiple frameworks within the same root directory during `vc link --repo`

View File

@@ -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<string, string>();
return new Map<string, Framework[]>();
});
// 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]) => {
...Array.from(detectedProjects.entries()).flatMap(
([rootDirectory, frameworks]) =>
frameworks.map((framework, i) => {
const name = slugify(
[basename(rootPath), basename(rootDirectory)]
[
basename(rootPath),
basename(rootDirectory),
i > 0 ? framework.slug : '',
]
.filter(Boolean)
.join('-')
);
return {
name: `${org.slug}/${name} (${framework})`,
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')

View File

@@ -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<string, string>();
const detectedProjects = new Map<string, Framework[]>();
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;

View File

@@ -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<string, Framework[]>
): 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']],
]);
});
});