mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 04:22:13 +00:00
Compare commits
57 Commits
@vercel/st
...
endangered
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fa6ff1701 | ||
|
|
06e123f494 | ||
|
|
c0d63d8017 | ||
|
|
6e9eef793b | ||
|
|
48471f9a7f | ||
|
|
0f50da19a2 | ||
|
|
057d0ed269 | ||
|
|
6ca6b397c9 | ||
|
|
6774f990b4 | ||
|
|
036989b977 | ||
|
|
b18c4dbd3a | ||
|
|
433bf5480b | ||
|
|
6c640c4f19 | ||
|
|
6cb571f480 | ||
|
|
a9d281f155 | ||
|
|
dbb7dd2607 | ||
|
|
1b62000f68 | ||
|
|
f78b1aaa87 | ||
|
|
dcb125dbba | ||
|
|
68f2c8acf9 | ||
|
|
ae0f9b7353 | ||
|
|
aca134fe53 | ||
|
|
0df400cf6b | ||
|
|
fed8f99f19 | ||
|
|
c415fd0fad | ||
|
|
bf6efd2d02 | ||
|
|
23dae76bc4 | ||
|
|
3f69e0110f | ||
|
|
28efa86e20 | ||
|
|
b96dead0d1 | ||
|
|
fbf562eb51 | ||
|
|
6954ca19ef | ||
|
|
87fa17b968 | ||
|
|
18a19c8928 | ||
|
|
46c1cef0f4 | ||
|
|
0072df8464 | ||
|
|
f2cbf0e4c3 | ||
|
|
9c56142c8a | ||
|
|
f3484f9801 | ||
|
|
9e6119a8a9 | ||
|
|
c5d2f1b470 | ||
|
|
9893e3af64 | ||
|
|
3cffb69b25 | ||
|
|
b76f28b331 | ||
|
|
3e92ec897b | ||
|
|
5147d0285e | ||
|
|
e117cfb0e8 | ||
|
|
2d269bb995 | ||
|
|
668d7e537b | ||
|
|
f0c85bbf8e | ||
|
|
da2a327f97 | ||
|
|
4abce84609 | ||
|
|
f0f2dc9af6 | ||
|
|
b7f1cffc16 | ||
|
|
d1f0997d0a | ||
|
|
e054d52bd6 | ||
|
|
e04c0decf0 |
@@ -17,7 +17,11 @@ import {
|
||||
BuildResultV3,
|
||||
NowBuildError,
|
||||
} from '@vercel/build-utils';
|
||||
import { detectBuilders } from '@vercel/fs-detectors';
|
||||
import {
|
||||
detectBuilders,
|
||||
detectFrameworkRecord,
|
||||
LocalFileSystemDetector,
|
||||
} from '@vercel/fs-detectors';
|
||||
import minimatch from 'minimatch';
|
||||
import {
|
||||
appendRoutesToPhase,
|
||||
@@ -59,6 +63,9 @@ import { toEnumerableError } from '../util/error';
|
||||
import { validateConfig } from '../util/validate-config';
|
||||
|
||||
import { setMonorepoDefaultSettings } from '../util/build/monorepo';
|
||||
import frameworks from '@vercel/frameworks';
|
||||
import { detectFrameworkVersion } from '@vercel/fs-detectors';
|
||||
import semver from 'semver';
|
||||
|
||||
type BuildResult = BuildResultV2 | BuildResultV3;
|
||||
|
||||
@@ -69,6 +76,20 @@ interface SerializedBuilder extends Builder {
|
||||
apiVersion: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Output API `config.json` file interface.
|
||||
*/
|
||||
interface BuildOutputConfig {
|
||||
version?: 3;
|
||||
wildcard?: BuildResultV2Typical['wildcard'];
|
||||
images?: BuildResultV2Typical['images'];
|
||||
routes?: BuildResultV2Typical['routes'];
|
||||
overrides?: Record<string, PathOverride>;
|
||||
framework?: {
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Contents of the `builds.json` file.
|
||||
*/
|
||||
@@ -434,7 +455,7 @@ async function doBuild(
|
||||
// Execute Builders for detected entrypoints
|
||||
// TODO: parallelize builds (except for frontend)
|
||||
const sortedBuilders = sortBuilders(builds);
|
||||
const buildResults: Map<Builder, BuildResult> = new Map();
|
||||
const buildResults: Map<Builder, BuildResult | BuildOutputConfig> = new Map();
|
||||
const overrides: PathOverride[] = [];
|
||||
const repoRootPath = cwd;
|
||||
const corepackShimDir = await initCorepack({ repoRootPath });
|
||||
@@ -538,8 +559,7 @@ async function doBuild(
|
||||
|
||||
// Merge existing `config.json` file into the one that will be produced
|
||||
const configPath = join(outputDir, 'config.json');
|
||||
// TODO: properly type
|
||||
const existingConfig = await readJSONFile<any>(configPath);
|
||||
const existingConfig = await readJSONFile<BuildOutputConfig>(configPath);
|
||||
if (existingConfig instanceof CantParseJSONFile) {
|
||||
throw existingConfig;
|
||||
}
|
||||
@@ -585,15 +605,17 @@ async function doBuild(
|
||||
const mergedOverrides: Record<string, PathOverride> =
|
||||
overrides.length > 0 ? Object.assign({}, ...overrides) : undefined;
|
||||
|
||||
const framework = await getFramework(cwd, buildResults);
|
||||
|
||||
// Write out the final `config.json` file based on the
|
||||
// user configuration and Builder build results
|
||||
// TODO: properly type
|
||||
const config = {
|
||||
const config: BuildOutputConfig = {
|
||||
version: 3,
|
||||
routes: mergedRoutes,
|
||||
images: mergedImages,
|
||||
wildcard: mergedWildcard,
|
||||
overrides: mergedOverrides,
|
||||
framework,
|
||||
};
|
||||
await fs.writeJSON(join(outputDir, 'config.json'), config, { spaces: 2 });
|
||||
|
||||
@@ -608,6 +630,50 @@ async function doBuild(
|
||||
);
|
||||
}
|
||||
|
||||
async function getFramework(
|
||||
cwd: string,
|
||||
buildResults: Map<Builder, BuildResult | BuildOutputConfig>
|
||||
): Promise<{ version: string } | undefined> {
|
||||
const detectedFramework = await detectFrameworkRecord({
|
||||
fs: new LocalFileSystemDetector(cwd),
|
||||
frameworkList: frameworks,
|
||||
});
|
||||
|
||||
if (!detectedFramework) {
|
||||
return;
|
||||
}
|
||||
|
||||
// determine framework version from build result
|
||||
if (detectedFramework.useRuntime) {
|
||||
for (const [build, buildResult] of buildResults.entries()) {
|
||||
if (
|
||||
'framework' in buildResult &&
|
||||
build.use === detectedFramework.useRuntime.use
|
||||
) {
|
||||
return buildResult.framework;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// determine framework version from listed package.json version
|
||||
if (detectedFramework.detectedVersion) {
|
||||
// check for a valid, explicit version, not a range
|
||||
if (semver.valid(detectedFramework.detectedVersion)) {
|
||||
return {
|
||||
version: detectedFramework.detectedVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// determine framework version with runtime lookup
|
||||
const frameworkVersion = await detectFrameworkVersion(detectedFramework);
|
||||
if (frameworkVersion) {
|
||||
return {
|
||||
version: frameworkVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function expandBuild(files: string[], build: Builder): Builder[] {
|
||||
if (!build.use) {
|
||||
throw new NowBuildError({
|
||||
@@ -648,7 +714,7 @@ function expandBuild(files: string[], build: Builder): Builder[] {
|
||||
|
||||
function mergeImages(
|
||||
images: BuildResultV2Typical['images'],
|
||||
buildResults: Iterable<BuildResult>
|
||||
buildResults: Iterable<BuildResult | BuildOutputConfig>
|
||||
): BuildResultV2Typical['images'] {
|
||||
for (const result of buildResults) {
|
||||
if ('images' in result && result.images) {
|
||||
@@ -659,7 +725,7 @@ function mergeImages(
|
||||
}
|
||||
|
||||
function mergeWildcard(
|
||||
buildResults: Iterable<BuildResult>
|
||||
buildResults: Iterable<BuildResult | BuildOutputConfig>
|
||||
): BuildResultV2Typical['wildcard'] {
|
||||
let wildcard: BuildResultV2Typical['wildcard'] = undefined;
|
||||
for (const result of buildResults) {
|
||||
|
||||
4
packages/cli/test/fixtures/unit/commands/build/minimal-nextjs/node_modules/next/package.json
generated
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/build/minimal-nextjs/node_modules/next/package.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "next",
|
||||
"version": "13.0.4"
|
||||
}
|
||||
5
packages/cli/test/fixtures/unit/commands/build/minimal-nextjs/package.json
vendored
Normal file
5
packages/cli/test/fixtures/unit/commands/build/minimal-nextjs/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"next": "13.0.4"
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ import ms from 'ms';
|
||||
import fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { getWriteableDirectory } from '@vercel/build-utils';
|
||||
import build from '../../../src/commands/build';
|
||||
import { client } from '../../mocks/client';
|
||||
import { defaultProject, useProject } from '../../mocks/project';
|
||||
import { useTeams } from '../../mocks/team';
|
||||
import { useUser } from '../../mocks/user';
|
||||
import { setupFixture } from '../../helpers/setup-fixture';
|
||||
import build from '../../../../src/commands/build';
|
||||
import { client } from '../../../mocks/client';
|
||||
import { defaultProject, useProject } from '../../../mocks/project';
|
||||
import { useTeams } from '../../../mocks/team';
|
||||
import { useUser } from '../../../mocks/user';
|
||||
import { setupFixture } from '../../../helpers/setup-fixture';
|
||||
import JSON5 from 'json5';
|
||||
// TODO (@Ethan-Arrowood) - After shipping support for turbo and nx, revisit rush support
|
||||
// import execa from 'execa';
|
||||
@@ -15,7 +15,7 @@ import JSON5 from 'json5';
|
||||
jest.setTimeout(ms('1 minute'));
|
||||
|
||||
const fixture = (name: string) =>
|
||||
join(__dirname, '../../fixtures/unit/commands/build', name);
|
||||
join(__dirname, '../../../fixtures/unit/commands/build', name);
|
||||
|
||||
describe('build', () => {
|
||||
const originalCwd = process.cwd();
|
||||
@@ -74,9 +74,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"next":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'next',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -119,9 +117,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"gatsby":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'gatsby',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -244,9 +240,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"astro":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'astro',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -288,9 +282,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"hexo":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'hexo',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -325,9 +317,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@11ty\\/eleventy":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@11ty/eleventy',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -364,9 +354,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
some: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@docusaurus\\/core":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@docusaurus/core',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -452,9 +440,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
some: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"docusaurus":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'docusaurus',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -503,9 +489,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"preact-cli":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'preact-cli',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -549,14 +533,10 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"solid-js":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'solid-js',
|
||||
},
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"solid-start":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'solid-start',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -589,9 +569,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
some: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@dojo\\/framework":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@dojo/framework',
|
||||
},
|
||||
{
|
||||
path: '.dojorc',
|
||||
@@ -651,9 +629,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"ember-cli":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'ember-cli',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -698,9 +674,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@vue\\/cli-service":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@vue/cli-service',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -753,9 +727,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@scullyio\\/init":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@scullyio/init',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -790,9 +762,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@ionic\\/angular":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@ionic/angular',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -835,9 +805,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@angular\\/cli":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@angular/cli',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -895,9 +863,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"polymer-cli":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'polymer-cli',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -953,14 +919,10 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"svelte":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'svelte',
|
||||
},
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"sirv-cli":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'sirv-cli',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1081,9 +1043,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@ionic\\/react":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@ionic/react',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1143,14 +1103,10 @@ export const frameworks = [
|
||||
detectors: {
|
||||
some: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"react-scripts":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'react-scripts',
|
||||
},
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"react-dev-utils":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'react-dev-utils',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1209,9 +1165,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"gridsome":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'gridsome',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1246,9 +1200,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"umi":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'umi',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1292,9 +1244,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"sapper":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'sapper',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1329,9 +1279,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"saber":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'saber',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1380,9 +1328,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@stencil\\/core":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@stencil/core',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1443,11 +1389,15 @@ export const frameworks = [
|
||||
sort: 2,
|
||||
envPrefix: 'NUXT_ENV_',
|
||||
detectors: {
|
||||
every: [
|
||||
some: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"nuxt3?(-edge)?":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'nuxt',
|
||||
},
|
||||
{
|
||||
matchPackage: 'nuxt3',
|
||||
},
|
||||
{
|
||||
matchPackage: 'nuxt3-edge',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1503,9 +1453,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"@redwoodjs\\/core":\\s*".+?"[^}]*}',
|
||||
matchPackage: '@redwoodjs/core',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1736,9 +1684,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"vite":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'vite',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1772,9 +1718,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"vitepress":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'vitepress',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1806,9 +1750,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*vuepress:\\s*".+?"[^}]*}',
|
||||
matchPackage: 'vuepress',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1841,9 +1783,7 @@ export const frameworks = [
|
||||
detectors: {
|
||||
every: [
|
||||
{
|
||||
path: 'package.json',
|
||||
matchContent:
|
||||
'"(dev)?(d|D)ependencies":\\s*{[^}]*"parcel":\\s*".+?"[^}]*}',
|
||||
matchPackage: 'parcel',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -2,15 +2,24 @@ import { Rewrite, Route } from '@vercel/routing-utils';
|
||||
|
||||
export interface FrameworkDetectionItem {
|
||||
/**
|
||||
* A file path
|
||||
* @example "package.json"
|
||||
* A file path to detect.
|
||||
* If specified, "matchPackage" cannot be specified.
|
||||
* @example "some-framework.config.json"
|
||||
*/
|
||||
path: string;
|
||||
path?: string;
|
||||
/**
|
||||
* A matcher
|
||||
* A matcher for the entire file.
|
||||
* If specified, "matchPackage" cannot be specified.
|
||||
* @example "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"next\":\\s*\".+?\"[^}]*}"
|
||||
*/
|
||||
matchContent?: string;
|
||||
/**
|
||||
* A matcher for a package specifically found in a "package.json" file.
|
||||
* If specified, "path" and "matchContext" cannot be specified.
|
||||
* If specified in multiple detectors, the first one will be used to resolve the framework version.
|
||||
* @example "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"next\":\\s*\".+?\"[^}]*}"
|
||||
*/
|
||||
matchPackage?: string;
|
||||
}
|
||||
|
||||
export interface SettingPlaceholder {
|
||||
|
||||
@@ -17,7 +17,7 @@ const SchemaFrameworkDetectionItem = {
|
||||
items: [
|
||||
{
|
||||
type: 'object',
|
||||
required: ['path'],
|
||||
required: [],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
path: {
|
||||
@@ -26,6 +26,9 @@ const SchemaFrameworkDetectionItem = {
|
||||
matchContent: {
|
||||
type: 'string',
|
||||
},
|
||||
matchPackage: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Framework, FrameworkDetectionItem } from '@vercel/frameworks';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { DetectorFilesystem } from './detectors/filesystem';
|
||||
|
||||
interface BaseFramework {
|
||||
@@ -11,49 +12,96 @@ export interface DetectFrameworkOptions {
|
||||
frameworkList: readonly BaseFramework[];
|
||||
}
|
||||
|
||||
async function matches(fs: DetectorFilesystem, framework: BaseFramework) {
|
||||
export interface DetectFrameworkRecordOptions {
|
||||
fs: DetectorFilesystem;
|
||||
frameworkList: readonly Framework[];
|
||||
}
|
||||
|
||||
type MatchResult = {
|
||||
framework: BaseFramework;
|
||||
detectedVersion?: string;
|
||||
};
|
||||
|
||||
async function matches(
|
||||
fs: DetectorFilesystem,
|
||||
framework: BaseFramework
|
||||
): Promise<MatchResult | undefined> {
|
||||
const { detectors } = framework;
|
||||
|
||||
if (!detectors) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
const { every, some } = detectors;
|
||||
|
||||
if (every !== undefined && !Array.isArray(every)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (some !== undefined && !Array.isArray(some)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
const check = async ({ path, matchContent }: FrameworkDetectionItem) => {
|
||||
const check = async ({
|
||||
path,
|
||||
matchContent,
|
||||
matchPackage,
|
||||
}: FrameworkDetectionItem): Promise<MatchResult | undefined> => {
|
||||
if (matchPackage && matchContent) {
|
||||
throw new Error(
|
||||
`Cannot specify "matchPackage" and "matchContent" in the same detector for "${framework.slug}"`
|
||||
);
|
||||
}
|
||||
if (matchPackage && path) {
|
||||
throw new Error(
|
||||
`Cannot specify "matchPackage" and "path" in the same detector for "${framework.slug}" because "path" is assumed to be "package.json".`
|
||||
);
|
||||
}
|
||||
|
||||
if (!path && !matchPackage) {
|
||||
throw new Error(
|
||||
`Must specify either "path" or "matchPackage" in detector for "${framework.slug}".`
|
||||
);
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
return false;
|
||||
path = 'package.json';
|
||||
}
|
||||
|
||||
if (matchPackage) {
|
||||
matchContent = `"(dev)?(d|D)ependencies":\\s*{[^}]*"${matchPackage}":\\s*"(.+?)"[^}]*}`;
|
||||
}
|
||||
|
||||
if ((await fs.hasPath(path)) === false) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchContent) {
|
||||
if ((await fs.isFile(path)) === false) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
const regex = new RegExp(matchContent, 'gm');
|
||||
const regex = new RegExp(matchContent, 'm');
|
||||
const content = await fs.readFile(path);
|
||||
|
||||
if (!regex.test(content.toString())) {
|
||||
return false;
|
||||
const match = content.toString().match(regex);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
if (matchPackage && match[3]) {
|
||||
return {
|
||||
framework,
|
||||
detectedVersion: match[3],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return {
|
||||
framework,
|
||||
};
|
||||
};
|
||||
|
||||
const result: boolean[] = [];
|
||||
const result: (MatchResult | undefined)[] = [];
|
||||
|
||||
if (every) {
|
||||
const everyResult = await Promise.all(every.map(item => check(item)));
|
||||
@@ -61,11 +109,12 @@ async function matches(fs: DetectorFilesystem, framework: BaseFramework) {
|
||||
}
|
||||
|
||||
if (some) {
|
||||
let someResult = false;
|
||||
let someResult: MatchResult | undefined;
|
||||
|
||||
for (const item of some) {
|
||||
if (await check(item)) {
|
||||
someResult = true;
|
||||
const itemResult = await check(item);
|
||||
if (itemResult) {
|
||||
someResult = itemResult;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -73,9 +122,20 @@ async function matches(fs: DetectorFilesystem, framework: BaseFramework) {
|
||||
result.push(someResult);
|
||||
}
|
||||
|
||||
return result.every(res => res === true);
|
||||
if (!result.every(res => !!res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const detectedVersion = result.find(
|
||||
r => typeof r === 'object' && r.detectedVersion
|
||||
)?.detectedVersion;
|
||||
return {
|
||||
framework,
|
||||
detectedVersion,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Deprecate and replace with `detectFrameworkRecord`
|
||||
export async function detectFramework({
|
||||
fs,
|
||||
frameworkList,
|
||||
@@ -90,3 +150,75 @@ export async function detectFramework({
|
||||
);
|
||||
return result.find(res => res !== null) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Framework with a `detectedVersion` specifying the version
|
||||
* or version range of the relevant package
|
||||
*/
|
||||
type VersionedFramework = Framework & {
|
||||
detectedVersion?: string;
|
||||
};
|
||||
|
||||
// Note: Does not currently support a `frameworkList` of monorepo managers
|
||||
export async function detectFrameworkRecord({
|
||||
fs,
|
||||
frameworkList,
|
||||
}: DetectFrameworkRecordOptions): Promise<VersionedFramework | null> {
|
||||
const result = await Promise.all(
|
||||
frameworkList.map(async frameworkMatch => {
|
||||
const matchResult = await matches(fs, frameworkMatch);
|
||||
if (matchResult) {
|
||||
return {
|
||||
...frameworkMatch,
|
||||
detectedVersion: matchResult?.detectedVersion,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
const frameworkRecord = result.find(res => res !== null) ?? null;
|
||||
|
||||
return frameworkRecord;
|
||||
}
|
||||
|
||||
export async function detectFrameworkVersion(
|
||||
frameworkRecord: Framework
|
||||
): Promise<string | undefined> {
|
||||
const allDetectors = [
|
||||
...(frameworkRecord.detectors?.every || []),
|
||||
...(frameworkRecord.detectors?.some || []),
|
||||
];
|
||||
const firstMatchPackage = allDetectors.find(d => d.matchPackage);
|
||||
|
||||
if (!firstMatchPackage?.matchPackage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const version = lookupInstalledVersion(
|
||||
process.execPath,
|
||||
firstMatchPackage.matchPackage
|
||||
);
|
||||
if (version) {
|
||||
return version;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function lookupInstalledVersion(
|
||||
cwd: string,
|
||||
packageName: string
|
||||
): string | undefined {
|
||||
try {
|
||||
const script = `require('${packageName}/package.json').version`;
|
||||
return spawnSync(cwd, ['-p', script], {
|
||||
encoding: 'utf-8',
|
||||
}).stdout.trim();
|
||||
} catch (error) {
|
||||
console.debug(
|
||||
`Error looking up version of installed package "${packageName}": ${error}`
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,11 @@ export {
|
||||
detectApiExtensions,
|
||||
} from './detect-builders';
|
||||
export { detectFileSystemAPI } from './detect-file-system-api';
|
||||
export { detectFramework } from './detect-framework';
|
||||
export {
|
||||
detectFramework,
|
||||
detectFrameworkRecord,
|
||||
detectFrameworkVersion,
|
||||
} from './detect-framework';
|
||||
export { getProjectPaths } from './get-project-paths';
|
||||
export { DetectorFilesystem } from './detectors/filesystem';
|
||||
export { LocalFileSystemDetector } from './detectors/local-file-system-detector';
|
||||
|
||||
194
packages/fs-detectors/test/unit.framework-detector-record.test.ts
vendored
Normal file
194
packages/fs-detectors/test/unit.framework-detector-record.test.ts
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
import frameworkList from '@vercel/frameworks';
|
||||
import { detectFrameworkRecord } from '../src';
|
||||
import VirtualFilesystem from './virtual-file-system';
|
||||
|
||||
describe('detectFrameworkRecord', () => {
|
||||
it('Do not detect anything', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'README.md': '# hi',
|
||||
'api/cheese.js': 'export default (req, res) => res.end("cheese");',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe(undefined);
|
||||
});
|
||||
|
||||
it('Detects a framework record with a matchPackage detector', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
next: '9.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const frameworkRecord = await detectFrameworkRecord({ fs, frameworkList });
|
||||
if (!frameworkRecord) {
|
||||
throw new Error(
|
||||
'`frameworkRecord` was not detected, expected "nextjs" frameworks object'
|
||||
);
|
||||
}
|
||||
expect(frameworkRecord.slug).toBe('nextjs');
|
||||
expect(frameworkRecord.name).toBe('Next.js');
|
||||
expect(frameworkRecord.detectedVersion).toBe('9.0.0');
|
||||
});
|
||||
|
||||
it('Detects a framework record with a matchPackage detector with slashes', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'@ionic/angular': '5.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const frameworkRecord = await detectFrameworkRecord({ fs, frameworkList });
|
||||
if (!frameworkRecord) {
|
||||
throw new Error(
|
||||
'`frameworkRecord` was not detected, expected "ionic-angular" frameworks object'
|
||||
);
|
||||
}
|
||||
expect(frameworkRecord.slug).toBe('ionic-angular');
|
||||
expect(frameworkRecord.detectedVersion).toBe('5.0.0');
|
||||
});
|
||||
|
||||
it('Detect first framework version found', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
nuxt: '1.0.0',
|
||||
nuxt3: '2.0.0',
|
||||
'nuxt3-edge': '3.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('nuxtjs');
|
||||
expect(framework?.detectedVersion).toBe('1.0.0');
|
||||
});
|
||||
|
||||
it('Detect frameworks based on ascending order in framework list', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
next: '9.0.0',
|
||||
gatsby: '4.18.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('nextjs');
|
||||
});
|
||||
|
||||
it('Detect Nuxt.js', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
nuxt: '1.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('nuxtjs');
|
||||
});
|
||||
|
||||
it('Detect Nuxt.js 3 edge', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'nuxt3-edge': '1.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('nuxtjs');
|
||||
});
|
||||
|
||||
it('Detect Gatsby', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
gatsby: '1.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('gatsby');
|
||||
});
|
||||
|
||||
it('Detect Hugo #1', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.yaml': 'baseURL: http://example.org/',
|
||||
'content/post.md': '# hello world',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('hugo');
|
||||
});
|
||||
|
||||
it('Detect Hugo #2', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.json': '{ "baseURL": "http://example.org/" }',
|
||||
'content/post.md': '# hello world',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('hugo');
|
||||
});
|
||||
|
||||
it('Detect Hugo #3', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.toml': 'baseURL = "http://example.org/"',
|
||||
'content/post.md': '# hello world',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('hugo');
|
||||
});
|
||||
|
||||
it('Detect Jekyll', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'_config.yml': 'config',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('jekyll');
|
||||
});
|
||||
|
||||
it('Detect Middleman', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.rb': 'config',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('middleman');
|
||||
});
|
||||
|
||||
it('Detect Scully', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'@angular/cli': 'latest',
|
||||
'@scullyio/init': 'latest',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('scully');
|
||||
});
|
||||
|
||||
it('Detect Zola', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.toml': 'base_url = "/"',
|
||||
});
|
||||
|
||||
const framework = await detectFrameworkRecord({ fs, frameworkList });
|
||||
expect(framework?.slug).toBe('zola');
|
||||
});
|
||||
});
|
||||
@@ -1,131 +1,7 @@
|
||||
import path from 'path';
|
||||
import frameworkList from '@vercel/frameworks';
|
||||
import workspaceManagers from '../src/workspaces/workspace-managers';
|
||||
import { detectFramework, DetectorFilesystem } from '../src';
|
||||
import { DetectorFilesystemStat } from '../src/detectors/filesystem';
|
||||
|
||||
const posixPath = path.posix;
|
||||
|
||||
class VirtualFilesystem extends DetectorFilesystem {
|
||||
private files: Map<string, Buffer>;
|
||||
private cwd: string;
|
||||
|
||||
constructor(files: { [key: string]: string | Buffer }, cwd = '') {
|
||||
super();
|
||||
this.files = new Map();
|
||||
this.cwd = cwd;
|
||||
Object.entries(files).map(([key, value]) => {
|
||||
const buffer = typeof value === 'string' ? Buffer.from(value) : value;
|
||||
this.files.set(key, buffer);
|
||||
});
|
||||
}
|
||||
|
||||
private _normalizePath(rawPath: string): string {
|
||||
return posixPath.normalize(rawPath);
|
||||
}
|
||||
|
||||
async _hasPath(name: string): Promise<boolean> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
for (const file of this.files.keys()) {
|
||||
if (file.startsWith(basePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async _isFile(name: string): Promise<boolean> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
return this.files.has(basePath);
|
||||
}
|
||||
|
||||
async _readFile(name: string): Promise<Buffer> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
const file = this.files.get(basePath);
|
||||
|
||||
if (file === undefined) {
|
||||
throw new Error('File does not exist');
|
||||
}
|
||||
|
||||
if (typeof file === 'string') {
|
||||
return Buffer.from(file);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* An example of how to implement readdir for a virtual filesystem.
|
||||
*/
|
||||
async _readdir(name = '/'): Promise<DetectorFilesystemStat[]> {
|
||||
return (
|
||||
[...this.files.keys()]
|
||||
.map(filepath => {
|
||||
const basePath = this._normalizePath(
|
||||
posixPath.join(this.cwd, name === '/' ? '' : name)
|
||||
);
|
||||
const fileDirectoryName = posixPath.dirname(filepath);
|
||||
|
||||
if (fileDirectoryName === basePath) {
|
||||
return {
|
||||
name: posixPath.basename(filepath),
|
||||
path: filepath.replace(
|
||||
this.cwd === '' ? this.cwd : `${this.cwd}/`,
|
||||
''
|
||||
),
|
||||
type: 'file',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
(basePath === '.' && fileDirectoryName !== '.') ||
|
||||
fileDirectoryName.startsWith(basePath)
|
||||
) {
|
||||
let subDirectoryName = fileDirectoryName.replace(
|
||||
basePath === '.' ? '' : `${basePath}/`,
|
||||
''
|
||||
);
|
||||
|
||||
if (subDirectoryName.includes('/')) {
|
||||
subDirectoryName = subDirectoryName.split('/')[0];
|
||||
}
|
||||
|
||||
return {
|
||||
name: subDirectoryName,
|
||||
path:
|
||||
name === '/'
|
||||
? subDirectoryName
|
||||
: this._normalizePath(posixPath.join(name, subDirectoryName)),
|
||||
type: 'dir',
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
// remove nulls
|
||||
.filter((stat): stat is DetectorFilesystemStat => stat !== null)
|
||||
// remove duplicates
|
||||
.filter(
|
||||
(stat, index, self) =>
|
||||
index ===
|
||||
self.findIndex(s => s.name === stat.name && s.path === stat.path)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* An example of how to implement chdir for a virtual filesystem.
|
||||
*/
|
||||
_chdir(name: string): DetectorFilesystem {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
const files = Object.fromEntries(
|
||||
[...this.files.keys()].map(key => [key, this.files.get(key) ?? ''])
|
||||
);
|
||||
|
||||
return new VirtualFilesystem(files, basePath);
|
||||
}
|
||||
}
|
||||
import { detectFramework } from '../src';
|
||||
import VirtualFilesystem from './virtual-file-system';
|
||||
|
||||
describe('DetectorFilesystem', () => {
|
||||
it('should return the directory contents relative to the cwd', async () => {
|
||||
|
||||
126
packages/fs-detectors/test/virtual-file-system.ts
vendored
Normal file
126
packages/fs-detectors/test/virtual-file-system.ts
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
import path from 'path';
|
||||
import { DetectorFilesystem } from '../src';
|
||||
import { DetectorFilesystemStat } from '../src/detectors/filesystem';
|
||||
|
||||
const posixPath = path.posix;
|
||||
|
||||
export default class VirtualFilesystem extends DetectorFilesystem {
|
||||
private files: Map<string, Buffer>;
|
||||
private cwd: string;
|
||||
|
||||
constructor(files: { [key: string]: string | Buffer }, cwd = '') {
|
||||
super();
|
||||
this.files = new Map();
|
||||
this.cwd = cwd;
|
||||
Object.entries(files).map(([key, value]) => {
|
||||
const buffer = typeof value === 'string' ? Buffer.from(value) : value;
|
||||
this.files.set(key, buffer);
|
||||
});
|
||||
}
|
||||
|
||||
private _normalizePath(rawPath: string): string {
|
||||
return posixPath.normalize(rawPath);
|
||||
}
|
||||
|
||||
async _hasPath(name: string): Promise<boolean> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
for (const file of this.files.keys()) {
|
||||
if (file.startsWith(basePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async _isFile(name: string): Promise<boolean> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
return this.files.has(basePath);
|
||||
}
|
||||
|
||||
async _readFile(name: string): Promise<Buffer> {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
const file = this.files.get(basePath);
|
||||
|
||||
if (file === undefined) {
|
||||
throw new Error('File does not exist');
|
||||
}
|
||||
|
||||
if (typeof file === 'string') {
|
||||
return Buffer.from(file);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* An example of how to implement readdir for a virtual filesystem.
|
||||
*/
|
||||
async _readdir(name = '/'): Promise<DetectorFilesystemStat[]> {
|
||||
return (
|
||||
[...this.files.keys()]
|
||||
.map(filepath => {
|
||||
const basePath = this._normalizePath(
|
||||
posixPath.join(this.cwd, name === '/' ? '' : name)
|
||||
);
|
||||
const fileDirectoryName = posixPath.dirname(filepath);
|
||||
|
||||
if (fileDirectoryName === basePath) {
|
||||
return {
|
||||
name: posixPath.basename(filepath),
|
||||
path: filepath.replace(
|
||||
this.cwd === '' ? this.cwd : `${this.cwd}/`,
|
||||
''
|
||||
),
|
||||
type: 'file',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
(basePath === '.' && fileDirectoryName !== '.') ||
|
||||
fileDirectoryName.startsWith(basePath)
|
||||
) {
|
||||
let subDirectoryName = fileDirectoryName.replace(
|
||||
basePath === '.' ? '' : `${basePath}/`,
|
||||
''
|
||||
);
|
||||
|
||||
if (subDirectoryName.includes('/')) {
|
||||
subDirectoryName = subDirectoryName.split('/')[0];
|
||||
}
|
||||
|
||||
return {
|
||||
name: subDirectoryName,
|
||||
path:
|
||||
name === '/'
|
||||
? subDirectoryName
|
||||
: this._normalizePath(posixPath.join(name, subDirectoryName)),
|
||||
type: 'dir',
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
// remove nulls
|
||||
.filter((stat): stat is DetectorFilesystemStat => stat !== null)
|
||||
// remove duplicates
|
||||
.filter(
|
||||
(stat, index, self) =>
|
||||
index ===
|
||||
self.findIndex(s => s.name === stat.name && s.path === stat.path)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* An example of how to implement chdir for a virtual filesystem.
|
||||
*/
|
||||
_chdir(name: string): DetectorFilesystem {
|
||||
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
|
||||
const files = Object.fromEntries(
|
||||
[...this.files.keys()].map(key => [key, this.files.get(key) ?? ''])
|
||||
);
|
||||
|
||||
return new VirtualFilesystem(files, basePath);
|
||||
}
|
||||
}
|
||||
@@ -932,6 +932,7 @@ export const build: BuildV2 = async ({
|
||||
]
|
||||
: []),
|
||||
],
|
||||
framework: { version: nextVersion },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2581,6 +2582,7 @@ export const build: BuildV2 = async ({
|
||||
]),
|
||||
]),
|
||||
],
|
||||
framework: { version: nextVersion },
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1755,5 +1755,6 @@ export async function serverBuild({
|
||||
},
|
||||
]),
|
||||
],
|
||||
framework: { version: nextVersion },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
getSpawnOptions,
|
||||
glob,
|
||||
NodejsLambda,
|
||||
readConfigFile,
|
||||
runNpmInstall,
|
||||
runPackageJsonScript,
|
||||
scanParentDirs,
|
||||
@@ -69,6 +68,8 @@ export const build: BuildV2 = async ({
|
||||
env: spawnOpts.env || {},
|
||||
});
|
||||
|
||||
let packageJson: PackageJson | undefined;
|
||||
|
||||
// Ensure `@remix-run/vercel` is in the project's `package.json`
|
||||
const packageJsonPath = await walkParentDirs({
|
||||
base: repoRootPath,
|
||||
@@ -76,9 +77,9 @@ export const build: BuildV2 = async ({
|
||||
filename: 'package.json',
|
||||
});
|
||||
if (packageJsonPath) {
|
||||
const packageJson: PackageJson = JSON.parse(
|
||||
await fs.readFile(packageJsonPath, 'utf8')
|
||||
);
|
||||
packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
||||
}
|
||||
if (packageJsonPath && packageJson) {
|
||||
const { dependencies = {}, devDependencies = {} } = packageJson;
|
||||
|
||||
let modified = false;
|
||||
@@ -139,17 +140,14 @@ export const build: BuildV2 = async ({
|
||||
cwd: entrypointFsDirname,
|
||||
});
|
||||
} else {
|
||||
const pkg = await readConfigFile<PackageJson>(
|
||||
join(entrypointFsDirname, 'package.json')
|
||||
);
|
||||
if (hasScript('vercel-build', pkg)) {
|
||||
if (hasScript('vercel-build', packageJson)) {
|
||||
debug(`Executing "yarn vercel-build"`);
|
||||
await runPackageJsonScript(
|
||||
entrypointFsDirname,
|
||||
'vercel-build',
|
||||
spawnOpts
|
||||
);
|
||||
} else if (hasScript('build', pkg)) {
|
||||
} else if (hasScript('build', packageJson)) {
|
||||
debug(`Executing "yarn build"`);
|
||||
await runPackageJsonScript(entrypointFsDirname, 'build', spawnOpts);
|
||||
} else {
|
||||
@@ -212,6 +210,13 @@ export const build: BuildV2 = async ({
|
||||
),
|
||||
]);
|
||||
|
||||
let framework: { version: string } | undefined;
|
||||
if (packageJson?.version) {
|
||||
framework = {
|
||||
version: packageJson.version,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
routes: [
|
||||
{
|
||||
@@ -231,10 +236,11 @@ export const build: BuildV2 = async ({
|
||||
render: renderFunction,
|
||||
...staticFiles,
|
||||
},
|
||||
framework,
|
||||
};
|
||||
};
|
||||
|
||||
function hasScript(scriptName: string, pkg: PackageJson | null) {
|
||||
function hasScript(scriptName: string, pkg: PackageJson | undefined) {
|
||||
const scripts = (pkg && pkg.scripts) || {};
|
||||
return typeof scripts[scriptName] === 'string';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user