@netlify/build 27.18.6-rc → 27.18.6

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.
Files changed (135) hide show
  1. package/lib/core/bin.js +83 -0
  2. package/lib/core/build.js +559 -0
  3. package/lib/core/config.js +186 -0
  4. package/lib/core/constants.js +156 -0
  5. package/lib/core/dev.js +31 -0
  6. package/lib/core/dry.js +39 -0
  7. package/lib/core/feature_flags.js +22 -0
  8. package/lib/core/flags.js +204 -0
  9. package/lib/core/lingering.js +85 -0
  10. package/lib/core/main.js +165 -0
  11. package/lib/core/missing_side_file.js +29 -0
  12. package/lib/core/normalize_flags.js +70 -0
  13. package/lib/core/severity.js +22 -0
  14. package/lib/core/user_node_version.js +41 -0
  15. package/lib/env/changes.js +52 -0
  16. package/lib/env/main.js +19 -0
  17. package/lib/env/metadata.js +81 -0
  18. package/lib/error/api.js +46 -0
  19. package/lib/error/build.js +50 -0
  20. package/lib/error/cancel.js +8 -0
  21. package/lib/error/colors.js +11 -0
  22. package/lib/error/handle.js +57 -0
  23. package/lib/error/info.js +46 -0
  24. package/lib/error/monitor/location.js +21 -0
  25. package/lib/error/monitor/normalize.js +96 -0
  26. package/lib/error/monitor/print.js +42 -0
  27. package/lib/error/monitor/report.js +138 -0
  28. package/lib/error/monitor/start.js +69 -0
  29. package/lib/error/parse/clean_stack.js +87 -0
  30. package/lib/error/parse/location.js +62 -0
  31. package/lib/error/parse/normalize.js +29 -0
  32. package/lib/error/parse/parse.js +97 -0
  33. package/lib/error/parse/plugin.js +70 -0
  34. package/lib/error/parse/properties.js +23 -0
  35. package/lib/error/parse/serialize_log.js +42 -0
  36. package/lib/error/parse/serialize_status.js +23 -0
  37. package/lib/error/parse/stack.js +43 -0
  38. package/lib/error/type.js +189 -0
  39. package/lib/install/functions.js +28 -0
  40. package/lib/install/local.js +62 -0
  41. package/lib/install/main.js +81 -0
  42. package/lib/install/missing.js +67 -0
  43. package/lib/log/colors.js +34 -0
  44. package/lib/log/description.js +26 -0
  45. package/lib/log/header.js +16 -0
  46. package/lib/log/header_func.js +17 -0
  47. package/lib/log/logger.js +161 -0
  48. package/lib/log/messages/compatibility.js +178 -0
  49. package/lib/log/messages/config.js +107 -0
  50. package/lib/log/messages/core.js +70 -0
  51. package/lib/log/messages/core_steps.js +104 -0
  52. package/lib/log/messages/dry.js +63 -0
  53. package/lib/log/messages/install.js +32 -0
  54. package/lib/log/messages/ipc.js +38 -0
  55. package/lib/log/messages/mutations.js +82 -0
  56. package/lib/log/messages/plugins.js +39 -0
  57. package/lib/log/messages/status.js +16 -0
  58. package/lib/log/messages/steps.js +22 -0
  59. package/lib/log/old_version.js +41 -0
  60. package/lib/log/serialize.js +13 -0
  61. package/lib/log/stream.js +85 -0
  62. package/lib/log/theme.js +26 -0
  63. package/lib/plugins/child/diff.js +55 -0
  64. package/lib/plugins/child/error.js +32 -0
  65. package/lib/plugins/child/lazy.js +18 -0
  66. package/lib/plugins/child/load.js +29 -0
  67. package/lib/plugins/child/logic.js +65 -0
  68. package/lib/plugins/child/main.js +51 -0
  69. package/lib/plugins/child/run.js +28 -0
  70. package/lib/plugins/child/status.js +74 -0
  71. package/lib/plugins/child/typescript.js +45 -0
  72. package/lib/plugins/child/utils.js +56 -0
  73. package/lib/plugins/child/validate.js +34 -0
  74. package/lib/plugins/compatibility.js +132 -0
  75. package/lib/plugins/error.js +50 -0
  76. package/lib/plugins/events.js +17 -0
  77. package/lib/plugins/expected_version.js +119 -0
  78. package/lib/plugins/ipc.js +145 -0
  79. package/lib/plugins/list.js +86 -0
  80. package/lib/plugins/load.js +70 -0
  81. package/lib/plugins/manifest/check.js +106 -0
  82. package/lib/plugins/manifest/load.js +41 -0
  83. package/lib/plugins/manifest/main.js +22 -0
  84. package/lib/plugins/manifest/path.js +31 -0
  85. package/lib/plugins/manifest/validate.js +108 -0
  86. package/lib/plugins/node_version.js +50 -0
  87. package/lib/plugins/options.js +88 -0
  88. package/lib/plugins/pinned_version.js +131 -0
  89. package/lib/plugins/resolve.js +152 -0
  90. package/lib/plugins/spawn.js +72 -0
  91. package/lib/plugins_core/add.js +49 -0
  92. package/lib/plugins_core/build_command.js +75 -0
  93. package/lib/plugins_core/deploy/buildbot_client.js +113 -0
  94. package/lib/plugins_core/deploy/index.js +73 -0
  95. package/lib/plugins_core/deploy/manifest.yml +1 -0
  96. package/lib/plugins_core/edge_functions/index.js +123 -0
  97. package/lib/plugins_core/edge_functions/lib/error.js +21 -0
  98. package/lib/plugins_core/edge_functions/lib/internal_manifest.js +60 -0
  99. package/lib/plugins_core/edge_functions/validate_manifest/validate_edge_functions_manifest.js +89 -0
  100. package/lib/plugins_core/functions/error.js +163 -0
  101. package/lib/plugins_core/functions/feature_flags.js +6 -0
  102. package/lib/plugins_core/functions/index.js +161 -0
  103. package/lib/plugins_core/functions/utils.js +66 -0
  104. package/lib/plugins_core/functions/zisi.js +56 -0
  105. package/lib/plugins_core/functions_install/index.js +13 -0
  106. package/lib/plugins_core/functions_install/manifest.yml +1 -0
  107. package/lib/plugins_core/list.js +27 -0
  108. package/lib/status/add.js +36 -0
  109. package/lib/status/colors.js +23 -0
  110. package/lib/status/load_error.js +11 -0
  111. package/lib/status/report.js +137 -0
  112. package/lib/status/success.js +18 -0
  113. package/lib/steps/core_step.js +92 -0
  114. package/lib/steps/error.js +102 -0
  115. package/lib/steps/get.js +51 -0
  116. package/lib/steps/plugin.js +85 -0
  117. package/lib/steps/return.js +52 -0
  118. package/lib/steps/run_core_steps.js +200 -0
  119. package/lib/steps/run_step.js +304 -0
  120. package/lib/steps/run_steps.js +179 -0
  121. package/lib/steps/update_config.js +93 -0
  122. package/lib/telemetry/main.js +136 -0
  123. package/lib/time/aggregate.js +146 -0
  124. package/lib/time/main.js +48 -0
  125. package/lib/time/measure.js +22 -0
  126. package/lib/time/report.js +59 -0
  127. package/lib/utils/errors.js +12 -0
  128. package/lib/utils/json.js +19 -0
  129. package/lib/utils/omit.js +6 -0
  130. package/lib/utils/package.js +23 -0
  131. package/lib/utils/remove_falsy.js +10 -0
  132. package/lib/utils/resolve.js +46 -0
  133. package/lib/utils/runtime.js +5 -0
  134. package/lib/utils/semver.js +34 -0
  135. package/package.json +8 -7
