@netlify/build 28.0.0-beta → 28.0.0
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/bin.js +5 -0
- package/lib/core/bin.js +66 -0
- package/lib/core/build.js +355 -0
- package/lib/core/config.js +121 -0
- package/lib/core/constants.js +116 -0
- package/lib/core/dev.js +27 -0
- package/lib/core/dry.js +21 -0
- package/lib/core/feature_flags.js +19 -0
- package/lib/core/flags.js +201 -0
- package/lib/core/lingering.js +68 -0
- package/lib/core/main.js +123 -0
- package/lib/core/missing_side_file.js +17 -0
- package/lib/core/normalize_flags.js +59 -0
- package/lib/core/severity.js +20 -0
- package/lib/core/user_node_version.js +32 -0
- package/lib/env/changes.js +43 -0
- package/lib/env/main.js +14 -0
- package/lib/env/metadata.js +68 -0
- package/lib/error/api.js +37 -0
- package/lib/error/build.js +36 -0
- package/{src → lib}/error/cancel.js +5 -6
- package/lib/error/colors.js +9 -0
- package/lib/error/handle.js +46 -0
- package/lib/error/info.js +37 -0
- package/lib/error/monitor/location.js +16 -0
- package/lib/error/monitor/normalize.js +86 -0
- package/lib/error/monitor/print.js +20 -0
- package/lib/error/monitor/report.js +120 -0
- package/lib/error/monitor/start.js +58 -0
- package/lib/error/parse/clean_stack.js +70 -0
- package/lib/error/parse/location.js +50 -0
- package/lib/error/parse/normalize.js +24 -0
- package/lib/error/parse/parse.js +67 -0
- package/lib/error/parse/plugin.js +55 -0
- package/lib/error/parse/properties.js +16 -0
- package/lib/error/parse/serialize_log.js +34 -0
- package/lib/error/parse/serialize_status.js +18 -0
- package/lib/error/parse/stack.js +34 -0
- package/lib/error/type.js +171 -0
- package/lib/install/functions.js +20 -0
- package/lib/install/local.js +45 -0
- package/lib/install/main.js +67 -0
- package/lib/install/missing.js +54 -0
- package/{src → lib}/log/colors.js +15 -22
- package/lib/log/description.js +21 -0
- package/lib/log/header.js +14 -0
- package/lib/log/header_func.js +13 -0
- package/lib/log/logger.js +130 -0
- package/lib/log/messages/compatibility.js +120 -0
- package/lib/log/messages/config.js +91 -0
- package/lib/log/messages/core.js +50 -0
- package/lib/log/messages/core_steps.js +75 -0
- package/lib/log/messages/dry.js +41 -0
- package/lib/log/messages/install.js +25 -0
- package/lib/log/messages/ipc.js +29 -0
- package/lib/log/messages/mutations.js +62 -0
- package/{src → lib}/log/messages/plugins.js +18 -32
- package/lib/log/messages/status.js +14 -0
- package/lib/log/messages/steps.js +18 -0
- package/lib/log/old_version.js +32 -0
- package/lib/log/serialize.js +10 -0
- package/lib/log/stream.js +68 -0
- package/lib/log/theme.js +25 -0
- package/lib/plugins/child/diff.js +46 -0
- package/lib/plugins/child/error.js +26 -0
- package/lib/plugins/child/lazy.js +15 -0
- package/lib/plugins/child/load.js +22 -0
- package/lib/plugins/child/logic.js +57 -0
- package/lib/plugins/child/main.js +37 -0
- package/lib/plugins/child/run.js +19 -0
- package/lib/plugins/child/status.js +63 -0
- package/lib/plugins/child/typescript.js +28 -0
- package/lib/plugins/child/utils.js +42 -0
- package/lib/plugins/child/validate.js +31 -0
- package/lib/plugins/compatibility.js +104 -0
- package/{src → lib}/plugins/error.js +31 -35
- package/{src → lib}/plugins/events.js +7 -12
- package/lib/plugins/expected_version.js +81 -0
- package/lib/plugins/ipc.js +120 -0
- package/lib/plugins/list.js +73 -0
- package/lib/plugins/load.js +50 -0
- package/lib/plugins/manifest/check.js +85 -0
- package/lib/plugins/manifest/load.js +38 -0
- package/lib/plugins/manifest/main.js +17 -0
- package/lib/plugins/manifest/path.js +24 -0
- package/lib/plugins/manifest/validate.js +91 -0
- package/lib/plugins/node_version.js +30 -0
- package/lib/plugins/options.js +55 -0
- package/lib/plugins/pinned_version.js +83 -0
- package/lib/plugins/resolve.js +110 -0
- package/lib/plugins/spawn.js +54 -0
- package/lib/plugins_core/add.js +35 -0
- package/lib/plugins_core/build_command.js +50 -0
- package/lib/plugins_core/deploy/buildbot_client.js +87 -0
- package/lib/plugins_core/deploy/index.js +49 -0
- package/{src → lib}/plugins_core/deploy/manifest.yml +0 -0
- package/lib/plugins_core/edge_functions/index.js +79 -0
- package/lib/plugins_core/edge_functions/lib/error.js +17 -0
- package/lib/plugins_core/edge_functions/lib/internal_manifest.js +50 -0
- package/lib/plugins_core/edge_functions/validate_manifest/validate_edge_functions_manifest.js +75 -0
- package/lib/plugins_core/functions/error.js +123 -0
- package/lib/plugins_core/functions/feature_flags.js +6 -0
- package/lib/plugins_core/functions/index.js +114 -0
- package/lib/plugins_core/functions/utils.js +45 -0
- package/lib/plugins_core/functions/zisi.js +39 -0
- package/{src → lib}/plugins_core/functions_install/index.js +8 -11
- package/{src → lib}/plugins_core/functions_install/manifest.yml +0 -0
- package/lib/plugins_core/list.js +20 -0
- package/lib/status/add.js +30 -0
- package/lib/status/colors.js +18 -0
- package/lib/status/load_error.js +10 -0
- package/lib/status/report.js +83 -0
- package/lib/status/success.js +14 -0
- package/lib/steps/core_step.js +57 -0
- package/lib/steps/error.js +65 -0
- package/lib/steps/get.js +43 -0
- package/lib/steps/plugin.js +55 -0
- package/lib/steps/return.js +25 -0
- package/lib/steps/run_core_steps.js +147 -0
- package/lib/steps/run_step.js +188 -0
- package/lib/steps/run_steps.js +96 -0
- package/lib/steps/update_config.js +66 -0
- package/lib/telemetry/main.js +97 -0
- package/lib/time/aggregate.js +120 -0
- package/lib/time/main.js +37 -0
- package/lib/time/measure.js +18 -0
- package/lib/time/report.js +47 -0
- package/lib/utils/errors.js +13 -0
- package/lib/utils/json.js +15 -0
- package/{src → lib}/utils/omit.js +3 -4
- package/lib/utils/package.js +22 -0
- package/lib/utils/remove_falsy.js +8 -0
- package/lib/utils/resolve.js +41 -0
- package/lib/utils/runtime.js +5 -0
- package/lib/utils/semver.js +28 -0
- package/package.json +39 -20
- package/types/config/netlify_config.d.ts +4 -4
- package/types/netlify_plugin_constants.d.ts +8 -8
- package/src/core/bin.js +0 -83
- package/src/core/config.js +0 -186
- package/src/core/constants.js +0 -156
- package/src/core/dry.js +0 -39
- package/src/core/feature_flags.js +0 -21
- package/src/core/flags.js +0 -194
- package/src/core/lingering.js +0 -85
- package/src/core/main.js +0 -692
- package/src/core/missing_side_file.js +0 -29
- package/src/core/normalize_flags.js +0 -69
- package/src/core/severity.js +0 -22
- package/src/core/user_node_version.js +0 -41
- package/src/env/changes.js +0 -52
- package/src/env/main.js +0 -19
- package/src/env/metadata.js +0 -81
- package/src/error/api.js +0 -46
- package/src/error/build.js +0 -50
- package/src/error/colors.js +0 -11
- package/src/error/handle.js +0 -57
- package/src/error/info.js +0 -46
- package/src/error/monitor/location.js +0 -21
- package/src/error/monitor/normalize.js +0 -77
- package/src/error/monitor/print.js +0 -42
- package/src/error/monitor/report.js +0 -133
- package/src/error/monitor/start.js +0 -69
- package/src/error/parse/clean_stack.js +0 -87
- package/src/error/parse/location.js +0 -58
- package/src/error/parse/normalize.js +0 -29
- package/src/error/parse/parse.js +0 -97
- package/src/error/parse/plugin.js +0 -70
- package/src/error/parse/properties.js +0 -23
- package/src/error/parse/serialize_log.js +0 -42
- package/src/error/parse/serialize_status.js +0 -23
- package/src/error/parse/stack.js +0 -43
- package/src/error/type.js +0 -182
- package/src/install/functions.js +0 -28
- package/src/install/local.js +0 -62
- package/src/install/main.js +0 -81
- package/src/install/missing.js +0 -67
- package/src/log/description.js +0 -26
- package/src/log/header.js +0 -16
- package/src/log/header_func.js +0 -17
- package/src/log/logger.js +0 -107
- package/src/log/messages/compatibility.js +0 -164
- package/src/log/messages/config.js +0 -105
- package/src/log/messages/core.js +0 -70
- package/src/log/messages/core_steps.js +0 -104
- package/src/log/messages/dry.js +0 -63
- package/src/log/messages/install.js +0 -20
- package/src/log/messages/ipc.js +0 -38
- package/src/log/messages/mutations.js +0 -82
- package/src/log/messages/status.js +0 -16
- package/src/log/messages/steps.js +0 -22
- package/src/log/old_version.js +0 -41
- package/src/log/serialize.js +0 -13
- package/src/log/stream.js +0 -85
- package/src/log/theme.js +0 -26
- package/src/plugins/child/diff.js +0 -55
- package/src/plugins/child/error.js +0 -32
- package/src/plugins/child/lazy.js +0 -18
- package/src/plugins/child/load.js +0 -29
- package/src/plugins/child/logic.js +0 -57
- package/src/plugins/child/main.js +0 -51
- package/src/plugins/child/run.js +0 -28
- package/src/plugins/child/status.js +0 -67
- package/src/plugins/child/typescript.js +0 -45
- package/src/plugins/child/utils.js +0 -56
- package/src/plugins/child/validate.js +0 -34
- package/src/plugins/compatibility.js +0 -128
- package/src/plugins/expected_version.js +0 -119
- package/src/plugins/ipc.js +0 -145
- package/src/plugins/list.js +0 -86
- package/src/plugins/load.js +0 -70
- package/src/plugins/manifest/check.js +0 -106
- package/src/plugins/manifest/load.js +0 -41
- package/src/plugins/manifest/main.js +0 -22
- package/src/plugins/manifest/path.js +0 -31
- package/src/plugins/manifest/validate.js +0 -108
- package/src/plugins/node_version.js +0 -50
- package/src/plugins/options.js +0 -88
- package/src/plugins/pinned_version.js +0 -131
- package/src/plugins/resolve.js +0 -152
- package/src/plugins/spawn.js +0 -66
- package/src/plugins_core/add.js +0 -49
- package/src/plugins_core/build_command.js +0 -75
- package/src/plugins_core/deploy/buildbot_client.js +0 -102
- package/src/plugins_core/deploy/index.js +0 -73
- package/src/plugins_core/edge_functions/index.js +0 -107
- package/src/plugins_core/edge_functions/lib/internal_manifest.js +0 -54
- package/src/plugins_core/functions/error.js +0 -163
- package/src/plugins_core/functions/feature_flags.js +0 -6
- package/src/plugins_core/functions/index.js +0 -160
- package/src/plugins_core/functions/utils.js +0 -66
- package/src/plugins_core/functions/zisi.js +0 -53
- package/src/plugins_core/list.js +0 -27
- package/src/status/add.js +0 -36
- package/src/status/colors.js +0 -23
- package/src/status/load_error.js +0 -11
- package/src/status/report.js +0 -128
- package/src/status/success.js +0 -18
- package/src/steps/core_step.js +0 -90
- package/src/steps/error.js +0 -102
- package/src/steps/get.js +0 -32
- package/src/steps/plugin.js +0 -85
- package/src/steps/return.js +0 -52
- package/src/steps/run_core_steps.js +0 -194
- package/src/steps/run_step.js +0 -300
- package/src/steps/run_steps.js +0 -179
- package/src/steps/update_config.js +0 -93
- package/src/telemetry/main.js +0 -136
- package/src/time/aggregate.js +0 -146
- package/src/time/main.js +0 -48
- package/src/time/measure.js +0 -22
- package/src/time/report.js +0 -59
- package/src/utils/errors.js +0 -12
- package/src/utils/json.js +0 -19
- package/src/utils/package.js +0 -23
- package/src/utils/remove_falsy.js +0 -10
- package/src/utils/resolve.js +0 -46
- package/src/utils/semver.js +0 -34
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import net from 'net';
|
|
2
|
+
import { normalize, resolve, relative } from 'path';
|
|
3
|
+
import { promisify } from 'util';
|
|
4
|
+
import { pEvent } from 'p-event';
|
|
5
|
+
import { addErrorInfo } from '../../error/info.js';
|
|
6
|
+
import { runsAfterDeploy } from '../../plugins/events.js';
|
|
7
|
+
import { addAsyncErrorMessage } from '../../utils/errors.js';
|
|
8
|
+
export const createBuildbotClient = function (buildbotServerSocket) {
|
|
9
|
+
const connectionOpts = getConnectionOpts(buildbotServerSocket);
|
|
10
|
+
const client = net.createConnection(connectionOpts);
|
|
11
|
+
return client;
|
|
12
|
+
};
|
|
13
|
+
// Windows does not support Unix sockets well, so we also support `host:port`
|
|
14
|
+
const getConnectionOpts = function (buildbotServerSocket) {
|
|
15
|
+
if (!buildbotServerSocket.includes(':')) {
|
|
16
|
+
return { path: buildbotServerSocket };
|
|
17
|
+
}
|
|
18
|
+
const [host, port] = buildbotServerSocket.split(':');
|
|
19
|
+
return { host, port };
|
|
20
|
+
};
|
|
21
|
+
const eConnectBuildbotClient = async function (client) {
|
|
22
|
+
await pEvent(client, 'connect');
|
|
23
|
+
};
|
|
24
|
+
export const connectBuildbotClient = addAsyncErrorMessage(eConnectBuildbotClient, 'Could not connect to buildbot');
|
|
25
|
+
export const closeBuildbotClient = async function (client) {
|
|
26
|
+
if (client.destroyed) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await promisify(client.end.bind(client))();
|
|
30
|
+
};
|
|
31
|
+
const cWritePayload = async function (buildbotClient, payload) {
|
|
32
|
+
await promisify(buildbotClient.write.bind(buildbotClient))(JSON.stringify(payload));
|
|
33
|
+
};
|
|
34
|
+
const writePayload = addAsyncErrorMessage(cWritePayload, 'Could not send payload to buildbot');
|
|
35
|
+
const cGetNextParsedResponsePromise = async function (buildbotClient) {
|
|
36
|
+
const data = await pEvent(buildbotClient, 'data');
|
|
37
|
+
return JSON.parse(data);
|
|
38
|
+
};
|
|
39
|
+
const getNextParsedResponsePromise = addAsyncErrorMessage(cGetNextParsedResponsePromise, 'Invalid response from buildbot');
|
|
40
|
+
export const deploySiteWithBuildbotClient = async function ({ client, events, buildDir, repositoryRoot, constants }) {
|
|
41
|
+
const action = shouldWaitForPostProcessing(events) ? 'deploySiteAndAwaitLive' : 'deploySite';
|
|
42
|
+
const deployDir = getDeployDir({ buildDir, repositoryRoot, constants });
|
|
43
|
+
const payload = { action, deployDir };
|
|
44
|
+
const [{ succeeded, values: { error, error_type: errorType } = {} }] = await Promise.all([
|
|
45
|
+
getNextParsedResponsePromise(client),
|
|
46
|
+
writePayload(client, payload),
|
|
47
|
+
]);
|
|
48
|
+
if (!succeeded) {
|
|
49
|
+
return handleDeployError(error, errorType);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
// The file paths in the buildbot are relative to the repository root.
|
|
53
|
+
// However, the file paths in Build plugins, including `constants.PUBLISH_DIR`
|
|
54
|
+
// are relative to the build directory, which is different when there is a
|
|
55
|
+
// base directory. This converts it.
|
|
56
|
+
// We need to call `normalize()` in case the publish directory is the
|
|
57
|
+
// repository root, so `deployDir` is "." not ""
|
|
58
|
+
const getDeployDir = function ({ buildDir, repositoryRoot, constants: { PUBLISH_DIR } }) {
|
|
59
|
+
const absolutePublishDir = resolve(buildDir, PUBLISH_DIR);
|
|
60
|
+
const relativePublishDir = relative(repositoryRoot, absolutePublishDir);
|
|
61
|
+
const deployDir = normalize(relativePublishDir);
|
|
62
|
+
return deployDir;
|
|
63
|
+
};
|
|
64
|
+
// We distinguish between user errors and system errors during deploys
|
|
65
|
+
const handleDeployError = function (error, errorType) {
|
|
66
|
+
const errorIs422 = error !== undefined && error.code === '422';
|
|
67
|
+
const errMsg = errorIs422
|
|
68
|
+
? `
|
|
69
|
+
File upload failed because of mismatched SHAs.
|
|
70
|
+
This can happen when files are changed after the deployment process has started but before they are uploaded.
|
|
71
|
+
|
|
72
|
+
Error: ${error}
|
|
73
|
+
`
|
|
74
|
+
: `Deploy did not succeed: ${error}`;
|
|
75
|
+
const errorA = new Error(errMsg);
|
|
76
|
+
const errorInfo = errorType === 'user' ? { type: 'resolveConfig' } : { type: 'coreStep', location: { coreStepName: 'Deploy site' } };
|
|
77
|
+
addErrorInfo(errorA, errorInfo);
|
|
78
|
+
throw errorA;
|
|
79
|
+
};
|
|
80
|
+
// We only wait for post-processing (last stage before site deploy) if the build
|
|
81
|
+
// has some plugins that do post-deploy logic
|
|
82
|
+
const shouldWaitForPostProcessing = function (events) {
|
|
83
|
+
return events.some(hasPostDeployLogic);
|
|
84
|
+
};
|
|
85
|
+
const hasPostDeployLogic = function (event) {
|
|
86
|
+
return runsAfterDeploy(event);
|
|
87
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { saveUpdatedConfig, restoreUpdatedConfig } from '../../core/config.js';
|
|
2
|
+
import { logDeploySuccess } from '../../log/messages/plugins.js';
|
|
3
|
+
import { createBuildbotClient, connectBuildbotClient, closeBuildbotClient, deploySiteWithBuildbotClient, } from './buildbot_client.js';
|
|
4
|
+
const coreStep = async function ({ buildDir, configPath, repositoryRoot, constants, buildbotServerSocket, events, logs, featureFlags, context, branch, configMutations, headersPath, redirectsPath, debug, saveConfig, }) {
|
|
5
|
+
const client = createBuildbotClient(buildbotServerSocket);
|
|
6
|
+
try {
|
|
7
|
+
await connectBuildbotClient(client);
|
|
8
|
+
await saveUpdatedConfig({
|
|
9
|
+
configMutations,
|
|
10
|
+
buildDir,
|
|
11
|
+
repositoryRoot,
|
|
12
|
+
configPath,
|
|
13
|
+
headersPath,
|
|
14
|
+
redirectsPath,
|
|
15
|
+
logs,
|
|
16
|
+
featureFlags,
|
|
17
|
+
context,
|
|
18
|
+
branch,
|
|
19
|
+
debug,
|
|
20
|
+
saveConfig,
|
|
21
|
+
});
|
|
22
|
+
await deploySiteWithBuildbotClient({ client, events, buildDir, repositoryRoot, constants });
|
|
23
|
+
await restoreUpdatedConfig({
|
|
24
|
+
configMutations,
|
|
25
|
+
buildDir,
|
|
26
|
+
repositoryRoot,
|
|
27
|
+
configPath,
|
|
28
|
+
headersPath,
|
|
29
|
+
redirectsPath,
|
|
30
|
+
saveConfig,
|
|
31
|
+
});
|
|
32
|
+
logDeploySuccess(logs);
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await closeBuildbotClient(client);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const shouldDeploy = function ({ buildbotServerSocket }) {
|
|
40
|
+
return buildbotServerSocket !== undefined;
|
|
41
|
+
};
|
|
42
|
+
export const deploySite = {
|
|
43
|
+
event: 'onPostBuild',
|
|
44
|
+
coreStep,
|
|
45
|
+
coreStepId: 'deploy_site',
|
|
46
|
+
coreStepName: 'Deploy site',
|
|
47
|
+
coreStepDescription: () => 'Deploy site',
|
|
48
|
+
condition: shouldDeploy,
|
|
49
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { dirname, join, resolve } from 'path';
|
|
3
|
+
import { bundle, find } from '@netlify/edge-bundler';
|
|
4
|
+
import { pathExists } from 'path-exists';
|
|
5
|
+
import { logFunctionsToBundle } from '../../log/messages/core_steps.js';
|
|
6
|
+
import { tagBundlingError } from './lib/error.js';
|
|
7
|
+
import { parseManifest } from './lib/internal_manifest.js';
|
|
8
|
+
import { validateEdgeFunctionsManifest } from './validate_manifest/validate_edge_functions_manifest.js';
|
|
9
|
+
// TODO: Replace this with a custom cache directory.
|
|
10
|
+
const DENO_CLI_CACHE_DIRECTORY = '.netlify/plugins/deno-cli';
|
|
11
|
+
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, }) {
|
|
13
|
+
const { edge_functions: configDeclarations = [] } = netlifyConfig;
|
|
14
|
+
const distPath = resolve(buildDir, distDirectory);
|
|
15
|
+
const internalSrcPath = resolve(buildDir, internalSrcDirectory);
|
|
16
|
+
const distImportMapPath = join(dirname(internalSrcPath), IMPORT_MAP_FILENAME);
|
|
17
|
+
const srcPath = srcDirectory ? resolve(buildDir, srcDirectory) : undefined;
|
|
18
|
+
const sourcePaths = [internalSrcPath, srcPath].filter(Boolean);
|
|
19
|
+
logFunctions({ internalSrcDirectory, internalSrcPath, logs, srcDirectory, srcPath });
|
|
20
|
+
const { declarations: internalDeclarations, importMap } = await parseManifest(internalSrcPath, systemLog);
|
|
21
|
+
const declarations = [...configDeclarations, ...internalDeclarations];
|
|
22
|
+
// If we're running in buildbot and the feature flag is enabled, we set the
|
|
23
|
+
// Deno cache dir to a directory that is persisted between builds.
|
|
24
|
+
const cacheDirectory = !isRunningLocally && featureFlags.edge_functions_cache_cli ? resolve(buildDir, DENO_CLI_CACHE_DIRECTORY) : undefined;
|
|
25
|
+
// Edge Bundler expects the dist directory to exist.
|
|
26
|
+
await fs.mkdir(distPath, { recursive: true });
|
|
27
|
+
try {
|
|
28
|
+
const { manifest } = await bundle(sourcePaths, distPath, declarations, {
|
|
29
|
+
basePath: buildDir,
|
|
30
|
+
cacheDirectory,
|
|
31
|
+
debug,
|
|
32
|
+
distImportMapPath,
|
|
33
|
+
featureFlags,
|
|
34
|
+
importMaps: [importMap].filter(Boolean),
|
|
35
|
+
systemLogger: featureFlags.edge_functions_system_logger ? systemLog : undefined,
|
|
36
|
+
});
|
|
37
|
+
systemLog('Edge Functions manifest:', manifest);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
tagBundlingError(error);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
await validateEdgeFunctionsManifest({ buildDir, constants: { EDGE_FUNCTIONS_DIST: distDirectory } });
|
|
44
|
+
return {};
|
|
45
|
+
};
|
|
46
|
+
// We run this core step if at least one of the functions directories (the
|
|
47
|
+
// one configured by the user or the internal one) exists. We use a dynamic
|
|
48
|
+
// `condition` because the directories might be created by the build command
|
|
49
|
+
// or plugins.
|
|
50
|
+
const hasEdgeFunctionsDirectories = async function ({ buildDir, constants: { INTERNAL_EDGE_FUNCTIONS_SRC, EDGE_FUNCTIONS_SRC }, }) {
|
|
51
|
+
const hasFunctionsSrc = EDGE_FUNCTIONS_SRC !== undefined && EDGE_FUNCTIONS_SRC !== '';
|
|
52
|
+
if (hasFunctionsSrc) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
const internalFunctionsSrc = resolve(buildDir, INTERNAL_EDGE_FUNCTIONS_SRC);
|
|
56
|
+
return await pathExists(internalFunctionsSrc);
|
|
57
|
+
};
|
|
58
|
+
const logFunctions = async ({ internalSrcDirectory, internalSrcPath, logs, srcDirectory: userFunctionsSrc, srcPath, }) => {
|
|
59
|
+
const [userFunctions, internalFunctions] = await Promise.all([find([srcPath]), find([internalSrcPath])]);
|
|
60
|
+
const userFunctionsSrcExists = await pathExists(srcPath);
|
|
61
|
+
const internalFunctionsSrc = internalSrcDirectory;
|
|
62
|
+
logFunctionsToBundle({
|
|
63
|
+
logs,
|
|
64
|
+
userFunctions: userFunctions.map(({ name }) => name),
|
|
65
|
+
userFunctionsSrc,
|
|
66
|
+
userFunctionsSrcExists,
|
|
67
|
+
internalFunctions: internalFunctions.map(({ name }) => name),
|
|
68
|
+
internalFunctionsSrc,
|
|
69
|
+
type: 'Edge Functions',
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
export const bundleEdgeFunctions = {
|
|
73
|
+
event: 'onBuild',
|
|
74
|
+
coreStep,
|
|
75
|
+
coreStepId: 'edge_functions_bundling',
|
|
76
|
+
coreStepName: 'Edge Functions bundling',
|
|
77
|
+
coreStepDescription: () => 'Edge Functions bundling',
|
|
78
|
+
condition: hasEdgeFunctionsDirectories,
|
|
79
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CUSTOM_ERROR_KEY, getErrorInfo, isBuildError } from '../../../error/info.js';
|
|
2
|
+
// If we have a custom error tagged with `functionsBundling` (which happens if
|
|
3
|
+
// there is an issue with user code), we tag it as coming from an edge function
|
|
4
|
+
// so that we can adjust the downstream error messages accordingly.
|
|
5
|
+
export const tagBundlingError = (error) => {
|
|
6
|
+
if (!isBuildError(error)) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const [errorInfo = {}] = getErrorInfo(error);
|
|
10
|
+
if (errorInfo.type !== 'functionsBundling') {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
error[CUSTOM_ERROR_KEY].location = {
|
|
14
|
+
...error[CUSTOM_ERROR_KEY].location,
|
|
15
|
+
functionType: 'edge',
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { dirname, join, resolve } from 'path';
|
|
3
|
+
import { pathToFileURL } from 'url';
|
|
4
|
+
const parseManifest = async (internalSourceDirectory, systemLog) => {
|
|
5
|
+
const manifestPath = join(internalSourceDirectory, 'manifest.json');
|
|
6
|
+
try {
|
|
7
|
+
const data = await fs.readFile(manifestPath);
|
|
8
|
+
const manifest = JSON.parse(data);
|
|
9
|
+
if (manifest.version !== 1) {
|
|
10
|
+
throw new Error('Unsupported manifest version');
|
|
11
|
+
}
|
|
12
|
+
const result = {
|
|
13
|
+
declarations: manifest.functions,
|
|
14
|
+
};
|
|
15
|
+
if (manifest.import_map) {
|
|
16
|
+
const importMapPath = resolve(dirname(manifestPath), manifest.import_map);
|
|
17
|
+
const importMap = await readImportMap(importMapPath);
|
|
18
|
+
return {
|
|
19
|
+
...result,
|
|
20
|
+
importMap: {
|
|
21
|
+
baseURL: pathToFileURL(importMapPath),
|
|
22
|
+
...importMap,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.code !== 'ENOENT') {
|
|
30
|
+
systemLog('Error while parsing internal edge functions manifest:', error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
declarations: [],
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
const readImportMap = async (path) => {
|
|
38
|
+
try {
|
|
39
|
+
const data = await fs.readFile(path);
|
|
40
|
+
const importMap = JSON.parse(data);
|
|
41
|
+
return importMap;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// no-op
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
imports: {},
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export { parseManifest };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join, resolve } from 'path';
|
|
3
|
+
import Ajv from 'ajv';
|
|
4
|
+
import ajvErrors from 'ajv-errors';
|
|
5
|
+
import { addErrorInfo } from '../../../error/info.js';
|
|
6
|
+
const ajv = new Ajv({ allErrors: true });
|
|
7
|
+
ajvErrors(ajv);
|
|
8
|
+
// regex pattern for manifest route pattern
|
|
9
|
+
// checks if the pattern string starts with ^ and ends with $
|
|
10
|
+
// we define this format in edge-bundler:
|
|
11
|
+
// https://github.com/netlify/edge-bundler/blob/main/src/manifest.ts#L66
|
|
12
|
+
const normalizedPatternRegex = /^\^.*\$$/;
|
|
13
|
+
ajv.addFormat('regexPattern', {
|
|
14
|
+
async: true,
|
|
15
|
+
validate: (data) => normalizedPatternRegex.test(data),
|
|
16
|
+
});
|
|
17
|
+
const bundlesSchema = {
|
|
18
|
+
$async: true,
|
|
19
|
+
type: 'object',
|
|
20
|
+
required: ['asset', 'format'],
|
|
21
|
+
properties: {
|
|
22
|
+
asset: { type: 'string' },
|
|
23
|
+
format: { type: 'string' },
|
|
24
|
+
},
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
};
|
|
27
|
+
const routesSchema = {
|
|
28
|
+
$async: true,
|
|
29
|
+
type: 'object',
|
|
30
|
+
required: ['function', 'pattern'],
|
|
31
|
+
properties: {
|
|
32
|
+
name: { type: 'string' },
|
|
33
|
+
function: { type: 'string' },
|
|
34
|
+
pattern: { type: 'string', format: 'regexPattern', errorMessage: `must match format ${normalizedPatternRegex}` },
|
|
35
|
+
},
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
};
|
|
38
|
+
const edgeManifestSchema = {
|
|
39
|
+
$async: true,
|
|
40
|
+
type: 'object',
|
|
41
|
+
required: ['bundles', 'routes', 'bundler_version'],
|
|
42
|
+
properties: {
|
|
43
|
+
bundles: {
|
|
44
|
+
type: 'array',
|
|
45
|
+
items: bundlesSchema,
|
|
46
|
+
},
|
|
47
|
+
routes: {
|
|
48
|
+
type: 'array',
|
|
49
|
+
items: routesSchema,
|
|
50
|
+
},
|
|
51
|
+
bundler_version: { type: 'string' },
|
|
52
|
+
},
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
errorMessage: "Couldn't validate Edge Functions manifest.json",
|
|
55
|
+
};
|
|
56
|
+
const validateManifest = async (manifestData) => {
|
|
57
|
+
const validate = ajv.compile(edgeManifestSchema);
|
|
58
|
+
await validate(manifestData);
|
|
59
|
+
};
|
|
60
|
+
export const validateEdgeFunctionsManifest = async function ({ buildDir, constants: { EDGE_FUNCTIONS_DIST: distDirectory }, }) {
|
|
61
|
+
try {
|
|
62
|
+
const edgeFunctionsDistPath = resolve(buildDir, distDirectory);
|
|
63
|
+
const manifestPath = join(edgeFunctionsDistPath, 'manifest.json');
|
|
64
|
+
const data = await fs.readFile(manifestPath);
|
|
65
|
+
const manifestData = JSON.parse(data);
|
|
66
|
+
await validateManifest(manifestData);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const isValidationErr = error instanceof Ajv.ValidationError;
|
|
70
|
+
const parsedErr = isValidationErr ? error.errors : error;
|
|
71
|
+
addErrorInfo(parsedErr, { type: 'coreStep' });
|
|
72
|
+
throw new Error(isValidationErr ? JSON.stringify(parsedErr, null, 2) : parsedErr);
|
|
73
|
+
}
|
|
74
|
+
return {};
|
|
75
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import readdirp from 'readdirp';
|
|
2
|
+
import { addErrorInfo } from '../../error/info.js';
|
|
3
|
+
const MODULE_NOT_FOUND_CODE = 'MODULE_NOT_FOUND';
|
|
4
|
+
const MODULE_NOT_FOUND_ESBUILD_REGEXP = /^Could not resolve ['"]([^'"]+)/;
|
|
5
|
+
const MODULE_NOT_FOUND_REGEXP = /Cannot find module ['"]([^'"]+)/;
|
|
6
|
+
// Handle errors coming from zip-it-and-ship-it
|
|
7
|
+
export const getZipError = async function (error, functionsSrc) {
|
|
8
|
+
const moduleNotFoundError = await getModuleNotFoundError(error, functionsSrc);
|
|
9
|
+
if (moduleNotFoundError) {
|
|
10
|
+
return moduleNotFoundError;
|
|
11
|
+
}
|
|
12
|
+
if (isPackageJsonError(error)) {
|
|
13
|
+
return getPackageJsonError(error);
|
|
14
|
+
}
|
|
15
|
+
return error;
|
|
16
|
+
};
|
|
17
|
+
const getModuleNotFoundError = async function (error, functionsSrc) {
|
|
18
|
+
const errorFromZisi = await getModuleNotFoundErrorFromZISI(error, functionsSrc);
|
|
19
|
+
if (errorFromZisi) {
|
|
20
|
+
return errorFromZisi;
|
|
21
|
+
}
|
|
22
|
+
const errorFromEsbuild = await getModuleNotFoundErrorFromEsbuild(error, functionsSrc);
|
|
23
|
+
if (errorFromEsbuild) {
|
|
24
|
+
return errorFromEsbuild;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const getModuleNotFoundErrorObject = async ({ error, functionsSrc, moduleNames }) => {
|
|
28
|
+
const message = await getModuleNotFoundMessage(functionsSrc, moduleNames);
|
|
29
|
+
error.message = `${message}\n\n${error.message}`;
|
|
30
|
+
addErrorInfo(error, { type: 'dependencies' });
|
|
31
|
+
return error;
|
|
32
|
+
};
|
|
33
|
+
const getModuleNotFoundMessage = async function (functionsSrc, moduleNames) {
|
|
34
|
+
if (moduleNames.length === 0 || !(await lacksNodeModules(functionsSrc))) {
|
|
35
|
+
return MODULE_NOT_FOUND_MESSAGE;
|
|
36
|
+
}
|
|
37
|
+
if (moduleNames.filter(Boolean).some(isLocalPath)) {
|
|
38
|
+
return PATH_NOT_FOUND_MESSAGE;
|
|
39
|
+
}
|
|
40
|
+
return getLocalInstallMessage(moduleNames);
|
|
41
|
+
};
|
|
42
|
+
const isLocalPath = function (moduleName) {
|
|
43
|
+
return moduleName.startsWith('.') || moduleName.startsWith('/');
|
|
44
|
+
};
|
|
45
|
+
const getModuleNotFoundErrorFromEsbuild = function (error, functionsSrc) {
|
|
46
|
+
const { errors = [] } = error;
|
|
47
|
+
const modulesNotFound = errors.reduce((modules, errorObject) => {
|
|
48
|
+
const match = errorObject.text.match(MODULE_NOT_FOUND_ESBUILD_REGEXP);
|
|
49
|
+
if (!match) {
|
|
50
|
+
return modules;
|
|
51
|
+
}
|
|
52
|
+
return [...modules, match[1]];
|
|
53
|
+
}, []);
|
|
54
|
+
if (modulesNotFound.length === 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
return getModuleNotFoundErrorObject({ error, functionsSrc, moduleNames: modulesNotFound });
|
|
58
|
+
};
|
|
59
|
+
const getModuleNotFoundErrorFromZISI = function (error, functionsSrc) {
|
|
60
|
+
if (!(error instanceof Error && error.code === MODULE_NOT_FOUND_CODE)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const moduleName = getModuleNameFromZISIError(error);
|
|
64
|
+
return getModuleNotFoundErrorObject({ error, functionsSrc, moduleNames: moduleName ? [moduleName] : [] });
|
|
65
|
+
};
|
|
66
|
+
// This error message always include the same words
|
|
67
|
+
const getModuleNameFromZISIError = function (error) {
|
|
68
|
+
if (typeof error.message !== 'string') {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const result = MODULE_NOT_FOUND_REGEXP.exec(error.message);
|
|
72
|
+
if (result === null) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
return result[1];
|
|
76
|
+
};
|
|
77
|
+
// Netlify Functions has a `package.json` but no `node_modules`
|
|
78
|
+
const lacksNodeModules = async function (functionsSrc) {
|
|
79
|
+
return (functionsSrc !== undefined &&
|
|
80
|
+
(await hasFunctionRootFile('package.json', functionsSrc)) &&
|
|
81
|
+
!(await hasFunctionRootFile('node_modules', functionsSrc)));
|
|
82
|
+
};
|
|
83
|
+
// Functions can be either files or directories, so we need to check on two
|
|
84
|
+
// depth levels
|
|
85
|
+
const hasFunctionRootFile = async function (filename, functionsSrc) {
|
|
86
|
+
const files = await readdirp.promise(functionsSrc, { depth: 1, fileFilter: filename });
|
|
87
|
+
return files.length !== 0;
|
|
88
|
+
};
|
|
89
|
+
const MODULE_NOT_FOUND_MESSAGE = `A Netlify Function failed to require one of its dependencies.
|
|
90
|
+
Please make sure it is present in the site's top-level "package.json".`;
|
|
91
|
+
const PATH_NOT_FOUND_MESSAGE = `A Netlify Function failed to require a local file.
|
|
92
|
+
Please make sure the file exists and its path is correctly spelled.`;
|
|
93
|
+
// A common mistake is to assume Netlify Functions dependencies are
|
|
94
|
+
// automatically installed. This checks for this pattern.
|
|
95
|
+
const getLocalInstallMessage = function (modules) {
|
|
96
|
+
const genericMessage = `
|
|
97
|
+
|
|
98
|
+
By default, dependencies inside a Netlify Function's "package.json" are not automatically installed.
|
|
99
|
+
There are several ways to fix this problem:
|
|
100
|
+
- Removing your Function's "package.json" and adding the dependencies to the project's top-level "package.json" instead. This is the fastest and safest solution.
|
|
101
|
+
- Running "npm install" or "yarn" inside your Netlify Function in your build command.
|
|
102
|
+
- Adding the following plugin to your "netlify.toml":
|
|
103
|
+
|
|
104
|
+
[[plugins]]
|
|
105
|
+
package = "@netlify/plugin-functions-install-core"
|
|
106
|
+
`;
|
|
107
|
+
if (modules.length === 1) {
|
|
108
|
+
return `A Netlify Function is using "${modules[0]}" but that dependency has not been installed yet.${genericMessage}`;
|
|
109
|
+
}
|
|
110
|
+
const moduleNames = modules.map((name) => `"${name}"`).join(', ');
|
|
111
|
+
return `A Netlify Function is using dependencies that have not been installed yet: ${moduleNames}${genericMessage}`;
|
|
112
|
+
};
|
|
113
|
+
// We need to load the site's `package.json` when bundling Functions. This is
|
|
114
|
+
// because `optionalDependencies` can make `import()` fail, but we don't want
|
|
115
|
+
// to error then. However, if the `package.json` is invalid, we fail the build.
|
|
116
|
+
const isPackageJsonError = function (error) {
|
|
117
|
+
return PACKAGE_JSON_ORIGINAL_MESSAGES.some((msg) => error.message.includes(msg));
|
|
118
|
+
};
|
|
119
|
+
const PACKAGE_JSON_ORIGINAL_MESSAGES = ['is invalid JSON', 'in JSON at position'];
|
|
120
|
+
const getPackageJsonError = function (error) {
|
|
121
|
+
addErrorInfo(error, { type: 'resolveConfig' });
|
|
122
|
+
return error;
|
|
123
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { resolve } from 'path';
|
|
2
|
+
import { zipFunctions } from '@netlify/zip-it-and-ship-it';
|
|
3
|
+
import { pathExists } from 'path-exists';
|
|
4
|
+
import { log } from '../../log/logger.js';
|
|
5
|
+
import { logBundleResults, logFunctionsNonExistingDir, logFunctionsToBundle } from '../../log/messages/core_steps.js';
|
|
6
|
+
import { getZipError } from './error.js';
|
|
7
|
+
import { getUserAndInternalFunctions, validateFunctionsSrc } from './utils.js';
|
|
8
|
+
import { getZisiParameters } from './zisi.js';
|
|
9
|
+
// Returns `true` if at least one of the functions has been configured to use
|
|
10
|
+
// esbuild.
|
|
11
|
+
const isUsingEsbuild = (functionsConfig = {}) => Object.values(functionsConfig).some((configObject) => configObject.node_bundler === 'esbuild');
|
|
12
|
+
const zipFunctionsAndLogResults = async ({ buildDir, childEnv, featureFlags, functionsConfig, functionsDist, functionsSrc, internalFunctionsSrc, isRunningLocally, logs, repositoryRoot, }) => {
|
|
13
|
+
const zisiParameters = getZisiParameters({
|
|
14
|
+
buildDir,
|
|
15
|
+
childEnv,
|
|
16
|
+
featureFlags,
|
|
17
|
+
functionsConfig,
|
|
18
|
+
functionsDist,
|
|
19
|
+
internalFunctionsSrc,
|
|
20
|
+
isRunningLocally,
|
|
21
|
+
repositoryRoot,
|
|
22
|
+
});
|
|
23
|
+
const bundler = isUsingEsbuild(functionsConfig) ? 'esbuild' : 'zisi';
|
|
24
|
+
try {
|
|
25
|
+
// Printing an empty line before bundling output.
|
|
26
|
+
log(logs, '');
|
|
27
|
+
const sourceDirectories = [internalFunctionsSrc, functionsSrc].filter(Boolean);
|
|
28
|
+
const results = await zipItAndShipIt.zipFunctions(sourceDirectories, functionsDist, zisiParameters);
|
|
29
|
+
logBundleResults({ logs, results });
|
|
30
|
+
return { bundler };
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw await getZipError(error, functionsSrc);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
// Plugin to package Netlify functions with @netlify/zip-it-and-ship-it
|
|
37
|
+
const coreStep = async function ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC: relativeInternalFunctionsSrc, IS_LOCAL: isRunningLocally, FUNCTIONS_SRC: relativeFunctionsSrc, FUNCTIONS_DIST: relativeFunctionsDist, }, buildDir, logs, netlifyConfig, featureFlags, repositoryRoot, }) {
|
|
38
|
+
const functionsSrc = relativeFunctionsSrc === undefined ? undefined : resolve(buildDir, relativeFunctionsSrc);
|
|
39
|
+
const functionsDist = resolve(buildDir, relativeFunctionsDist);
|
|
40
|
+
const internalFunctionsSrc = resolve(buildDir, relativeInternalFunctionsSrc);
|
|
41
|
+
const internalFunctionsSrcExists = await pathExists(internalFunctionsSrc);
|
|
42
|
+
const functionsSrcExists = await validateFunctionsSrc({ functionsSrc, logs, relativeFunctionsSrc });
|
|
43
|
+
const [userFunctions = [], internalFunctions = []] = await getUserAndInternalFunctions({
|
|
44
|
+
featureFlags,
|
|
45
|
+
functionsSrc,
|
|
46
|
+
functionsSrcExists,
|
|
47
|
+
internalFunctionsSrc,
|
|
48
|
+
internalFunctionsSrcExists,
|
|
49
|
+
});
|
|
50
|
+
if (functionsSrc && !functionsSrcExists) {
|
|
51
|
+
logFunctionsNonExistingDir(logs, relativeFunctionsSrc);
|
|
52
|
+
if (internalFunctions.length !== 0) {
|
|
53
|
+
log(logs, '');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
logFunctionsToBundle({
|
|
57
|
+
logs,
|
|
58
|
+
userFunctions,
|
|
59
|
+
userFunctionsSrc: relativeFunctionsSrc,
|
|
60
|
+
userFunctionsSrcExists: functionsSrcExists,
|
|
61
|
+
internalFunctions,
|
|
62
|
+
internalFunctionsSrc: relativeInternalFunctionsSrc,
|
|
63
|
+
});
|
|
64
|
+
if (userFunctions.length === 0 && internalFunctions.length === 0) {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
const { bundler } = await zipFunctionsAndLogResults({
|
|
68
|
+
buildDir,
|
|
69
|
+
childEnv,
|
|
70
|
+
featureFlags,
|
|
71
|
+
functionsConfig: netlifyConfig.functions,
|
|
72
|
+
functionsDist,
|
|
73
|
+
functionsSrc,
|
|
74
|
+
internalFunctionsSrc,
|
|
75
|
+
isRunningLocally,
|
|
76
|
+
logs,
|
|
77
|
+
repositoryRoot,
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
tags: {
|
|
81
|
+
bundler,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
// We run this core step if at least one of the functions directories (the
|
|
86
|
+
// one configured by the user or the internal one) exists. We use a dynamic
|
|
87
|
+
// `condition` because the directories might be created by the build command
|
|
88
|
+
// or plugins.
|
|
89
|
+
const hasFunctionsDirectories = async function ({ buildDir, constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC } }) {
|
|
90
|
+
const hasFunctionsSrc = FUNCTIONS_SRC !== undefined && FUNCTIONS_SRC !== '';
|
|
91
|
+
if (hasFunctionsSrc) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
const internalFunctionsSrc = resolve(buildDir, INTERNAL_FUNCTIONS_SRC);
|
|
95
|
+
return await pathExists(internalFunctionsSrc);
|
|
96
|
+
};
|
|
97
|
+
export const bundleFunctions = {
|
|
98
|
+
event: 'onBuild',
|
|
99
|
+
coreStep,
|
|
100
|
+
coreStepId: 'functions_bundling',
|
|
101
|
+
coreStepName: 'Functions bundling',
|
|
102
|
+
coreStepDescription: () => 'Functions bundling',
|
|
103
|
+
condition: hasFunctionsDirectories,
|
|
104
|
+
};
|
|
105
|
+
// Named imports with ES modules cannot be mocked (unlike CommonJS) because
|
|
106
|
+
// they are bound at load time.
|
|
107
|
+
// However, some of our tests are asserting which arguments are passed to
|
|
108
|
+
// `zip-it-and-ship-it` methods. Therefore, we need to use an intermediary
|
|
109
|
+
// function and export them so tests can use it.
|
|
110
|
+
export const zipItAndShipIt = {
|
|
111
|
+
async zipFunctions(...args) {
|
|
112
|
+
return await zipFunctions(...args);
|
|
113
|
+
},
|
|
114
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { relative } from 'path';
|
|
3
|
+
import { listFunctions } from '@netlify/zip-it-and-ship-it';
|
|
4
|
+
import { addErrorInfo } from '../../error/info.js';
|
|
5
|
+
import { getZisiFeatureFlags } from './feature_flags.js';
|
|
6
|
+
// Returns the `mainFile` of each function found in `functionsSrc`, relative to
|
|
7
|
+
// `functionsSrc`.
|
|
8
|
+
const getRelativeFunctionMainFiles = async function ({ featureFlags, functionsSrc }) {
|
|
9
|
+
if (functionsSrc === undefined) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
const zisiFeatureFlags = getZisiFeatureFlags(featureFlags);
|
|
13
|
+
const functions = await listFunctions(functionsSrc, { featureFlags: zisiFeatureFlags });
|
|
14
|
+
return functions.map(({ mainFile }) => relative(functionsSrc, mainFile));
|
|
15
|
+
};
|
|
16
|
+
export const getUserAndInternalFunctions = ({ featureFlags, functionsSrc, functionsSrcExists, internalFunctionsSrc, internalFunctionsSrcExists, }) => {
|
|
17
|
+
const paths = [
|
|
18
|
+
functionsSrcExists ? functionsSrc : undefined,
|
|
19
|
+
internalFunctionsSrcExists ? internalFunctionsSrc : undefined,
|
|
20
|
+
];
|
|
21
|
+
return Promise.all(paths.map((path) => path && getRelativeFunctionMainFiles({ featureFlags, functionsSrc: path })));
|
|
22
|
+
};
|
|
23
|
+
// Returns `true` if the functions directory exists and is valid. Returns
|
|
24
|
+
// `false` if it doesn't exist. Throws an error if it's invalid or can't
|
|
25
|
+
// be accessed.
|
|
26
|
+
export const validateFunctionsSrc = async function ({ functionsSrc, relativeFunctionsSrc }) {
|
|
27
|
+
if (functionsSrc === undefined) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const stats = await fs.stat(functionsSrc);
|
|
32
|
+
if (stats.isDirectory()) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
const error = new Error(`The Netlify Functions setting should target a directory, not a regular file: ${relativeFunctionsSrc}`);
|
|
36
|
+
addErrorInfo(error, { type: 'resolveConfig' });
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code === 'ENOENT') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
};
|