@netlify/build 29.48.2 → 29.49.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/core/build.d.ts +4 -4
- package/lib/core/build.js +5 -4
- package/lib/core/dry.d.ts +2 -1
- package/lib/core/dry.js +8 -8
- package/lib/core/feature_flags.js +1 -4
- package/lib/log/description.d.ts +1 -1
- package/lib/log/messages/core_steps.d.ts +2 -1
- package/lib/log/messages/core_steps.js +9 -2
- package/lib/log/output_flusher.d.ts +0 -1
- package/lib/log/stream.d.ts +0 -2
- package/lib/plugins/child/utils.d.ts +1 -1
- package/lib/plugins/options.d.ts +3 -3
- package/lib/plugins_core/blobs_upload/index.js +10 -8
- package/lib/plugins_core/deploy/buildbot_client.d.ts +0 -1
- package/lib/plugins_core/deploy_config/index.js +38 -17
- package/lib/plugins_core/deploy_config/util.d.ts +2 -0
- package/lib/plugins_core/deploy_config/util.js +30 -0
- package/lib/plugins_core/dev_blobs_upload/index.js +10 -8
- package/lib/plugins_core/edge_functions/index.d.ts +4 -2
- package/lib/plugins_core/edge_functions/index.js +37 -15
- package/lib/plugins_core/functions/index.d.ts +5 -2
- package/lib/plugins_core/functions/index.js +21 -7
- package/lib/plugins_core/functions/utils.d.ts +3 -1
- package/lib/plugins_core/functions/utils.js +4 -1
- package/lib/plugins_core/functions/zisi.d.ts +1 -1
- package/lib/plugins_core/functions/zisi.js +4 -3
- package/lib/report/statsd.d.ts +1 -1
- package/lib/time/measure.d.ts +0 -1
- package/lib/utils/blobs.d.ts +14 -8
- package/lib/utils/blobs.js +46 -18
- package/lib/utils/frameworks_api.d.ts +45 -0
- package/lib/utils/frameworks_api.js +75 -0
- package/lib/utils/package.d.ts +1 -1
- package/package.json +4 -4
package/lib/core/build.d.ts
CHANGED
|
@@ -16,18 +16,18 @@ export declare const startBuild: (flags: Partial<BuildFlags>) => {
|
|
|
16
16
|
cacheDir: string;
|
|
17
17
|
sendStatus: boolean;
|
|
18
18
|
saveConfig: boolean;
|
|
19
|
-
apiHost?: string
|
|
19
|
+
apiHost?: string;
|
|
20
20
|
testOpts: import("./types.js").TestOptions;
|
|
21
21
|
statsd: {
|
|
22
22
|
port: number;
|
|
23
23
|
};
|
|
24
|
-
timeline: string;
|
|
24
|
+
timeline: "build" | string;
|
|
25
25
|
cachedConfig: Record<string, unknown>;
|
|
26
26
|
siteId: string;
|
|
27
27
|
dry: false;
|
|
28
|
-
context: string;
|
|
28
|
+
context: "production" | string;
|
|
29
29
|
statsdOpts: {
|
|
30
|
-
host?: number
|
|
30
|
+
host?: number;
|
|
31
31
|
port: number;
|
|
32
32
|
};
|
|
33
33
|
};
|
package/lib/core/build.js
CHANGED
|
@@ -280,9 +280,10 @@ const initAndRunBuild = async function ({ pluginsOptions, netlifyConfig, configO
|
|
|
280
280
|
context,
|
|
281
281
|
systemLog,
|
|
282
282
|
});
|
|
283
|
-
const pluginsEnv =
|
|
284
|
-
|
|
285
|
-
:
|
|
283
|
+
const pluginsEnv = {
|
|
284
|
+
...childEnv,
|
|
285
|
+
...getBlobsEnvironmentContext({ api, deployId: deployId, siteId: siteInfo?.id, token }),
|
|
286
|
+
};
|
|
286
287
|
if (pluginsOptionsA?.length) {
|
|
287
288
|
const buildPlugins = {};
|
|
288
289
|
for (const plugin of pluginsOptionsA) {
|
|
@@ -394,7 +395,7 @@ const runBuild = async function ({ childProcesses, pluginsOptions, netlifyConfig
|
|
|
394
395
|
});
|
|
395
396
|
const { steps, events } = timeline === 'dev' ? getDevSteps(devCommand, pluginsSteps, eventHandlers) : getSteps(pluginsSteps, eventHandlers);
|
|
396
397
|
if (dry) {
|
|
397
|
-
await doDryRun({ buildDir, steps, netlifyConfig, constants, buildbotServerSocket, logs });
|
|
398
|
+
await doDryRun({ buildDir, steps, netlifyConfig, constants, buildbotServerSocket, logs, featureFlags });
|
|
398
399
|
return { netlifyConfig };
|
|
399
400
|
}
|
|
400
401
|
const { stepsCount, netlifyConfig: netlifyConfigA, statuses, failedPlugins, timers: timersB, configMutations, metrics, } = await runSteps({
|
package/lib/core/dry.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export function doDryRun({ buildDir, steps, netlifyConfig, constants, buildbotServerSocket, logs }: {
|
|
1
|
+
export function doDryRun({ buildDir, steps, netlifyConfig, constants, buildbotServerSocket, logs, featureFlags, }: {
|
|
2
2
|
buildDir: any;
|
|
3
3
|
steps: any;
|
|
4
4
|
netlifyConfig: any;
|
|
5
5
|
constants: any;
|
|
6
6
|
buildbotServerSocket: any;
|
|
7
7
|
logs: any;
|
|
8
|
+
featureFlags: any;
|
|
8
9
|
}): Promise<void>;
|
package/lib/core/dry.js
CHANGED
|
@@ -2,22 +2,22 @@ import pFilter from 'p-filter';
|
|
|
2
2
|
import { logDryRunStart, logDryRunStep, logDryRunEnd } from '../log/messages/dry.js';
|
|
3
3
|
import { runsOnlyOnBuildFailure } from '../plugins/events.js';
|
|
4
4
|
// If the `dry` flag is specified, do a dry run
|
|
5
|
-
export const doDryRun = async function ({ buildDir, steps, netlifyConfig, constants, buildbotServerSocket, logs }) {
|
|
6
|
-
const successSteps = await pFilter(steps, ({ event, condition }) => shouldIncludeStep({ buildDir, event, condition, netlifyConfig, constants, buildbotServerSocket }));
|
|
5
|
+
export const doDryRun = async function ({ buildDir, steps, netlifyConfig, constants, buildbotServerSocket, logs, featureFlags, }) {
|
|
6
|
+
const successSteps = await pFilter(steps, ({ event, condition }) => shouldIncludeStep({ buildDir, event, condition, netlifyConfig, constants, buildbotServerSocket, featureFlags }));
|
|
7
7
|
const eventWidth = Math.max(...successSteps.map(getEventLength));
|
|
8
8
|
const stepsCount = successSteps.length;
|
|
9
9
|
logDryRunStart({ logs, eventWidth, stepsCount });
|
|
10
|
-
successSteps
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
10
|
+
successSteps
|
|
11
|
+
.filter((step) => !step.quiet)
|
|
12
|
+
.forEach((step, index) => {
|
|
14
13
|
logDryRunStep({ logs, step, index, netlifyConfig, eventWidth, stepsCount });
|
|
15
14
|
});
|
|
16
15
|
logDryRunEnd(logs);
|
|
17
16
|
};
|
|
18
|
-
const shouldIncludeStep = async function ({ buildDir, event, condition, netlifyConfig, constants, buildbotServerSocket, }) {
|
|
17
|
+
const shouldIncludeStep = async function ({ buildDir, event, condition, netlifyConfig, constants, buildbotServerSocket, featureFlags, }) {
|
|
19
18
|
return (!runsOnlyOnBuildFailure(event) &&
|
|
20
|
-
(condition === undefined ||
|
|
19
|
+
(condition === undefined ||
|
|
20
|
+
(await condition({ buildDir, constants, netlifyConfig, buildbotServerSocket, featureFlags }))));
|
|
21
21
|
};
|
|
22
22
|
const getEventLength = function ({ event }) {
|
|
23
23
|
return event.length;
|
|
@@ -10,13 +10,10 @@ const getFeatureFlag = function (name) {
|
|
|
10
10
|
};
|
|
11
11
|
// Default values for feature flags
|
|
12
12
|
export const DEFAULT_FEATURE_FLAGS = {
|
|
13
|
-
build_inject_blobs_context: false,
|
|
14
13
|
buildbot_zisi_trace_nft: false,
|
|
15
14
|
buildbot_zisi_esbuild_parser: false,
|
|
16
|
-
buildbot_zisi_system_log: false,
|
|
17
|
-
edge_functions_cache_cli: false,
|
|
18
|
-
edge_functions_system_logger: false,
|
|
19
15
|
netlify_build_reduced_output: false,
|
|
20
16
|
netlify_build_updated_plugin_compatibility: false,
|
|
17
|
+
netlify_build_frameworks_api: false,
|
|
21
18
|
netlify_build_plugin_system_log: false,
|
|
22
19
|
};
|
package/lib/log/description.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const getBuildCommandDescription: (buildCommandOrigin: any) => any;
|
|
2
2
|
/** Retrieve human-friendly plugin origin */
|
|
3
|
-
export declare const getPluginOrigin: (loadedFrom:
|
|
3
|
+
export declare const getPluginOrigin: (loadedFrom: "package.json" | string, origin: string) => string;
|
|
@@ -3,13 +3,14 @@ export function logBundleResults({ logs, results }: {
|
|
|
3
3
|
results?: any[] | undefined;
|
|
4
4
|
}): void;
|
|
5
5
|
export function logFunctionsNonExistingDir(logs: any, relativeFunctionsSrc: any): void;
|
|
6
|
-
export function logFunctionsToBundle({ logs, userFunctions, userFunctionsSrc, userFunctionsSrcExists, internalFunctions, internalFunctionsSrc, type, }: {
|
|
6
|
+
export function logFunctionsToBundle({ logs, userFunctions, userFunctionsSrc, userFunctionsSrcExists, internalFunctions, internalFunctionsSrc, frameworkFunctions, type, }: {
|
|
7
7
|
logs: any;
|
|
8
8
|
userFunctions: any;
|
|
9
9
|
userFunctionsSrc: any;
|
|
10
10
|
userFunctionsSrcExists: any;
|
|
11
11
|
internalFunctions: any;
|
|
12
12
|
internalFunctionsSrc: any;
|
|
13
|
+
frameworkFunctions: any;
|
|
13
14
|
type?: string | undefined;
|
|
14
15
|
}): void;
|
|
15
16
|
export function logSecretsScanSkipMessage(logs: any, msg: any): void;
|
|
@@ -41,11 +41,18 @@ export const logFunctionsNonExistingDir = function (logs, relativeFunctionsSrc)
|
|
|
41
41
|
log(logs, `The Netlify Functions setting targets a non-existing directory: ${relativeFunctionsSrc}`);
|
|
42
42
|
};
|
|
43
43
|
// Print the list of Netlify Functions about to be bundled
|
|
44
|
-
export const logFunctionsToBundle = function ({ logs, userFunctions, userFunctionsSrc, userFunctionsSrcExists, internalFunctions, internalFunctionsSrc, type = 'Functions', }) {
|
|
44
|
+
export const logFunctionsToBundle = function ({ logs, userFunctions, userFunctionsSrc, userFunctionsSrcExists, internalFunctions, internalFunctionsSrc, frameworkFunctions, type = 'Functions', }) {
|
|
45
45
|
if (internalFunctions.length !== 0) {
|
|
46
46
|
log(logs, `Packaging ${type} from ${THEME.highlightWords(internalFunctionsSrc)} directory:`);
|
|
47
47
|
logArray(logs, internalFunctions, { indent: false });
|
|
48
48
|
}
|
|
49
|
+
if (frameworkFunctions.length !== 0) {
|
|
50
|
+
if (internalFunctions.length !== 0) {
|
|
51
|
+
log(logs, '');
|
|
52
|
+
}
|
|
53
|
+
log(logs, `Packaging ${type} generated by your framework:`);
|
|
54
|
+
logArray(logs, frameworkFunctions, { indent: false });
|
|
55
|
+
}
|
|
49
56
|
if (!userFunctionsSrcExists) {
|
|
50
57
|
return;
|
|
51
58
|
}
|
|
@@ -53,7 +60,7 @@ export const logFunctionsToBundle = function ({ logs, userFunctions, userFunctio
|
|
|
53
60
|
log(logs, `No ${type} were found in ${THEME.highlightWords(userFunctionsSrc)} directory`);
|
|
54
61
|
return;
|
|
55
62
|
}
|
|
56
|
-
if (internalFunctions.length !== 0) {
|
|
63
|
+
if (internalFunctions.length !== 0 || frameworkFunctions.length !== 0) {
|
|
57
64
|
log(logs, '');
|
|
58
65
|
}
|
|
59
66
|
log(logs, `Packaging ${type} from ${THEME.highlightWords(userFunctionsSrc)} directory:`);
|
package/lib/log/stream.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export function getUtils({ event, constants: { FUNCTIONS_SRC, INTERNAL_FUNCTIONS
|
|
|
20
20
|
list: (optsA: any) => Promise<string[]>;
|
|
21
21
|
getCacheDir: (optsA: any) => string;
|
|
22
22
|
};
|
|
23
|
-
run: (file: string, args?:
|
|
23
|
+
run: (file: string, args?: string[] | object, options?: Record<string, unknown>) => import("execa").ExecaChildProcess<string>;
|
|
24
24
|
functions: {
|
|
25
25
|
add: (src: any) => Promise<void>;
|
|
26
26
|
list: any;
|
package/lib/plugins/options.d.ts
CHANGED
|
@@ -10,9 +10,9 @@ export declare const getSpawnInfo: () => {
|
|
|
10
10
|
pluginPackageJson: PackageJson;
|
|
11
11
|
};
|
|
12
12
|
location: {
|
|
13
|
-
event:
|
|
13
|
+
event: "load";
|
|
14
14
|
packageName: string;
|
|
15
|
-
loadedFrom:
|
|
16
|
-
origin:
|
|
15
|
+
loadedFrom: "core";
|
|
16
|
+
origin: "core";
|
|
17
17
|
};
|
|
18
18
|
};
|
|
@@ -5,6 +5,7 @@ import semver from 'semver';
|
|
|
5
5
|
import { DEFAULT_API_HOST } from '../../core/normalize_flags.js';
|
|
6
6
|
import { logError } from '../../log/logger.js';
|
|
7
7
|
import { getFileWithMetadata, getKeysToUpload, scanForBlobs } from '../../utils/blobs.js';
|
|
8
|
+
import { getBlobs } from '../../utils/frameworks_api.js';
|
|
8
9
|
const coreStep = async function ({ logs, deployId, buildDir, packagePath, constants: { SITE_ID, NETLIFY_API_TOKEN, NETLIFY_API_HOST }, systemLog, }) {
|
|
9
10
|
// This should never happen due to the condition check
|
|
10
11
|
if (!deployId || !NETLIFY_API_TOKEN) {
|
|
@@ -29,22 +30,23 @@ const coreStep = async function ({ logs, deployId, buildDir, packagePath, consta
|
|
|
29
30
|
systemLog('No blobs to upload to deploy store.');
|
|
30
31
|
return {};
|
|
31
32
|
}
|
|
32
|
-
// If using the deploy config API
|
|
33
|
-
// was configured for the deploy.
|
|
34
|
-
|
|
33
|
+
// If using the deploy config API or the Frameworks API, configure the store
|
|
34
|
+
// to use the region that was configured for the deploy. We don't do it for
|
|
35
|
+
// the legacy file-based upload API since that would be a breaking change.
|
|
36
|
+
if (blobs.apiVersion > 1) {
|
|
35
37
|
storeOpts.experimentalRegion = 'auto';
|
|
36
38
|
}
|
|
37
39
|
const blobStore = getDeployStore(storeOpts);
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
+
const blobsToUpload = blobs.apiVersion >= 3 ? await getBlobs(blobs.directory) : await getKeysToUpload(blobs.directory);
|
|
41
|
+
if (blobsToUpload.length === 0) {
|
|
40
42
|
systemLog('No blobs to upload to deploy store.');
|
|
41
43
|
return {};
|
|
42
44
|
}
|
|
43
|
-
systemLog(`Uploading ${
|
|
45
|
+
systemLog(`Uploading ${blobsToUpload.length} blobs to deploy store...`);
|
|
44
46
|
try {
|
|
45
|
-
await pMap(
|
|
47
|
+
await pMap(blobsToUpload, async ({ key, contentPath, metadataPath }) => {
|
|
46
48
|
systemLog(`Uploading blob ${key}`);
|
|
47
|
-
const { data, metadata } = await getFileWithMetadata(
|
|
49
|
+
const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath);
|
|
48
50
|
await blobStore.set(key, data, { metadata });
|
|
49
51
|
}, { concurrency: 10 });
|
|
50
52
|
}
|
|
@@ -1,33 +1,55 @@
|
|
|
1
|
-
import { promises as fs } from 'fs';
|
|
2
|
-
import { resolve } from 'path';
|
|
3
1
|
import { mergeConfigs } from '@netlify/config';
|
|
4
2
|
import { getConfigMutations } from '../../plugins/child/diff.js';
|
|
5
|
-
import { filterConfig } from './util.js';
|
|
3
|
+
import { filterConfig, loadConfigFile } from './util.js';
|
|
6
4
|
// The properties that can be set using this API. Each element represents a
|
|
7
5
|
// path using dot-notation — e.g. `["build", "functions"]` represents the
|
|
8
6
|
// `build.functions` property.
|
|
9
|
-
const ALLOWED_PROPERTIES = [
|
|
7
|
+
const ALLOWED_PROPERTIES = [
|
|
8
|
+
['build', 'functions'],
|
|
9
|
+
['build', 'publish'],
|
|
10
|
+
['functions', '*'],
|
|
11
|
+
['functions', '*', '*'],
|
|
12
|
+
['headers'],
|
|
13
|
+
['images', 'remote_images'],
|
|
14
|
+
['redirects'],
|
|
15
|
+
];
|
|
16
|
+
// For array properties, any values set in this API will be merged with the
|
|
17
|
+
// main configuration file in such a way that user-defined values always take
|
|
18
|
+
// precedence. The exception are these properties that let frameworks set
|
|
19
|
+
// values that should be evaluated before any user-defined values. They use
|
|
20
|
+
// a special notation where `redirects!` represents "forced redirects", etc.
|
|
21
|
+
const OVERRIDE_PROPERTIES = new Set(['redirects!']);
|
|
10
22
|
const coreStep = async function ({ buildDir, netlifyConfig, packagePath, systemLog = () => {
|
|
11
23
|
// no-op
|
|
12
24
|
}, }) {
|
|
13
|
-
|
|
14
|
-
let config = {};
|
|
25
|
+
let config;
|
|
15
26
|
try {
|
|
16
|
-
|
|
17
|
-
config = JSON.parse(data);
|
|
27
|
+
config = await loadConfigFile(buildDir, packagePath);
|
|
18
28
|
}
|
|
19
29
|
catch (err) {
|
|
20
|
-
|
|
21
|
-
if (err.code === 'ENOENT') {
|
|
22
|
-
return {};
|
|
23
|
-
}
|
|
24
|
-
systemLog(`Failed to read Deploy Configuration API: ${err.message}`);
|
|
30
|
+
systemLog(`Failed to read Frameworks API: ${err.message}`);
|
|
25
31
|
throw new Error('An error occured while processing the platform configurarion defined by your framework');
|
|
26
32
|
}
|
|
33
|
+
if (!config) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
const configOverrides = {};
|
|
37
|
+
for (const key in config) {
|
|
38
|
+
// If the key uses the special notation for defining mutations that should
|
|
39
|
+
// take precedence over user-defined properties, extract the canonical
|
|
40
|
+
// property, set it on a different object, and delete it from the main one.
|
|
41
|
+
if (OVERRIDE_PROPERTIES.has(key)) {
|
|
42
|
+
const canonicalKey = key.slice(0, -1);
|
|
43
|
+
configOverrides[canonicalKey] = config[key];
|
|
44
|
+
delete config[key];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
27
47
|
// Filtering out any properties that can't be mutated using this API.
|
|
28
48
|
const filteredConfig = filterConfig(config, [], ALLOWED_PROPERTIES, systemLog);
|
|
29
49
|
// Merging the config extracted from the API with the initial config.
|
|
30
|
-
const newConfig = mergeConfigs([filteredConfig, netlifyConfig], {
|
|
50
|
+
const newConfig = mergeConfigs([filteredConfig, netlifyConfig, configOverrides], {
|
|
51
|
+
concatenateArrays: true,
|
|
52
|
+
});
|
|
31
53
|
// Diffing the initial and the new configs to compute the mutations (what
|
|
32
54
|
// changed between them).
|
|
33
55
|
const configMutations = getConfigMutations(netlifyConfig, newConfig, applyDeployConfig.event);
|
|
@@ -38,9 +60,8 @@ const coreStep = async function ({ buildDir, netlifyConfig, packagePath, systemL
|
|
|
38
60
|
export const applyDeployConfig = {
|
|
39
61
|
event: 'onBuild',
|
|
40
62
|
coreStep,
|
|
41
|
-
coreStepId: '
|
|
42
|
-
coreStepName: 'Applying
|
|
63
|
+
coreStepId: 'frameworks_api_config',
|
|
64
|
+
coreStepName: 'Applying configuration from Frameworks API',
|
|
43
65
|
coreStepDescription: () => '',
|
|
44
|
-
condition: ({ featureFlags }) => featureFlags?.netlify_build_deploy_configuration_api,
|
|
45
66
|
quiet: true,
|
|
46
67
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { NetlifyConfig } from '../../index.js';
|
|
1
2
|
import { SystemLogger } from '../types.js';
|
|
3
|
+
export declare const loadConfigFile: (buildDir: string, packagePath?: string) => Promise<Partial<NetlifyConfig> | undefined>;
|
|
2
4
|
/**
|
|
3
5
|
* Checks whether a property can be defined using the Deploy Configuration API.
|
|
4
6
|
*/
|
|
@@ -1,5 +1,35 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
1
3
|
import isPlainObject from 'is-plain-obj';
|
|
2
4
|
import mapObject, { mapObjectSkip } from 'map-obj';
|
|
5
|
+
import { FRAMEWORKS_API_CONFIG_ENDPOINT } from '../../utils/frameworks_api.js';
|
|
6
|
+
export const loadConfigFile = async (buildDir, packagePath) => {
|
|
7
|
+
const configPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_CONFIG_ENDPOINT);
|
|
8
|
+
try {
|
|
9
|
+
const data = await fs.readFile(configPath, 'utf8');
|
|
10
|
+
return JSON.parse(data);
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
// If the file doesn't exist, this is a non-error.
|
|
14
|
+
if (err.code !== 'ENOENT') {
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// The first version of this API was called "Deploy Configuration API" and it
|
|
19
|
+
// had `.netlify/deploy` as its base directory. For backwards-compatibility,
|
|
20
|
+
// we need to support that path for the config file.
|
|
21
|
+
const legacyConfigPath = resolve(buildDir, packagePath ?? '', '.netlify/deploy/v1/config.json');
|
|
22
|
+
try {
|
|
23
|
+
const data = await fs.readFile(legacyConfigPath, 'utf8');
|
|
24
|
+
return JSON.parse(data);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
// If the file doesn't exist, this is a non-error.
|
|
28
|
+
if (err.code !== 'ENOENT') {
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
3
33
|
/**
|
|
4
34
|
* Checks whether a property matches a template that may contain wildcards.
|
|
5
35
|
* Both the property and the template use a dot-notation represented as an
|
|
@@ -4,6 +4,7 @@ import pMap from 'p-map';
|
|
|
4
4
|
import semver from 'semver';
|
|
5
5
|
import { log, logError } from '../../log/logger.js';
|
|
6
6
|
import { getFileWithMetadata, getKeysToUpload, scanForBlobs } from '../../utils/blobs.js';
|
|
7
|
+
import { getBlobs } from '../../utils/frameworks_api.js';
|
|
7
8
|
const coreStep = async function ({ debug, logs, deployId, buildDir, quiet, packagePath, constants: { SITE_ID, NETLIFY_API_TOKEN, NETLIFY_API_HOST }, }) {
|
|
8
9
|
// This should never happen due to the condition check
|
|
9
10
|
if (!deployId || !NETLIFY_API_TOKEN) {
|
|
@@ -30,28 +31,29 @@ const coreStep = async function ({ debug, logs, deployId, buildDir, quiet, packa
|
|
|
30
31
|
}
|
|
31
32
|
return {};
|
|
32
33
|
}
|
|
33
|
-
// If using the deploy config API
|
|
34
|
-
// was configured for the deploy.
|
|
35
|
-
|
|
34
|
+
// If using the deploy config API or the Frameworks API, configure the store
|
|
35
|
+
// to use the region that was configured for the deploy. We don't do it for
|
|
36
|
+
// the legacy file-based upload API since that would be a breaking change.
|
|
37
|
+
if (blobs.apiVersion > 1) {
|
|
36
38
|
storeOpts.experimentalRegion = 'auto';
|
|
37
39
|
}
|
|
38
40
|
const blobStore = getDeployStore(storeOpts);
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
+
const blobsToUpload = blobs.apiVersion >= 3 ? await getBlobs(blobs.directory) : await getKeysToUpload(blobs.directory);
|
|
42
|
+
if (blobsToUpload.length === 0) {
|
|
41
43
|
if (!quiet) {
|
|
42
44
|
log(logs, 'No blobs to upload to deploy store.');
|
|
43
45
|
}
|
|
44
46
|
return {};
|
|
45
47
|
}
|
|
46
48
|
if (!quiet) {
|
|
47
|
-
log(logs, `Uploading ${
|
|
49
|
+
log(logs, `Uploading ${blobsToUpload.length} blobs to deploy store...`);
|
|
48
50
|
}
|
|
49
51
|
try {
|
|
50
|
-
await pMap(
|
|
52
|
+
await pMap(blobsToUpload, async ({ key, contentPath, metadataPath }) => {
|
|
51
53
|
if (debug && !quiet) {
|
|
52
54
|
log(logs, `- Uploading blob ${key}`, { indent: true });
|
|
53
55
|
}
|
|
54
|
-
const { data, metadata } = await getFileWithMetadata(
|
|
56
|
+
const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath);
|
|
55
57
|
await blobStore.set(key, data, { metadata });
|
|
56
58
|
}, { concurrency: 10 });
|
|
57
59
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Metric } from '../../core/report_metrics.js';
|
|
2
2
|
export declare const bundleEdgeFunctions: {
|
|
3
3
|
event: string;
|
|
4
|
-
coreStep: ({ buildDir, constants: { EDGE_FUNCTIONS_DIST: distDirectory, EDGE_FUNCTIONS_SRC: srcDirectory, INTERNAL_EDGE_FUNCTIONS_SRC: internalSrcDirectory, IS_LOCAL: isRunningLocally, }, debug, systemLog, featureFlags, logs, netlifyConfig, edgeFunctionsBootstrapURL, }: {
|
|
4
|
+
coreStep: ({ buildDir, packagePath, constants: { EDGE_FUNCTIONS_DIST: distDirectory, EDGE_FUNCTIONS_SRC: srcDirectory, INTERNAL_EDGE_FUNCTIONS_SRC: internalSrcDirectory, IS_LOCAL: isRunningLocally, }, debug, systemLog, featureFlags, logs, netlifyConfig, edgeFunctionsBootstrapURL, }: {
|
|
5
5
|
buildDir: string;
|
|
6
6
|
packagePath: string;
|
|
7
7
|
constants: Record<string, string>;
|
|
@@ -17,11 +17,13 @@ export declare const bundleEdgeFunctions: {
|
|
|
17
17
|
coreStepId: string;
|
|
18
18
|
coreStepName: string;
|
|
19
19
|
coreStepDescription: () => string;
|
|
20
|
-
condition: ({ buildDir, constants: { INTERNAL_EDGE_FUNCTIONS_SRC, EDGE_FUNCTIONS_SRC }, }: {
|
|
20
|
+
condition: ({ buildDir, constants: { INTERNAL_EDGE_FUNCTIONS_SRC, EDGE_FUNCTIONS_SRC }, featureFlags, packagePath, }: {
|
|
21
21
|
buildDir: any;
|
|
22
22
|
constants: {
|
|
23
23
|
INTERNAL_EDGE_FUNCTIONS_SRC: any;
|
|
24
24
|
EDGE_FUNCTIONS_SRC: any;
|
|
25
25
|
};
|
|
26
|
+
featureFlags: any;
|
|
27
|
+
packagePath: any;
|
|
26
28
|
}) => Promise<boolean>;
|
|
27
29
|
};
|
|
@@ -4,23 +4,36 @@ import { bundle, find } from '@netlify/edge-bundler';
|
|
|
4
4
|
import { pathExists } from 'path-exists';
|
|
5
5
|
import { log, reduceLogLines } from '../../log/logger.js';
|
|
6
6
|
import { logFunctionsToBundle } from '../../log/messages/core_steps.js';
|
|
7
|
+
import { FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT, FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP, } from '../../utils/frameworks_api.js';
|
|
7
8
|
import { tagBundlingError } from './lib/error.js';
|
|
8
9
|
import { validateEdgeFunctionsManifest } from './validate_manifest/validate_edge_functions_manifest.js';
|
|
9
10
|
// TODO: Replace this with a custom cache directory.
|
|
10
11
|
const DENO_CLI_CACHE_DIRECTORY = '.netlify/plugins/deno-cli';
|
|
11
12
|
const IMPORT_MAP_FILENAME = 'edge-functions-import-map.json';
|
|
12
|
-
const coreStep = async function ({ buildDir, constants: { EDGE_FUNCTIONS_DIST: distDirectory, EDGE_FUNCTIONS_SRC: srcDirectory, INTERNAL_EDGE_FUNCTIONS_SRC: internalSrcDirectory, IS_LOCAL: isRunningLocally, }, debug, systemLog, featureFlags, logs, netlifyConfig, edgeFunctionsBootstrapURL, }) {
|
|
13
|
-
const { edge_functions: declarations = [] } = netlifyConfig;
|
|
14
|
-
const { deno_import_map: userDefinedImportMap } =
|
|
13
|
+
const coreStep = async function ({ buildDir, packagePath, constants: { EDGE_FUNCTIONS_DIST: distDirectory, EDGE_FUNCTIONS_SRC: srcDirectory, INTERNAL_EDGE_FUNCTIONS_SRC: internalSrcDirectory, IS_LOCAL: isRunningLocally, }, debug, systemLog, featureFlags, logs, netlifyConfig, edgeFunctionsBootstrapURL, }) {
|
|
14
|
+
const { edge_functions: declarations = [], functions } = netlifyConfig;
|
|
15
|
+
const { deno_import_map: userDefinedImportMap } = functions['*'];
|
|
16
|
+
const importMapPaths = [userDefinedImportMap];
|
|
15
17
|
const distPath = resolve(buildDir, distDirectory);
|
|
16
18
|
const internalSrcPath = resolve(buildDir, internalSrcDirectory);
|
|
17
19
|
const distImportMapPath = join(dirname(internalSrcPath), IMPORT_MAP_FILENAME);
|
|
18
20
|
const srcPath = srcDirectory ? resolve(buildDir, srcDirectory) : undefined;
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const frameworksAPISrcPath = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT);
|
|
22
|
+
const generatedFunctionPaths = [internalSrcPath];
|
|
23
|
+
if (featureFlags.netlify_build_frameworks_api) {
|
|
24
|
+
if (await pathExists(frameworksAPISrcPath)) {
|
|
25
|
+
generatedFunctionPaths.unshift(frameworksAPISrcPath);
|
|
26
|
+
}
|
|
27
|
+
const frameworkImportMap = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT, FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP);
|
|
28
|
+
if (await pathExists(frameworkImportMap)) {
|
|
29
|
+
importMapPaths.push(frameworkImportMap);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const sourcePaths = [...generatedFunctionPaths, srcPath].filter(Boolean);
|
|
33
|
+
logFunctions({ frameworksAPISrcPath, internalSrcDirectory, internalSrcPath, logs, srcDirectory, srcPath });
|
|
34
|
+
// If we're running in buildbot, we set the Deno cache dir to a directory
|
|
35
|
+
// that is persisted between builds.
|
|
36
|
+
const cacheDirectory = !isRunningLocally ? resolve(buildDir, DENO_CLI_CACHE_DIRECTORY) : undefined;
|
|
24
37
|
// Cleaning up the dist directory, in case it has any artifacts from previous
|
|
25
38
|
// builds.
|
|
26
39
|
try {
|
|
@@ -48,10 +61,10 @@ const coreStep = async function ({ buildDir, constants: { EDGE_FUNCTIONS_DIST: d
|
|
|
48
61
|
debug,
|
|
49
62
|
distImportMapPath,
|
|
50
63
|
featureFlags,
|
|
51
|
-
importMapPaths
|
|
64
|
+
importMapPaths,
|
|
52
65
|
userLogger: (...args) => log(logs, reduceLogLines(args)),
|
|
53
|
-
systemLogger:
|
|
54
|
-
internalSrcFolder:
|
|
66
|
+
systemLogger: systemLog,
|
|
67
|
+
internalSrcFolder: generatedFunctionPaths,
|
|
55
68
|
bootstrapURL: edgeFunctionsBootstrapURL,
|
|
56
69
|
vendorDirectory,
|
|
57
70
|
});
|
|
@@ -84,19 +97,27 @@ const getMetrics = (manifest) => {
|
|
|
84
97
|
// one configured by the user or the internal one) exists. We use a dynamic
|
|
85
98
|
// `condition` because the directories might be created by the build command
|
|
86
99
|
// or plugins.
|
|
87
|
-
const hasEdgeFunctionsDirectories = async function ({ buildDir, constants: { INTERNAL_EDGE_FUNCTIONS_SRC, EDGE_FUNCTIONS_SRC }, }) {
|
|
100
|
+
const hasEdgeFunctionsDirectories = async function ({ buildDir, constants: { INTERNAL_EDGE_FUNCTIONS_SRC, EDGE_FUNCTIONS_SRC }, featureFlags, packagePath, }) {
|
|
88
101
|
const hasFunctionsSrc = EDGE_FUNCTIONS_SRC !== undefined && EDGE_FUNCTIONS_SRC !== '';
|
|
89
102
|
if (hasFunctionsSrc) {
|
|
90
103
|
return true;
|
|
91
104
|
}
|
|
92
105
|
const internalFunctionsSrc = resolve(buildDir, INTERNAL_EDGE_FUNCTIONS_SRC);
|
|
93
|
-
|
|
106
|
+
if (await pathExists(internalFunctionsSrc)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (featureFlags.netlify_build_frameworks_api) {
|
|
110
|
+
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT);
|
|
111
|
+
return await pathExists(frameworkFunctionsSrc);
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
94
114
|
};
|
|
95
|
-
const logFunctions = async ({ internalSrcDirectory, internalSrcPath, logs, srcDirectory: userFunctionsSrc, srcPath, }) => {
|
|
96
|
-
const [userFunctionsSrcExists, userFunctions, internalFunctions] = await Promise.all([
|
|
115
|
+
const logFunctions = async ({ frameworksAPISrcPath, internalSrcDirectory, internalSrcPath, logs, srcDirectory: userFunctionsSrc, srcPath, }) => {
|
|
116
|
+
const [userFunctionsSrcExists, userFunctions, internalFunctions, frameworkFunctions] = await Promise.all([
|
|
97
117
|
srcPath ? pathExists(srcPath) : Promise.resolve(false),
|
|
98
118
|
srcPath ? find([srcPath]) : Promise.resolve([]),
|
|
99
119
|
find([internalSrcPath]),
|
|
120
|
+
frameworksAPISrcPath ? find([frameworksAPISrcPath]) : Promise.resolve([]),
|
|
100
121
|
]);
|
|
101
122
|
logFunctionsToBundle({
|
|
102
123
|
logs,
|
|
@@ -105,6 +126,7 @@ const logFunctions = async ({ internalSrcDirectory, internalSrcPath, logs, srcDi
|
|
|
105
126
|
userFunctionsSrcExists,
|
|
106
127
|
internalFunctions: internalFunctions.map(({ name }) => name),
|
|
107
128
|
internalFunctionsSrc: internalSrcDirectory,
|
|
129
|
+
frameworkFunctions: frameworkFunctions.map(({ name }) => name),
|
|
108
130
|
type: 'Edge Functions',
|
|
109
131
|
});
|
|
110
132
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const bundleFunctions: {
|
|
2
2
|
event: string;
|
|
3
|
-
coreStep: ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC: relativeInternalFunctionsSrc, IS_LOCAL: isRunningLocally, FUNCTIONS_SRC: relativeFunctionsSrc, FUNCTIONS_DIST: relativeFunctionsDist, }, buildDir, logs, netlifyConfig, featureFlags, repositoryRoot, userNodeVersion, systemLog, }: {
|
|
3
|
+
coreStep: ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC: relativeInternalFunctionsSrc, IS_LOCAL: isRunningLocally, FUNCTIONS_SRC: relativeFunctionsSrc, FUNCTIONS_DIST: relativeFunctionsDist, }, buildDir, packagePath, logs, netlifyConfig, featureFlags, repositoryRoot, userNodeVersion, systemLog, }: {
|
|
4
4
|
childEnv: any;
|
|
5
5
|
constants: {
|
|
6
6
|
INTERNAL_FUNCTIONS_SRC: any;
|
|
@@ -9,6 +9,7 @@ export declare const bundleFunctions: {
|
|
|
9
9
|
FUNCTIONS_DIST: any;
|
|
10
10
|
};
|
|
11
11
|
buildDir: any;
|
|
12
|
+
packagePath: any;
|
|
12
13
|
logs: any;
|
|
13
14
|
netlifyConfig: any;
|
|
14
15
|
featureFlags: any;
|
|
@@ -34,12 +35,14 @@ export declare const bundleFunctions: {
|
|
|
34
35
|
coreStepId: string;
|
|
35
36
|
coreStepName: string;
|
|
36
37
|
coreStepDescription: () => string;
|
|
37
|
-
condition: ({ buildDir, constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC } }: {
|
|
38
|
+
condition: ({ buildDir, constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC }, featureFlags, packagePath, }: {
|
|
38
39
|
buildDir: any;
|
|
39
40
|
constants: {
|
|
40
41
|
INTERNAL_FUNCTIONS_SRC: any;
|
|
41
42
|
FUNCTIONS_SRC: any;
|
|
42
43
|
};
|
|
44
|
+
featureFlags: any;
|
|
45
|
+
packagePath: any;
|
|
43
46
|
}) => Promise<boolean>;
|
|
44
47
|
};
|
|
45
48
|
export declare const zipItAndShipIt: {
|
|
@@ -4,6 +4,7 @@ import { pathExists } from 'path-exists';
|
|
|
4
4
|
import { addErrorInfo } from '../../error/info.js';
|
|
5
5
|
import { log } from '../../log/logger.js';
|
|
6
6
|
import { logBundleResults, logFunctionsNonExistingDir, logFunctionsToBundle } from '../../log/messages/core_steps.js';
|
|
7
|
+
import { FRAMEWORKS_API_FUNCTIONS_ENDPOINT } from '../../utils/frameworks_api.js';
|
|
7
8
|
import { getZipError } from './error.js';
|
|
8
9
|
import { getUserAndInternalFunctions, validateFunctionsSrc } from './utils.js';
|
|
9
10
|
import { getZisiParameters } from './zisi.js';
|
|
@@ -45,7 +46,7 @@ const validateCustomRoutes = function (functions) {
|
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
};
|
|
48
|
-
const zipFunctionsAndLogResults = async ({ buildDir, childEnv, featureFlags, functionsConfig, functionsDist, functionsSrc, internalFunctionsSrc, isRunningLocally, logs, repositoryRoot, userNodeVersion, systemLog, }) => {
|
|
49
|
+
const zipFunctionsAndLogResults = async ({ buildDir, childEnv, featureFlags, functionsConfig, functionsDist, functionsSrc, frameworkFunctionsSrc, internalFunctionsSrc, isRunningLocally, logs, repositoryRoot, userNodeVersion, systemLog, }) => {
|
|
49
50
|
const zisiParameters = getZisiParameters({
|
|
50
51
|
buildDir,
|
|
51
52
|
childEnv,
|
|
@@ -61,7 +62,7 @@ const zipFunctionsAndLogResults = async ({ buildDir, childEnv, featureFlags, fun
|
|
|
61
62
|
try {
|
|
62
63
|
// Printing an empty line before bundling output.
|
|
63
64
|
log(logs, '');
|
|
64
|
-
const sourceDirectories = [internalFunctionsSrc, functionsSrc].filter(Boolean);
|
|
65
|
+
const sourceDirectories = [frameworkFunctionsSrc, internalFunctionsSrc, functionsSrc].filter(Boolean);
|
|
65
66
|
const results = await zipItAndShipIt.zipFunctions(sourceDirectories, functionsDist, zisiParameters);
|
|
66
67
|
validateCustomRoutes(results);
|
|
67
68
|
const bundlers = Array.from(getBundlers(results));
|
|
@@ -73,18 +74,22 @@ const zipFunctionsAndLogResults = async ({ buildDir, childEnv, featureFlags, fun
|
|
|
73
74
|
}
|
|
74
75
|
};
|
|
75
76
|
// Plugin to package Netlify functions with @netlify/zip-it-and-ship-it
|
|
76
|
-
const coreStep = async function ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC: relativeInternalFunctionsSrc, IS_LOCAL: isRunningLocally, FUNCTIONS_SRC: relativeFunctionsSrc, FUNCTIONS_DIST: relativeFunctionsDist, }, buildDir, logs, netlifyConfig, featureFlags, repositoryRoot, userNodeVersion, systemLog, }) {
|
|
77
|
+
const coreStep = async function ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC: relativeInternalFunctionsSrc, IS_LOCAL: isRunningLocally, FUNCTIONS_SRC: relativeFunctionsSrc, FUNCTIONS_DIST: relativeFunctionsDist, }, buildDir, packagePath, logs, netlifyConfig, featureFlags, repositoryRoot, userNodeVersion, systemLog, }) {
|
|
77
78
|
const functionsSrc = relativeFunctionsSrc === undefined ? undefined : resolve(buildDir, relativeFunctionsSrc);
|
|
78
79
|
const functionsDist = resolve(buildDir, relativeFunctionsDist);
|
|
79
80
|
const internalFunctionsSrc = resolve(buildDir, relativeInternalFunctionsSrc);
|
|
80
81
|
const internalFunctionsSrcExists = await pathExists(internalFunctionsSrc);
|
|
82
|
+
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_ENDPOINT);
|
|
83
|
+
const frameworkFunctionsSrcExists = await pathExists(frameworkFunctionsSrc);
|
|
81
84
|
const functionsSrcExists = await validateFunctionsSrc({ functionsSrc, relativeFunctionsSrc });
|
|
82
|
-
const [userFunctions = [], internalFunctions = []] = await getUserAndInternalFunctions({
|
|
85
|
+
const [userFunctions = [], internalFunctions = [], frameworkFunctions = []] = await getUserAndInternalFunctions({
|
|
83
86
|
featureFlags,
|
|
84
87
|
functionsSrc,
|
|
85
88
|
functionsSrcExists,
|
|
86
89
|
internalFunctionsSrc,
|
|
87
90
|
internalFunctionsSrcExists,
|
|
91
|
+
frameworkFunctionsSrc,
|
|
92
|
+
frameworkFunctionsSrcExists,
|
|
88
93
|
});
|
|
89
94
|
if (functionsSrc && !functionsSrcExists) {
|
|
90
95
|
logFunctionsNonExistingDir(logs, relativeFunctionsSrc);
|
|
@@ -99,8 +104,9 @@ const coreStep = async function ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC
|
|
|
99
104
|
userFunctionsSrcExists: functionsSrcExists,
|
|
100
105
|
internalFunctions,
|
|
101
106
|
internalFunctionsSrc: relativeInternalFunctionsSrc,
|
|
107
|
+
frameworkFunctions,
|
|
102
108
|
});
|
|
103
|
-
if (userFunctions.length === 0 && internalFunctions.length === 0) {
|
|
109
|
+
if (userFunctions.length === 0 && internalFunctions.length === 0 && frameworkFunctions.length === 0) {
|
|
104
110
|
return {};
|
|
105
111
|
}
|
|
106
112
|
const { bundlers } = await zipFunctionsAndLogResults({
|
|
@@ -110,6 +116,7 @@ const coreStep = async function ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC
|
|
|
110
116
|
functionsConfig: netlifyConfig.functions,
|
|
111
117
|
functionsDist,
|
|
112
118
|
functionsSrc,
|
|
119
|
+
frameworkFunctionsSrc,
|
|
113
120
|
internalFunctionsSrc,
|
|
114
121
|
isRunningLocally,
|
|
115
122
|
logs,
|
|
@@ -129,13 +136,20 @@ const coreStep = async function ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC
|
|
|
129
136
|
// one configured by the user or the internal one) exists. We use a dynamic
|
|
130
137
|
// `condition` because the directories might be created by the build command
|
|
131
138
|
// or plugins.
|
|
132
|
-
const hasFunctionsDirectories = async function ({ buildDir, constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC } }) {
|
|
139
|
+
const hasFunctionsDirectories = async function ({ buildDir, constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC }, featureFlags, packagePath, }) {
|
|
133
140
|
const hasFunctionsSrc = FUNCTIONS_SRC !== undefined && FUNCTIONS_SRC !== '';
|
|
134
141
|
if (hasFunctionsSrc) {
|
|
135
142
|
return true;
|
|
136
143
|
}
|
|
137
144
|
const internalFunctionsSrc = resolve(buildDir, INTERNAL_FUNCTIONS_SRC);
|
|
138
|
-
|
|
145
|
+
if (await pathExists(internalFunctionsSrc)) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
if (featureFlags.netlify_build_frameworks_api) {
|
|
149
|
+
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_ENDPOINT);
|
|
150
|
+
return await pathExists(frameworkFunctionsSrc);
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
139
153
|
};
|
|
140
154
|
export const bundleFunctions = {
|
|
141
155
|
event: 'onBuild',
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export function getUserAndInternalFunctions({ featureFlags, functionsSrc, functionsSrcExists, internalFunctionsSrc, internalFunctionsSrcExists, }: {
|
|
1
|
+
export function getUserAndInternalFunctions({ featureFlags, functionsSrc, functionsSrcExists, internalFunctionsSrc, internalFunctionsSrcExists, frameworkFunctionsSrc, frameworkFunctionsSrcExists, }: {
|
|
2
2
|
featureFlags: any;
|
|
3
3
|
functionsSrc: any;
|
|
4
4
|
functionsSrcExists: any;
|
|
5
5
|
internalFunctionsSrc: any;
|
|
6
6
|
internalFunctionsSrcExists: any;
|
|
7
|
+
frameworkFunctionsSrc: any;
|
|
8
|
+
frameworkFunctionsSrcExists: any;
|
|
7
9
|
}): Promise<any[]>;
|
|
8
10
|
export function validateFunctionsSrc({ functionsSrc, relativeFunctionsSrc }: {
|
|
9
11
|
functionsSrc: any;
|
|
@@ -13,11 +13,14 @@ const getRelativeFunctionMainFiles = async function ({ featureFlags, functionsSr
|
|
|
13
13
|
const functions = await listFunctions(functionsSrc, { featureFlags: zisiFeatureFlags });
|
|
14
14
|
return functions.map(({ mainFile }) => relative(functionsSrc, mainFile));
|
|
15
15
|
};
|
|
16
|
-
export const getUserAndInternalFunctions = ({ featureFlags, functionsSrc, functionsSrcExists, internalFunctionsSrc, internalFunctionsSrcExists, }) => {
|
|
16
|
+
export const getUserAndInternalFunctions = ({ featureFlags, functionsSrc, functionsSrcExists, internalFunctionsSrc, internalFunctionsSrcExists, frameworkFunctionsSrc, frameworkFunctionsSrcExists, }) => {
|
|
17
17
|
const paths = [
|
|
18
18
|
functionsSrcExists ? functionsSrc : undefined,
|
|
19
19
|
internalFunctionsSrcExists ? internalFunctionsSrc : undefined,
|
|
20
20
|
];
|
|
21
|
+
if (featureFlags.netlify_build_frameworks_api && frameworkFunctionsSrcExists) {
|
|
22
|
+
paths.push(frameworkFunctionsSrc);
|
|
23
|
+
}
|
|
21
24
|
return Promise.all(paths.map((path) => path && getRelativeFunctionMainFiles({ featureFlags, functionsSrc: path })));
|
|
22
25
|
};
|
|
23
26
|
// Returns `true` if the functions directory exists and is valid. Returns
|
|
@@ -6,7 +6,7 @@ type GetZisiParametersType = {
|
|
|
6
6
|
featureFlags: FeatureFlags;
|
|
7
7
|
functionsConfig: Record<string, any>;
|
|
8
8
|
functionsDist: string;
|
|
9
|
-
internalFunctionsSrc: string;
|
|
9
|
+
internalFunctionsSrc: string | undefined;
|
|
10
10
|
isRunningLocally: boolean;
|
|
11
11
|
repositoryRoot: string;
|
|
12
12
|
userNodeVersion: string;
|
|
@@ -25,8 +25,9 @@ export const getZisiParameters = ({ buildDir, childEnv, featureFlags, functionsC
|
|
|
25
25
|
normalizeFunctionConfig({ buildDir, functionConfig: object, isRunningLocally, nodeVersion }),
|
|
26
26
|
]);
|
|
27
27
|
const zisiFeatureFlags = getZisiFeatureFlags(featureFlags);
|
|
28
|
-
// Only internal functions
|
|
29
|
-
|
|
28
|
+
// Only the legacy internal functions directory is allowed to have a JSON
|
|
29
|
+
// config file.
|
|
30
|
+
const configFileDirectories = internalFunctionsSrc ? [resolve(internalFunctionsSrc)] : undefined;
|
|
30
31
|
return {
|
|
31
32
|
basePath: buildDir,
|
|
32
33
|
config,
|
|
@@ -35,7 +36,7 @@ export const getZisiParameters = ({ buildDir, childEnv, featureFlags, functionsC
|
|
|
35
36
|
repositoryRoot,
|
|
36
37
|
configFileDirectories,
|
|
37
38
|
internalSrcFolder: internalFunctionsSrc,
|
|
38
|
-
systemLog
|
|
39
|
+
systemLog,
|
|
39
40
|
};
|
|
40
41
|
};
|
|
41
42
|
// The function configuration keys returned by @netlify/config are not an exact
|
package/lib/report/statsd.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface InputStatsDOptions {
|
|
|
4
4
|
port?: number;
|
|
5
5
|
}
|
|
6
6
|
export type StatsDOptions = Required<InputStatsDOptions>;
|
|
7
|
-
export declare const validateStatsDOptions: (statsdOpts: InputStatsDOptions) => statsdOpts is
|
|
7
|
+
export declare const validateStatsDOptions: (statsdOpts: InputStatsDOptions) => statsdOpts is StatsDOptions;
|
|
8
8
|
/**
|
|
9
9
|
* Start a new StatsD Client and a new UDP socket
|
|
10
10
|
*/
|
package/lib/time/measure.d.ts
CHANGED
package/lib/utils/blobs.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
1
|
/** Retrieve the absolute path of the deploy scoped internal blob directories */
|
|
3
2
|
export declare const getBlobsDirs: (buildDir: string, packagePath?: string) => string[];
|
|
4
3
|
interface EnvironmentContext {
|
|
@@ -17,21 +16,28 @@ export declare const getBlobsEnvironmentContext: ({ api, deployId, siteId, token
|
|
|
17
16
|
};
|
|
18
17
|
/**
|
|
19
18
|
* Detect if there are any blobs to upload, and if so, what directory they're
|
|
20
|
-
* in and
|
|
21
|
-
* newer deploy config API endpoint.
|
|
19
|
+
* in and what version of the file-based API is being used.
|
|
22
20
|
*
|
|
23
21
|
* @param buildDir The build directory. (current working directory where the build is executed)
|
|
24
|
-
* @param packagePath An optional package path for
|
|
22
|
+
* @param packagePath An optional package path for monorepos
|
|
25
23
|
* @returns
|
|
26
24
|
*/
|
|
27
25
|
export declare const scanForBlobs: (buildDir: string, packagePath?: string) => Promise<{
|
|
26
|
+
apiVersion: number;
|
|
28
27
|
directory: string;
|
|
29
|
-
isLegacyDirectory: boolean;
|
|
30
28
|
} | null>;
|
|
31
|
-
/**
|
|
32
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Returns the blobs that should be uploaded for a given directory tree. The
|
|
31
|
+
* result is an array with the blob key, the path to its data file, and the
|
|
32
|
+
* path to its metadata file.
|
|
33
|
+
*/
|
|
34
|
+
export declare const getKeysToUpload: (blobsDir: string) => Promise<{
|
|
35
|
+
key: string;
|
|
36
|
+
contentPath: string;
|
|
37
|
+
metadataPath: string;
|
|
38
|
+
}[]>;
|
|
33
39
|
/** Read a file and its metadata file from the blobs directory */
|
|
34
|
-
export declare const getFileWithMetadata: (
|
|
40
|
+
export declare const getFileWithMetadata: (key: string, contentPath: string, metadataPath?: string) => Promise<{
|
|
35
41
|
data: Buffer;
|
|
36
42
|
metadata: Record<string, string>;
|
|
37
43
|
}>;
|
package/lib/utils/blobs.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fdir } from 'fdir';
|
|
4
4
|
import { DEFAULT_API_HOST } from '../core/normalize_flags.js';
|
|
5
|
+
import { FRAMEWORKS_API_BLOBS_ENDPOINT } from './frameworks_api.js';
|
|
5
6
|
const LEGACY_BLOBS_PATH = '.netlify/blobs/deploy';
|
|
6
7
|
const DEPLOY_CONFIG_BLOBS_PATH = '.netlify/deploy/v1/blobs/deploy';
|
|
7
8
|
/** Retrieve the absolute path of the deploy scoped internal blob directories */
|
|
@@ -27,51 +28,78 @@ export const getBlobsEnvironmentContext = ({ api = { host: DEFAULT_API_HOST, sch
|
|
|
27
28
|
};
|
|
28
29
|
/**
|
|
29
30
|
* Detect if there are any blobs to upload, and if so, what directory they're
|
|
30
|
-
* in and
|
|
31
|
-
* newer deploy config API endpoint.
|
|
31
|
+
* in and what version of the file-based API is being used.
|
|
32
32
|
*
|
|
33
33
|
* @param buildDir The build directory. (current working directory where the build is executed)
|
|
34
|
-
* @param packagePath An optional package path for
|
|
34
|
+
* @param packagePath An optional package path for monorepos
|
|
35
35
|
* @returns
|
|
36
36
|
*/
|
|
37
37
|
export const scanForBlobs = async function (buildDir, packagePath) {
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
38
|
+
// We start by looking for files using the Frameworks API.
|
|
39
|
+
const frameworkBlobsDir = path.resolve(buildDir, packagePath || '', FRAMEWORKS_API_BLOBS_ENDPOINT, 'deploy');
|
|
40
|
+
const frameworkBlobsDirScan = await new fdir().onlyCounts().crawl(frameworkBlobsDir).withPromise();
|
|
41
|
+
if (frameworkBlobsDirScan.files > 0) {
|
|
41
42
|
return {
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
apiVersion: 3,
|
|
44
|
+
directory: frameworkBlobsDir,
|
|
44
45
|
};
|
|
45
46
|
}
|
|
47
|
+
// Next, we look for files using the legacy Deploy Configuration API. It was
|
|
48
|
+
// short-lived and not really documented, but we do have sites relying on
|
|
49
|
+
// it, so we must support it for backwards-compatibility.
|
|
50
|
+
const deployConfigBlobsDir = path.resolve(buildDir, packagePath || '', DEPLOY_CONFIG_BLOBS_PATH);
|
|
51
|
+
const deployConfigBlobsDirScan = await new fdir().onlyCounts().crawl(deployConfigBlobsDir).withPromise();
|
|
52
|
+
if (deployConfigBlobsDirScan.files > 0) {
|
|
53
|
+
return {
|
|
54
|
+
apiVersion: 2,
|
|
55
|
+
directory: deployConfigBlobsDir,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Finally, we look for files using the initial spec for file-based Blobs
|
|
59
|
+
// uploads.
|
|
46
60
|
const legacyBlobsDir = path.resolve(buildDir, packagePath || '', LEGACY_BLOBS_PATH);
|
|
47
61
|
const legacyBlobsDirScan = await new fdir().onlyCounts().crawl(legacyBlobsDir).withPromise();
|
|
48
62
|
if (legacyBlobsDirScan.files > 0) {
|
|
49
63
|
return {
|
|
64
|
+
apiVersion: 1,
|
|
50
65
|
directory: legacyBlobsDir,
|
|
51
|
-
isLegacyDirectory: true,
|
|
52
66
|
};
|
|
53
67
|
}
|
|
54
68
|
return null;
|
|
55
69
|
};
|
|
56
70
|
const METADATA_PREFIX = '$';
|
|
57
71
|
const METADATA_SUFFIX = '.json';
|
|
58
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Returns the blobs that should be uploaded for a given directory tree. The
|
|
74
|
+
* result is an array with the blob key, the path to its data file, and the
|
|
75
|
+
* path to its metadata file.
|
|
76
|
+
*/
|
|
59
77
|
export const getKeysToUpload = async (blobsDir) => {
|
|
78
|
+
const blobsToUpload = [];
|
|
60
79
|
const files = await new fdir()
|
|
61
80
|
.withRelativePaths() // we want the relative path from the blobsDir
|
|
62
81
|
.filter((fpath) => !path.basename(fpath).startsWith(METADATA_PREFIX))
|
|
63
82
|
.crawl(blobsDir)
|
|
64
83
|
.withPromise();
|
|
65
|
-
|
|
66
|
-
|
|
84
|
+
files.forEach((filePath) => {
|
|
85
|
+
const key = filePath.split(path.sep).join('/');
|
|
86
|
+
const contentPath = path.resolve(blobsDir, filePath);
|
|
87
|
+
const basename = path.basename(filePath);
|
|
88
|
+
const metadataPath = path.resolve(blobsDir, path.dirname(filePath), `${METADATA_PREFIX}${basename}${METADATA_SUFFIX}`);
|
|
89
|
+
blobsToUpload.push({
|
|
90
|
+
key,
|
|
91
|
+
contentPath,
|
|
92
|
+
metadataPath,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
return blobsToUpload;
|
|
67
96
|
};
|
|
68
97
|
/** Read a file and its metadata file from the blobs directory */
|
|
69
|
-
export const getFileWithMetadata = async (
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const [data, metadata] = await Promise.all([readFile(contentPath), readMetadata(metadataPath)]).catch((err) => {
|
|
98
|
+
export const getFileWithMetadata = async (key, contentPath, metadataPath) => {
|
|
99
|
+
const [data, metadata] = await Promise.all([
|
|
100
|
+
readFile(contentPath),
|
|
101
|
+
metadataPath ? readMetadata(metadataPath) : {},
|
|
102
|
+
]).catch((err) => {
|
|
75
103
|
throw new Error(`Failed while reading '${key}' and its metadata: ${err.message}`);
|
|
76
104
|
});
|
|
77
105
|
return { data, metadata };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const FRAMEWORKS_API_BLOBS_ENDPOINT = ".netlify/v1/blobs";
|
|
2
|
+
export declare const FRAMEWORKS_API_CONFIG_ENDPOINT = ".netlify/v1/config.json";
|
|
3
|
+
export declare const FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT = ".netlify/v1/edge-functions";
|
|
4
|
+
export declare const FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP = "import_map.json";
|
|
5
|
+
export declare const FRAMEWORKS_API_FUNCTIONS_ENDPOINT = ".netlify/v1/functions";
|
|
6
|
+
type DirectoryTreeFiles = Map<string, string[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Traverses a directory tree in search of leaf files. The key of each leaf
|
|
9
|
+
* file is determined by its path relative to the base directory.
|
|
10
|
+
*
|
|
11
|
+
* For example, given the following directory tree:
|
|
12
|
+
*
|
|
13
|
+
* .netlify/
|
|
14
|
+
* └── v1/
|
|
15
|
+
* └── blobs/
|
|
16
|
+
* └── deploy/
|
|
17
|
+
* ├── example.com/
|
|
18
|
+
* │ └── blob
|
|
19
|
+
* └── netlify.com/
|
|
20
|
+
* ├── blob
|
|
21
|
+
* └── blob.meta.json
|
|
22
|
+
*
|
|
23
|
+
* If this method is called on `.netlify/v1/blobs/deploy` with `blob` and
|
|
24
|
+
* `blob.meta.json` as leaf names, it will return the following Map:
|
|
25
|
+
*
|
|
26
|
+
* {
|
|
27
|
+
* "example.com" => [
|
|
28
|
+
* "/full/path/to/.netlify/v1/blobs/deploy/example.com/blob"
|
|
29
|
+
* ],
|
|
30
|
+
* "netlify.com" => [
|
|
31
|
+
* "/full/path/to/.netlify/v1/blobs/deploy/netlify.com/blob",
|
|
32
|
+
* "/full/path/to/.netlify/v1/blobs/deploy/netlify.com/blob.meta.json"
|
|
33
|
+
* ]
|
|
34
|
+
* }
|
|
35
|
+
*/
|
|
36
|
+
export declare const findFiles: (directory: string, leafNames: Set<string>) => Promise<DirectoryTreeFiles>;
|
|
37
|
+
/**
|
|
38
|
+
* Finds blobs and their corresponding metadata files in a given directory.
|
|
39
|
+
*/
|
|
40
|
+
export declare const getBlobs: (blobsDirectory: string) => Promise<{
|
|
41
|
+
key: string;
|
|
42
|
+
contentPath: string;
|
|
43
|
+
metadataPath?: string;
|
|
44
|
+
}[]>;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { basename, dirname, resolve, sep } from 'node:path';
|
|
2
|
+
import { fdir } from 'fdir';
|
|
3
|
+
export const FRAMEWORKS_API_BLOBS_ENDPOINT = '.netlify/v1/blobs';
|
|
4
|
+
export const FRAMEWORKS_API_CONFIG_ENDPOINT = '.netlify/v1/config.json';
|
|
5
|
+
export const FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT = '.netlify/v1/edge-functions';
|
|
6
|
+
export const FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP = 'import_map.json';
|
|
7
|
+
export const FRAMEWORKS_API_FUNCTIONS_ENDPOINT = '.netlify/v1/functions';
|
|
8
|
+
/**
|
|
9
|
+
* Traverses a directory tree in search of leaf files. The key of each leaf
|
|
10
|
+
* file is determined by its path relative to the base directory.
|
|
11
|
+
*
|
|
12
|
+
* For example, given the following directory tree:
|
|
13
|
+
*
|
|
14
|
+
* .netlify/
|
|
15
|
+
* └── v1/
|
|
16
|
+
* └── blobs/
|
|
17
|
+
* └── deploy/
|
|
18
|
+
* ├── example.com/
|
|
19
|
+
* │ └── blob
|
|
20
|
+
* └── netlify.com/
|
|
21
|
+
* ├── blob
|
|
22
|
+
* └── blob.meta.json
|
|
23
|
+
*
|
|
24
|
+
* If this method is called on `.netlify/v1/blobs/deploy` with `blob` and
|
|
25
|
+
* `blob.meta.json` as leaf names, it will return the following Map:
|
|
26
|
+
*
|
|
27
|
+
* {
|
|
28
|
+
* "example.com" => [
|
|
29
|
+
* "/full/path/to/.netlify/v1/blobs/deploy/example.com/blob"
|
|
30
|
+
* ],
|
|
31
|
+
* "netlify.com" => [
|
|
32
|
+
* "/full/path/to/.netlify/v1/blobs/deploy/netlify.com/blob",
|
|
33
|
+
* "/full/path/to/.netlify/v1/blobs/deploy/netlify.com/blob.meta.json"
|
|
34
|
+
* ]
|
|
35
|
+
* }
|
|
36
|
+
*/
|
|
37
|
+
export const findFiles = async (directory, leafNames) => {
|
|
38
|
+
const results = new Map();
|
|
39
|
+
const groups = await new fdir()
|
|
40
|
+
.withRelativePaths()
|
|
41
|
+
.filter((path) => leafNames.has(basename(path)))
|
|
42
|
+
.group()
|
|
43
|
+
.crawl(directory)
|
|
44
|
+
.withPromise();
|
|
45
|
+
groups.forEach(({ files }) => {
|
|
46
|
+
if (files.length === 0) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const key = dirname(files[0]).split(sep).join('/');
|
|
50
|
+
results.set(key, files.map((relativePath) => resolve(directory, relativePath)));
|
|
51
|
+
});
|
|
52
|
+
return results;
|
|
53
|
+
};
|
|
54
|
+
const BLOBS_CONTENT_FILE = 'blob';
|
|
55
|
+
const BLOBS_META_FILE = 'blob.meta.json';
|
|
56
|
+
/**
|
|
57
|
+
* Finds blobs and their corresponding metadata files in a given directory.
|
|
58
|
+
*/
|
|
59
|
+
export const getBlobs = async (blobsDirectory) => {
|
|
60
|
+
const files = await findFiles(blobsDirectory, new Set([BLOBS_CONTENT_FILE, BLOBS_META_FILE]));
|
|
61
|
+
const blobs = [];
|
|
62
|
+
files.forEach((filePaths, key) => {
|
|
63
|
+
const contentPath = filePaths.find((path) => basename(path) === 'blob');
|
|
64
|
+
if (!contentPath) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const metadataPath = filePaths.find((path) => basename(path) === BLOBS_META_FILE);
|
|
68
|
+
blobs.push({
|
|
69
|
+
key,
|
|
70
|
+
contentPath,
|
|
71
|
+
metadataPath,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
return blobs;
|
|
75
|
+
};
|
package/lib/utils/package.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ type PackageResult = {
|
|
|
6
6
|
/**
|
|
7
7
|
* Retrieve `package.json` from a specific directory
|
|
8
8
|
*/
|
|
9
|
-
export declare const getPackageJson: (cwd: string, options?: Omit<Options,
|
|
9
|
+
export declare const getPackageJson: (cwd: string, options?: Omit<Options, "cwd">) => Promise<PackageResult>;
|
|
10
10
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/build",
|
|
3
|
-
"version": "29.
|
|
3
|
+
"version": "29.49.1",
|
|
4
4
|
"description": "Netlify build module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./lib/index.js",
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"@bugsnag/js": "^7.0.0",
|
|
71
71
|
"@netlify/blobs": "^7.3.0",
|
|
72
72
|
"@netlify/cache-utils": "^5.1.5",
|
|
73
|
-
"@netlify/config": "^20.15.
|
|
74
|
-
"@netlify/edge-bundler": "12.
|
|
73
|
+
"@netlify/config": "^20.15.4",
|
|
74
|
+
"@netlify/edge-bundler": "12.1.1",
|
|
75
75
|
"@netlify/framework-info": "^9.8.13",
|
|
76
76
|
"@netlify/functions-utils": "^5.2.69",
|
|
77
77
|
"@netlify/git-utils": "^5.1.1",
|
|
@@ -165,5 +165,5 @@
|
|
|
165
165
|
"engines": {
|
|
166
166
|
"node": "^14.16.0 || >=16.0.0"
|
|
167
167
|
},
|
|
168
|
-
"gitHead": "
|
|
168
|
+
"gitHead": "b993b18469d4355e164f605e742e69dcb5612905"
|
|
169
169
|
}
|