@@ -0,0 +1,75 @@
1
+ import { platform } from 'process'
2
+
3
+ import { execa } from 'execa'
4
+
5
+ import { addErrorInfo } from '../error/info.js'
6
+ import { getBuildCommandDescription } from '../log/description.js'
7
+ import { logBuildCommandStart } from '../log/messages/steps.js'
8
+ import { getBuildCommandStdio, handleBuildCommandOutput } from '../log/stream.js'
9
+
10
+ // Fire `build.command`
11
+ const coreStep = async function ({
12
+ configPath,
13
+ buildDir,
14
+ nodePath,
15
+ childEnv,
16
+ logs,
17
+ netlifyConfig: {
18
+ build: { command: buildCommand, commandOrigin: buildCommandOrigin },
19
+ },
20
+ }) {
21
+ logBuildCommandStart(logs, buildCommand)
22
+
23
+ const stdio = getBuildCommandStdio(logs)
24
+ const childProcess = execa(buildCommand, {
25
+ shell: SHELL,
26
+ cwd: buildDir,
27
+ preferLocal: true,
28
+ execPath: nodePath,
29
+ env: childEnv,
30
+ extendEnv: false,
31
+ stdio,
32
+ })
33
+
34
+ try {
35
+ const buildCommandOutput = await childProcess
36
+ handleBuildCommandOutput(buildCommandOutput, logs)
37
+ return {}
38
+ } catch (error) {
39
+ // In our test environment we use `stdio: 'pipe'` on the build command, meaning our `stdout/stderr` output are
40
+ // buffered and consequently added to `error.message`. To avoid this and end up with duplicated output in our
41
+ // logs/snapshots we need to rely on `error.shortMessage`.
42
+ error.message = error.shortMessage
43
+ handleBuildCommandOutput(error, logs)
44
+ addErrorInfo(error, { type: 'buildCommand', location: { buildCommand, buildCommandOrigin, configPath } })
45
+ throw error
46
+ }
47
+ }
48
+
49
+ // We use Bash on Unix and `cmd.exe` on Windows
50
+ const SHELL = platform === 'win32' ? true : 'bash'
51
+
52
+ const coreStepDescription = function ({
53
+ netlifyConfig: {
54
+ build: { commandOrigin: buildCommandOrigin },
55
+ },
56
+ }) {
57
+ return getBuildCommandDescription(buildCommandOrigin)
58
+ }
59
+
60
+ const hasBuildCommand = function ({
61
+ netlifyConfig: {
62
+ build: { command: buildCommand },
63
+ },
64
+ }) {
65
+ return buildCommand !== undefined && buildCommand !== ''
66
+ }
67
+
68
+ export const buildCommandCore = {
69
+ event: 'onBuild',
70
+ coreStep,
71
+ coreStepId: 'build_command',
72
+ coreStepName: 'build.command',
73
+ coreStepDescription,
74
+ condition: hasBuildCommand,
75
+ }
@@ -0,0 +1,113 @@
1
+ import net from 'net'
2
+ import { normalize, resolve, relative } from 'path'
3
+ import { promisify } from 'util'
4
+
5
+ import { pEvent } from 'p-event'
6
+
7
+ import { addErrorInfo } from '../../error/info.js'
8
+ import { runsAfterDeploy } from '../../plugins/events.js'
9
+ import { addAsyncErrorMessage } from '../../utils/errors.js'
10
+
11
+ export const createBuildbotClient = function (buildbotServerSocket) {
12
+ const connectionOpts = getConnectionOpts(buildbotServerSocket)
13
+ const client = net.createConnection(connectionOpts)
14
+ return client
15
+ }
16
+
17
+ // Windows does not support Unix sockets well, so we also support `host:port`
18
+ const getConnectionOpts = function (buildbotServerSocket) {
19
+ if (!buildbotServerSocket.includes(':')) {
20
+ return { path: buildbotServerSocket }
21
+ }
22
+
23
+ const [host, port] = buildbotServerSocket.split(':')
24
+ return { host, port }
25
+ }
26
+
27
+ const eConnectBuildbotClient = async function (client) {
28
+ await pEvent(client, 'connect')
29
+ }
30
+
31
+ export const connectBuildbotClient = addAsyncErrorMessage(eConnectBuildbotClient, 'Could not connect to buildbot')
32
+
33
+ export const closeBuildbotClient = async function (client) {
34
+ if (client.destroyed) {
35
+ return
36
+ }
37
+
38
+ await promisify(client.end.bind(client))()
39
+ }
40
+
41
+ const cWritePayload = async function (buildbotClient, payload) {
42
+ await promisify(buildbotClient.write.bind(buildbotClient))(JSON.stringify(payload))
43
+ }
44
+
45
+ const writePayload = addAsyncErrorMessage(cWritePayload, 'Could not send payload to buildbot')
46
+
47
+ const cGetNextParsedResponsePromise = async function (buildbotClient) {
48
+ const data = await pEvent(buildbotClient, 'data')
49
+ return JSON.parse(data)
50
+ }
51
+
52
+ const getNextParsedResponsePromise = addAsyncErrorMessage(
53
+ cGetNextParsedResponsePromise,
54
+ 'Invalid response from buildbot',
55
+ )
56
+
57
+ export const deploySiteWithBuildbotClient = async function ({ client, events, buildDir, repositoryRoot, constants }) {
58
+ const action = shouldWaitForPostProcessing(events) ? 'deploySiteAndAwaitLive' : 'deploySite'
59
+ const deployDir = getDeployDir({ buildDir, repositoryRoot, constants })
60
+ const payload = { action, deployDir }
61
+
62
+ const [{ succeeded, values: { error, error_type: errorType } = {} }] = await Promise.all([
63
+ getNextParsedResponsePromise(client),
64
+ writePayload(client, payload),
65
+ ])
66
+
67
+ if (!succeeded) {
68
+ return handleDeployError(error, errorType)
69
+ }
70
+ }
71
+
72
+ // The file paths in the buildbot are relative to the repository root.
73
+ // However, the file paths in Build plugins, including `constants.PUBLISH_DIR`
74
+ // are relative to the build directory, which is different when there is a
75
+ // base directory. This converts it.
76
+ // We need to call `normalize()` in case the publish directory is the
77
+ // repository root, so `deployDir` is "." not ""
78
+ const getDeployDir = function ({ buildDir, repositoryRoot, constants: { PUBLISH_DIR } }) {
79
+ const absolutePublishDir = resolve(buildDir, PUBLISH_DIR)
80
+ const relativePublishDir = relative(repositoryRoot, absolutePublishDir)
81
+ const deployDir = normalize(relativePublishDir)
82
+ return deployDir
83
+ }
84
+
85
+ // We distinguish between user errors and system errors during deploys
86
+ const handleDeployError = function (error, errorType) {
87
+ const errorIs422 = error !== undefined && error.code === '422'
88
+
89
+ const errMsg = errorIs422
90
+ ? `
91
+ File upload failed because of mismatched SHAs.
92
+ This can happen when files are changed after the deployment process has started but before they are uploaded.
93
+
94
+ Error: ${error}
95
+ `
96
+ : `Deploy did not succeed: ${error}`
97
+
98
+ const errorA = new Error(errMsg)
99
+ const errorInfo =
100
+ errorType === 'user' ? { type: 'resolveConfig' } : { type: 'coreStep', location: { coreStepName: 'Deploy site' } }
101
+ addErrorInfo(errorA, errorInfo)
102
+ throw errorA
103
+ }
104
+
105
+ // We only wait for post-processing (last stage before site deploy) if the build
106
+ // has some plugins that do post-deploy logic
107
+ const shouldWaitForPostProcessing = function (events) {
108
+ return events.some(hasPostDeployLogic)
109
+ }
110
+
111
+ const hasPostDeployLogic = function (event) {
112
+ return runsAfterDeploy(event)
113
+ }
@@ -0,0 +1,73 @@
1
+ import { saveUpdatedConfig, restoreUpdatedConfig } from '../../core/config.js'
2
+ import { logDeploySuccess } from '../../log/messages/plugins.js'
3
+
4
+ import {
5
+ createBuildbotClient,
6
+ connectBuildbotClient,
7
+ closeBuildbotClient,
8
+ deploySiteWithBuildbotClient,
9
+ } from './buildbot_client.js'
10
+
11
+ const coreStep = async function ({
12
+ buildDir,
13
+ configPath,
14
+ repositoryRoot,
15
+ constants,
16
+ buildbotServerSocket,
17
+ events,
18
+ logs,
19
+ featureFlags,
20
+ context,
21
+ branch,
22
+ configMutations,
23
+ headersPath,
24
+ redirectsPath,
25
+ debug,
26
+ saveConfig,
27
+ }) {
28
+ const client = createBuildbotClient(buildbotServerSocket)
29
+ try {
30
+ await connectBuildbotClient(client)
31
+ await saveUpdatedConfig({
32
+ configMutations,
33
+ buildDir,
34
+ repositoryRoot,
35
+ configPath,
36
+ headersPath,
37
+ redirectsPath,
38
+ logs,
39
+ featureFlags,
40
+ context,
41
+ branch,
42
+ debug,
43
+ saveConfig,
44
+ })
45
+ await deploySiteWithBuildbotClient({ client, events, buildDir, repositoryRoot, constants })
46
+ await restoreUpdatedConfig({
47
+ configMutations,
48
+ buildDir,
49
+ repositoryRoot,
50
+ configPath,
51
+ headersPath,
52
+ redirectsPath,
53
+ saveConfig,
54
+ })
55
+ logDeploySuccess(logs)
56
+ return {}
57
+ } finally {
58
+ await closeBuildbotClient(client)
59
+ }
60
+ }
61
+
62
+ const shouldDeploy = function ({ buildbotServerSocket }) {
63
+ return buildbotServerSocket !== undefined
64
+ }
65
+
66
+ export const deploySite = {
67
+ event: 'onPostBuild',
68
+ coreStep,
69
+ coreStepId: 'deploy_site',
70
+ coreStepName: 'Deploy site',
71
+ coreStepDescription: () => 'Deploy site',
72
+ condition: shouldDeploy,
73
+ }
@@ -0,0 +1 @@
1
+ name: '@netlify/plugin-deploy-core'
@@ -0,0 +1,123 @@
1
+ import { promises as fs } from 'fs'
2
+ import { dirname, join, resolve } from 'path'
3
+
4
+ import { bundle, find } from '@netlify/edge-bundler'
5
+ import { pathExists } from 'path-exists'
6
+
7
+ import { logFunctionsToBundle } from '../../log/messages/core_steps.js'
8
+
9
+ import { tagBundlingError } from './lib/error.js'
10
+ import { parseManifest } from './lib/internal_manifest.js'
11
+ import { validateEdgeFunctionsManifest } from './validate_manifest/validate_edge_functions_manifest.js'
12
+
13
+ // TODO: Replace this with a custom cache directory.
14
+ const DENO_CLI_CACHE_DIRECTORY = '.netlify/plugins/deno-cli'
15
+ const IMPORT_MAP_FILENAME = 'edge-functions-import-map.json'
16
+
17
+ // eslint-disable-next-line complexity, max-statements
18
+ const coreStep = async function ({
19
+ buildDir,
20
+ constants: {
21
+ EDGE_FUNCTIONS_DIST: distDirectory,
22
+ EDGE_FUNCTIONS_SRC: srcDirectory,
23
+ INTERNAL_EDGE_FUNCTIONS_SRC: internalSrcDirectory,
24
+ IS_LOCAL: isRunningLocally,
25
+ },
26
+ debug,
27
+ systemLog,
28
+ featureFlags,
29
+ logs,
30
+ netlifyConfig,
31
+ }) {
32
+ const { edge_functions: configDeclarations = [] } = netlifyConfig
33
+ const distPath = resolve(buildDir, distDirectory)
34
+ const internalSrcPath = resolve(buildDir, internalSrcDirectory)
35
+ const distImportMapPath = join(dirname(internalSrcPath), IMPORT_MAP_FILENAME)
36
+ const srcPath = srcDirectory ? resolve(buildDir, srcDirectory) : undefined
37
+ const sourcePaths = [internalSrcPath, srcPath].filter(Boolean)
38
+
39
+ logFunctions({ internalSrcDirectory, internalSrcPath, logs, srcDirectory, srcPath })
40
+
41
+ const { declarations: internalDeclarations, importMap } = await parseManifest(internalSrcPath, systemLog)
42
+ const declarations = [...configDeclarations, ...internalDeclarations]
43
+
44
+ // If we're running in buildbot and the feature flag is enabled, we set the
45
+ // Deno cache dir to a directory that is persisted between builds.
46
+ const cacheDirectory =
47
+ !isRunningLocally && featureFlags.edge_functions_cache_cli ? resolve(buildDir, DENO_CLI_CACHE_DIRECTORY) : undefined
48
+
49
+ // Edge Bundler expects the dist directory to exist.
50
+ await fs.mkdir(distPath, { recursive: true })
51
+
52
+ try {
53
+ const { manifest } = await bundle(sourcePaths, distPath, declarations, {
54
+ basePath: buildDir,
55
+ cacheDirectory,
56
+ debug,
57
+ distImportMapPath,
58
+ featureFlags,
59
+ importMaps: [importMap].filter(Boolean),
60
+ systemLogger: featureFlags.edge_functions_system_logger ? systemLog : undefined,
61
+ })
62
+
63
+ systemLog('Edge Functions manifest:', manifest)
64
+ } catch (error) {
65
+ tagBundlingError(error)
66
+
67
+ throw error
68
+ }
69
+
70
+ await validateEdgeFunctionsManifest({ buildDir, constants: { EDGE_FUNCTIONS_DIST: distDirectory } })
71
+
72
+ return {}
73
+ }
74
+
75
+ // We run this core step if at least one of the functions directories (the
76
+ // one configured by the user or the internal one) exists. We use a dynamic
77
+ // `condition` because the directories might be created by the build command
78
+ // or plugins.
79
+ const hasEdgeFunctionsDirectories = async function ({
80
+ buildDir,
81
+ constants: { INTERNAL_EDGE_FUNCTIONS_SRC, EDGE_FUNCTIONS_SRC },
82
+ }) {
83
+ const hasFunctionsSrc = EDGE_FUNCTIONS_SRC !== undefined && EDGE_FUNCTIONS_SRC !== ''
84
+
85
+ if (hasFunctionsSrc) {
86
+ return true
87
+ }
88
+
89
+ const internalFunctionsSrc = resolve(buildDir, INTERNAL_EDGE_FUNCTIONS_SRC)
90
+
91
+ return await pathExists(internalFunctionsSrc)
92
+ }
93
+
94
+ const logFunctions = async ({
95
+ internalSrcDirectory,
96
+ internalSrcPath,
97
+ logs,
98
+ srcDirectory: userFunctionsSrc,
99
+ srcPath,
100
+ }) => {
101
+ const [userFunctions, internalFunctions] = await Promise.all([find([srcPath]), find([internalSrcPath])])
102
+ const userFunctionsSrcExists = await pathExists(srcPath)
103
+ const internalFunctionsSrc = internalSrcDirectory
104
+
105
+ logFunctionsToBundle({
106
+ logs,
107
+ userFunctions: userFunctions.map(({ name }) => name),
108
+ userFunctionsSrc,
109
+ userFunctionsSrcExists,
110
+ internalFunctions: internalFunctions.map(({ name }) => name),
111
+ internalFunctionsSrc,
112
+ type: 'Edge Functions',
113
+ })
114
+ }
115
+
116
+ export const bundleEdgeFunctions = {
117
+ event: 'onBuild',
118
+ coreStep,
119
+ coreStepId: 'edge_functions_bundling',
120
+ coreStepName: 'Edge Functions bundling',
121
+ coreStepDescription: () => 'Edge Functions bundling',
122
+ condition: hasEdgeFunctionsDirectories,
123
+ }
@@ -0,0 +1,21 @@
1
+ import { CUSTOM_ERROR_KEY, getErrorInfo, isBuildError } from '../../../error/info.js'
2
+
3
+ // If we have a custom error tagged with `functionsBundling` (which happens if
4
+ // there is an issue with user code), we tag it as coming from an edge function
5
+ // so that we can adjust the downstream error messages accordingly.
6
+ export const tagBundlingError = (error) => {
7
+ if (!isBuildError(error)) {
8
+ return
9
+ }
10
+
11
+ const [errorInfo = {}] = getErrorInfo(error)
12
+
13
+ if (errorInfo.type !== 'functionsBundling') {
14
+ return
15
+ }
16
+
17
+ error[CUSTOM_ERROR_KEY].location = {
18
+ ...error[CUSTOM_ERROR_KEY].location,
19
+ functionType: 'edge',
20
+ }
21
+ }
@@ -0,0 +1,60 @@
1
+ import { promises as fs } from 'fs'
2
+ import { dirname, join, resolve } from 'path'
3
+ import { pathToFileURL } from 'url'
4
+
5
+ const parseManifest = async (internalSourceDirectory, systemLog) => {
6
+ const manifestPath = join(internalSourceDirectory, 'manifest.json')
7
+
8
+ try {
9
+ const data = await fs.readFile(manifestPath)
10
+ const manifest = JSON.parse(data)
11
+
12
+ if (manifest.version !== 1) {
13
+ throw new Error('Unsupported manifest version')
14
+ }
15
+
16
+ const result = {
17
+ declarations: manifest.functions,
18
+ }
19
+
20
+ if (manifest.import_map) {
21
+ const importMapPath = resolve(dirname(manifestPath), manifest.import_map)
22
+ const importMap = await readImportMap(importMapPath)
23
+
24
+ return {
25
+ ...result,
26
+ importMap: {
27
+ baseURL: pathToFileURL(importMapPath),
28
+ ...importMap,
29
+ },
30
+ }
31
+ }
32
+
33
+ return result
34
+ } catch (error) {
35
+ if (error.code !== 'ENOENT') {
36
+ systemLog('Error while parsing internal edge functions manifest:', error)
37
+ }
38
+ }
39
+
40
+ return {
41
+ declarations: [],
42
+ }
43
+ }
44
+
45
+ const readImportMap = async (path) => {
46
+ try {
47
+ const data = await fs.readFile(path)
48
+ const importMap = JSON.parse(data)
49
+
50
+ return importMap
51
+ } catch {
52
+ // no-op
53
+ }
54
+
55
+ return {
56
+ imports: {},
57
+ }
58
+ }
59
+
60
+ export { parseManifest }
@@ -0,0 +1,89 @@
1
+ import { promises as fs } from 'fs'
2
+ import { join, resolve } from 'path'
3
+
4
+ import Ajv from 'ajv'
5
+ import ajvErrors from 'ajv-errors'
6
+
7
+ import { addErrorInfo } from '../../../error/info.js'
8
+
9
+ const ajv = new Ajv({ allErrors: true })
10
+ ajvErrors(ajv)
11
+
12
+ // regex pattern for manifest route pattern
13
+ // checks if the pattern string starts with ^ and ends with $
14
+ // we define this format in edge-bundler:
15
+ // https://github.com/netlify/edge-bundler/blob/main/src/manifest.ts#L66
16
+ const normalizedPatternRegex = /^\^.*\$$/
17
+ ajv.addFormat('regexPattern', {
18
+ async: true,
19
+ validate: (data) => normalizedPatternRegex.test(data),
20
+ })
21
+
22
+ const bundlesSchema = {
23
+ $async: true,
24
+ type: 'object',
25
+ required: ['asset', 'format'],
26
+ properties: {
27
+ asset: { type: 'string' },
28
+ format: { type: 'string' },
29
+ },
30
+ additionalProperties: false,
31
+ }
32
+
33
+ const routesSchema = {
34
+ $async: true,
35
+ type: 'object',
36
+ required: ['function', 'pattern'],
37
+ properties: {
38
+ function: { type: 'string' },
39
+ pattern: { type: 'string', format: 'regexPattern', errorMessage: `must match format ${normalizedPatternRegex}` },
40
+ },
41
+ additionalProperties: false,
42
+ }
43
+
44
+ const edgeManifestSchema = {
45
+ $async: true,
46
+ type: 'object',
47
+ required: ['bundles', 'routes', 'bundler_version'],
48
+ properties: {
49
+ bundles: {
50
+ type: 'array',
51
+ items: bundlesSchema,
52
+ },
53
+ routes: {
54
+ type: 'array',
55
+ items: routesSchema,
56
+ },
57
+ bundler_version: { type: 'string' },
58
+ },
59
+ additionalProperties: false,
60
+ errorMessage: "Couldn't validate Edge Functions manifest.json",
61
+ }
62
+
63
+ const validateManifest = async (manifestData) => {
64
+ const validate = ajv.compile(edgeManifestSchema)
65
+
66
+ await validate(manifestData)
67
+ }
68
+
69
+ export const validateEdgeFunctionsManifest = async function ({
70
+ buildDir,
71
+ constants: { EDGE_FUNCTIONS_DIST: distDirectory },
72
+ }) {
73
+ try {
74
+ const edgeFunctionsDistPath = resolve(buildDir, distDirectory)
75
+ const manifestPath = join(edgeFunctionsDistPath, 'manifest.json')
76
+ const data = await fs.readFile(manifestPath)
77
+ const manifestData = JSON.parse(data)
78
+
79
+ await validateManifest(manifestData)
80
+ } catch (error) {
81
+ const isValidationErr = error instanceof Ajv.ValidationError
82
+ const parsedErr = isValidationErr ? error.errors : error
83
+
84
+ addErrorInfo(parsedErr, { type: 'coreStep' })
85
+ throw new Error(isValidationErr ? JSON.stringify(parsedErr, null, 2) : parsedErr)
86
+ }
87
+
88
+ return {}
89
+ }