[cli] Add Variants to Build Output API (#11098)

Makes adjustments to replace `flags` with `variants`.

Also marks the current `flags` implementation as deprecated, as it
should get removed soon. Which I'll do in a follow up PR.
This commit is contained in:
Andy
2024-02-01 10:28:12 +01:00
committed by GitHub
parent 4027a18337
commit 8ba0ce9324
5 changed files with 94 additions and 6 deletions

View File

@@ -0,0 +1,7 @@
---
'@vercel/build-utils': minor
'vercel': minor
'@vercel/next': patch
---
Mark `flags` as deprecated and replace them with `variants`

View File

@@ -440,7 +440,9 @@ export interface Cron {
schedule: string;
}
// TODO: Proper description once complete
/**
* @deprecated Replaced by Variants. Remove once fully replaced.
*/
export interface Flag {
key: string;
defaultValue?: unknown;
@@ -471,7 +473,9 @@ export interface BuildResultV2Typical {
framework?: {
version: string;
};
/** @deprecated Replaced by Variants. Remove once fully replaced. */
flags?: Flag[];
variants?: Record<string, VariantDefinition>;
}
export type BuildResultV2 = BuildResultV2Typical | BuildResultBuildOutput;
@@ -491,3 +495,28 @@ export type ShouldServe = (
export type StartDevServer = (
options: StartDevServerOptions
) => Promise<StartDevServerResult>;
/**
* TODO: The following types will eventually be exported by a more
* relevant package.
*/
type VariantJSONArray = ReadonlyArray<VariantJSONValue>;
type VariantJSONValue =
| string
| boolean
| number
| null
| VariantJSONArray
| { [key: string]: VariantJSONValue };
type VariantOption = {
value: VariantJSONValue;
label?: string;
};
export interface VariantDefinition {
options?: VariantOption[];
url?: string;
description?: string;
}

View File

@@ -22,6 +22,7 @@ import {
Cron,
validateNpmrc,
Flag,
VariantDefinition,
} from '@vercel/build-utils';
import {
detectBuilders,
@@ -95,7 +96,9 @@ interface BuildOutputConfig {
version: string;
};
crons?: Cron[];
/** @deprecated Replaced by Variants. Remove once fully replaced. */
flags?: Flag[];
variants?: Record<string, VariantDefinition>;
}
/**
@@ -678,10 +681,13 @@ async function doBuild(
overrides: mergedOverrides,
framework,
crons: mergedCrons,
/** @deprecated Replaced by Variants. Remove once fully replaced. */
flags: mergedFlags,
};
await fs.writeJSON(join(outputDir, 'config.json'), config, { spaces: 2 });
await writeVariantsJson(client, buildResults.values(), outputDir);
const relOutputDir = relative(cwd, outputDir);
output.print(
`${prependEmoji(
@@ -826,6 +832,51 @@ function mergeFlags(
});
}
/**
* Takes the build output and writes all the variants into the `variants.json`
* file. It'll skip variants that already exist.
*/
async function writeVariantsJson(
{ output }: Client,
buildResults: Iterable<BuildResult | BuildOutputConfig>,
outputDir: string
): Promise<void> {
const variantsFilePath = join(outputDir, 'variants.json');
let hasVariants = true;
const variants = (await fs.readJSON(variantsFilePath).catch(error => {
if (error.code === 'ENOENT') {
hasVariants = false;
return { definitions: {} };
}
throw error;
})) as { definitions: Record<string, VariantDefinition> };
for (const result of buildResults) {
if (!('variants' in result) || !result.variants) continue;
for (const [key, defintion] of Object.entries(result.variants)) {
if (result.variants[key]) {
output.warn(
`The variant "${key}" was found multiple times. Only its first occurrence will be considered.`
);
continue;
}
hasVariants = true;
variants.definitions[key] = defintion;
}
}
// Only create the file when there are variants to write,
// or when the file already exists.
if (hasVariants) {
await fs.writeJSON(variantsFilePath, variants, { spaces: 2 });
}
}
async function writeBuildJson(buildsJson: BuildsManifest, outputDir: string) {
await fs.writeJSON(join(outputDir, 'builds.json'), buildsJson, { spaces: 2 });
}

View File

@@ -47,7 +47,7 @@ import {
UnwrapPromise,
getOperationType,
FunctionsConfigManifestV1,
VariantsManifest,
VariantsManifestLegacy,
RSC_CONTENT_TYPE,
RSC_PREFETCH_SUFFIX,
normalizePrefetches,
@@ -158,7 +158,7 @@ export async function serverBuild({
imagesManifest?: NextImagesManifest;
prerenderManifest: NextPrerenderedRoutes;
requiredServerFilesManifest: NextRequiredServerFilesManifest;
variantsManifest: VariantsManifest | null;
variantsManifest: VariantsManifestLegacy | null;
}): Promise<BuildResult> {
lambdaPages = Object.assign({}, lambdaPages, lambdaAppPaths);

View File

@@ -3257,7 +3257,8 @@ export function isApiPage(page: string | undefined) {
.match(/(serverless|server)\/pages\/api(\/|\.js$)/);
}
export type VariantsManifest = Record<
/** @deprecated */
export type VariantsManifestLegacy = Record<
string,
{
defaultValue?: unknown;
@@ -3268,7 +3269,7 @@ export type VariantsManifest = Record<
export async function getVariantsManifest(
entryPath: string,
outputDirectory: string
): Promise<null | VariantsManifest> {
): Promise<null | VariantsManifestLegacy> {
const pathVariantsManifest = path.join(
entryPath,
outputDirectory,
@@ -3282,7 +3283,7 @@ export async function getVariantsManifest(
if (!hasVariantsManifest) return null;
const variantsManifest: VariantsManifest = await fs.readJSON(
const variantsManifest: VariantsManifestLegacy = await fs.readJSON(
pathVariantsManifest
);