@netlify/config
Version:
Netlify config module
92 lines (91 loc) • 4.85 kB
JavaScript
import path from 'node:path';
import { throwUserError } from './error.js';
export const NETLIFY_API_STAGING_HOSTNAME = 'api-staging.netlify.com';
export const NETLIFY_API_HOSTNAME = 'api.netlify.com';
export const EXTENSION_API_BASE_URL = 'https://api.netlifysdk.com';
export const EXTENSION_API_STAGING_BASE_URL = 'https://api-staging.netlifysdk.com';
/**
* normalizeAndMergeExtensions accepts several lists of extensions configured for the current build
* target, normalizes them to compensate for some differences between the various APIs we load this
* data from (one of two API endpoints and the user's config file), and merges them into a single
* list.
*
* Note that it merges extension data provided by the config file (configExtensions) only when
* context=dev. When it does so, config file data will be merged into any available API data, giving
* a preference to config file data.
*/
export const normalizeAndMergeExtensions = ({ apiExtensions, buildDir, configExtensions = [], context, }) => {
const apiExtensionsBySlug = new Map(apiExtensions.map((extension) => [
extension.slug,
{
...extension,
buildPlugin: extension.has_build
? { origin: 'remote', packageURL: new URL('/packages/buildhooks.tgz', extension.version) }
: null,
dev: null,
},
]));
const configExtensionsBySlug = new Map(
// Only use configuration-file data in development mode
(context === 'dev' ? configExtensions : []).map((extension) => {
let buildPluginPackageURL = null;
if (extension.dev?.path) {
let resolvedPath = path.isAbsolute(extension.dev.path)
? extension.dev.path
: path.resolve(buildDir, extension.dev.path);
const normalizedExtname = path.extname(resolvedPath).toLowerCase();
// If the user has specified a path to an extension directory rather than to a tarball
// package, interpret this as a shortcut for "the default Netlify Extension build plugin
// artifact path, please."
//
// This sort of emulates SDK v1/2/3 behavior, and is an effort at making extension dev mode
// friendlier to use. We can feel free to revisit this chocie at a later date.
if (normalizedExtname === '') {
resolvedPath = path.join(resolvedPath, '.ntli/site/static/packages/buildhooks.tgz');
}
buildPluginPackageURL = new URL(`file://${resolvedPath}`);
}
return [
extension.name,
// Normalize dev extensions to a similar shape as an API extension
{
author: undefined,
dev: extension.dev,
extension_token: undefined,
has_build: buildPluginPackageURL !== null,
name: extension.name,
slug: extension.name,
version: undefined,
buildPlugin: buildPluginPackageURL !== null ? { origin: 'local', packageURL: buildPluginPackageURL } : null,
},
];
}));
// Merge API and configuration file metadata together by merging development metadata onto API
// metadata.
//
// Explicitly allow the configuration file to reference an extension that doesn't yet exist in the
// API so users can test their build hooks without publishing the extension first.
const mergedExtensions = [...new Set([...apiExtensionsBySlug.keys(), ...configExtensionsBySlug.keys()])]
.map((slug) => [apiExtensionsBySlug.get(slug), configExtensionsBySlug.get(slug)])
.map(([apiExtension, configExtension]) => {
return {
author: configExtension?.author ?? apiExtension?.author ?? '',
buildPlugin: configExtension?.buildPlugin ?? apiExtension?.buildPlugin ?? null,
dev: configExtension?.dev,
extension_token: configExtension?.extension_token ?? apiExtension?.extension_token ?? '',
has_build: configExtension?.has_build ?? apiExtension?.has_build ?? false,
name: configExtension?.name ?? apiExtension?.name ?? '',
slug: configExtension?.slug ?? apiExtension?.slug ?? '',
version: configExtension?.version ?? apiExtension?.version ?? '',
};
});
for (const extension of mergedExtensions) {
if (extension.buildPlugin !== null) {
const normalizedExtname = path.extname(extension.buildPlugin.packageURL.toString()).toLowerCase();
if (normalizedExtname !== '.tgz') {
throwUserError(`Extension ${extension.slug} contains unexpected build plugin URL: '${extension.buildPlugin.packageURL.toString()}'. Build plugin URLs must end in '.tgz'.`);
}
}
}
return mergedExtensions;
};