@netlify/build 27.18.5-rc → 27.18.5

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/package.json +8 -9
  2. package/src/core/bin.js +83 -0
  3. package/src/core/build.js +559 -0
  4. package/src/core/config.js +186 -0
  5. package/src/core/constants.js +156 -0
  6. package/src/core/dev.js +31 -0
  7. package/src/core/dry.js +39 -0
  8. package/src/core/feature_flags.js +22 -0
  9. package/src/core/flags.js +204 -0
  10. package/src/core/lingering.js +85 -0
  11. package/src/core/main.js +165 -0
  12. package/src/core/missing_side_file.js +29 -0
  13. package/src/core/normalize_flags.js +70 -0
  14. package/src/core/severity.js +22 -0
  15. package/src/core/user_node_version.js +41 -0
  16. package/src/env/changes.js +52 -0
  17. package/src/env/main.js +19 -0
  18. package/src/env/metadata.js +81 -0
  19. package/src/error/api.js +46 -0
  20. package/src/error/build.js +50 -0
  21. package/src/error/cancel.js +8 -0
  22. package/src/error/colors.js +11 -0
  23. package/src/error/handle.js +57 -0
  24. package/src/error/info.js +46 -0
  25. package/src/error/monitor/location.js +21 -0
  26. package/src/error/monitor/normalize.js +96 -0
  27. package/src/error/monitor/print.js +42 -0
  28. package/src/error/monitor/report.js +138 -0
  29. package/src/error/monitor/start.js +69 -0
  30. package/src/error/parse/clean_stack.js +87 -0
  31. package/src/error/parse/location.js +62 -0
  32. package/src/error/parse/normalize.js +29 -0
  33. package/src/error/parse/parse.js +97 -0
  34. package/src/error/parse/plugin.js +70 -0
  35. package/src/error/parse/properties.js +23 -0
  36. package/src/error/parse/serialize_log.js +42 -0
  37. package/src/error/parse/serialize_status.js +23 -0
  38. package/src/error/parse/stack.js +43 -0
  39. package/src/error/type.js +189 -0
  40. package/src/install/functions.js +28 -0
  41. package/src/install/local.js +62 -0
  42. package/src/install/main.js +81 -0
  43. package/src/install/missing.js +67 -0
  44. package/src/log/colors.js +34 -0
  45. package/src/log/description.js +26 -0
  46. package/src/log/header.js +16 -0
  47. package/src/log/header_func.js +17 -0
  48. package/src/log/logger.js +161 -0
  49. package/src/log/messages/compatibility.js +178 -0
  50. package/src/log/messages/config.js +107 -0
  51. package/src/log/messages/core.js +70 -0
  52. package/src/log/messages/core_steps.js +104 -0
  53. package/src/log/messages/dry.js +63 -0
  54. package/src/log/messages/install.js +32 -0
  55. package/src/log/messages/ipc.js +38 -0
  56. package/src/log/messages/mutations.js +82 -0
  57. package/src/log/messages/plugins.js +39 -0
  58. package/src/log/messages/status.js +16 -0
  59. package/src/log/messages/steps.js +22 -0
  60. package/src/log/old_version.js +41 -0
  61. package/src/log/serialize.js +13 -0
  62. package/src/log/stream.js +85 -0
  63. package/src/log/theme.js +26 -0
  64. package/src/plugins/child/diff.js +55 -0
  65. package/src/plugins/child/error.js +32 -0
  66. package/src/plugins/child/lazy.js +18 -0
  67. package/src/plugins/child/load.js +29 -0
  68. package/src/plugins/child/logic.js +65 -0
  69. package/src/plugins/child/main.js +51 -0
  70. package/src/plugins/child/run.js +28 -0
  71. package/src/plugins/child/status.js +74 -0
  72. package/src/plugins/child/typescript.js +45 -0
  73. package/src/plugins/child/utils.js +56 -0
  74. package/src/plugins/child/validate.js +34 -0
  75. package/src/plugins/compatibility.js +132 -0
  76. package/src/plugins/error.js +50 -0
  77. package/src/plugins/events.js +17 -0
  78. package/src/plugins/expected_version.js +119 -0
  79. package/src/plugins/ipc.js +145 -0
  80. package/src/plugins/list.js +86 -0
  81. package/src/plugins/load.js +70 -0
  82. package/src/plugins/manifest/check.js +106 -0
  83. package/src/plugins/manifest/load.js +41 -0
  84. package/src/plugins/manifest/main.js +22 -0
  85. package/src/plugins/manifest/path.js +31 -0
  86. package/src/plugins/manifest/validate.js +108 -0
  87. package/src/plugins/node_version.js +50 -0
  88. package/src/plugins/options.js +88 -0
  89. package/src/plugins/pinned_version.js +131 -0
  90. package/src/plugins/resolve.js +152 -0
  91. package/src/plugins/spawn.js +72 -0
  92. package/src/plugins_core/add.js +49 -0
  93. package/src/plugins_core/build_command.js +75 -0
  94. package/src/plugins_core/deploy/buildbot_client.js +113 -0
  95. package/src/plugins_core/deploy/index.js +73 -0
  96. package/src/plugins_core/deploy/manifest.yml +1 -0
  97. package/src/plugins_core/edge_functions/index.js +123 -0
  98. package/src/plugins_core/edge_functions/lib/error.js +21 -0
  99. package/src/plugins_core/edge_functions/lib/internal_manifest.js +60 -0
  100. package/src/plugins_core/edge_functions/validate_manifest/validate_edge_functions_manifest.js +89 -0
  101. package/src/plugins_core/functions/error.js +163 -0
  102. package/src/plugins_core/functions/feature_flags.js +6 -0
  103. package/src/plugins_core/functions/index.js +161 -0
  104. package/src/plugins_core/functions/utils.js +66 -0
  105. package/src/plugins_core/functions/zisi.js +56 -0
  106. package/src/plugins_core/functions_install/index.js +13 -0
  107. package/src/plugins_core/functions_install/manifest.yml +1 -0
  108. package/src/plugins_core/list.js +27 -0
  109. package/src/status/add.js +36 -0
  110. package/src/status/colors.js +23 -0
  111. package/src/status/load_error.js +11 -0
  112. package/src/status/report.js +137 -0
  113. package/src/status/success.js +18 -0
  114. package/src/steps/core_step.js +92 -0
  115. package/src/steps/error.js +102 -0
  116. package/src/steps/get.js +51 -0
  117. package/src/steps/plugin.js +85 -0
  118. package/src/steps/return.js +52 -0
  119. package/src/steps/run_core_steps.js +200 -0
  120. package/src/steps/run_step.js +304 -0
  121. package/src/steps/run_steps.js +179 -0
  122. package/src/steps/update_config.js +93 -0
  123. package/src/telemetry/main.js +136 -0
  124. package/src/time/aggregate.js +146 -0
  125. package/src/time/main.js +48 -0
  126. package/src/time/measure.js +22 -0
  127. package/src/time/report.js +59 -0
  128. package/src/utils/errors.js +12 -0
  129. package/src/utils/json.js +19 -0
  130. package/src/utils/omit.js +6 -0
  131. package/src/utils/package.js +23 -0
  132. package/src/utils/remove_falsy.js +10 -0
  133. package/src/utils/resolve.js +46 -0
  134. package/src/utils/runtime.js +5 -0
  135. package/src/utils/semver.js +34 -0
@@ -0,0 +1,57 @@
1
+ import { cwd as getCwd } from 'process'
2
+
3
+ import { pathExists } from 'path-exists'
4
+
5
+ import { logBuildError } from '../log/messages/core.js'
6
+ import { logOldCliVersionError } from '../log/old_version.js'
7
+
8
+ import { removeErrorColors } from './colors.js'
9
+ import { getErrorInfo } from './info.js'
10
+ import { reportBuildError } from './monitor/report.js'
11
+ import { parseErrorInfo } from './parse/parse.js'
12
+
13
+ // Logs and reports a build failure
14
+ export const handleBuildError = async function (
15
+ error,
16
+ { errorMonitor, netlifyConfig, childEnv, mode, logs, debug, testOpts },
17
+ ) {
18
+ const basicErrorInfo = parseErrorInfo(error)
19
+
20
+ if (await isCancelCrash(error)) {
21
+ return basicErrorInfo
22
+ }
23
+
24
+ removeErrorColors(error)
25
+ // Some errors, such as telemetry ones, should not be logged
26
+ if (basicErrorInfo.showInBuildLog) {
27
+ logBuildError({ error, netlifyConfig, mode, logs, debug, testOpts })
28
+ }
29
+ logOldCliVersionError({ mode, testOpts })
30
+ await reportBuildError({ error, errorMonitor, childEnv, logs, testOpts })
31
+
32
+ return basicErrorInfo
33
+ }
34
+
35
+ // When builds are canceled, the whole filesystem is being deleted.
36
+ // However, the process (and build) keeps going. Because no files exist anymore,
37
+ // the build eventually crashes with a randomly odd error. Those should not be
38
+ // logged nor reported.
39
+ // However builds canceled with `utils.build.cancelBuild()` should still show
40
+ // "Build canceled by ..."
41
+ const isCancelCrash = async function (error) {
42
+ const [{ type }] = getErrorInfo(error)
43
+ if (type === 'cancelBuild') {
44
+ return false
45
+ }
46
+
47
+ try {
48
+ // TODO: find a better way to detect that the build is being cancelled.
49
+ // Otherwise bugs due to (for example) the build command removing
50
+ // `process.cwd` are currently not logged/reported.
51
+ const cwd = getCwd()
52
+ return !(await pathExists(cwd))
53
+ // `process.cwd()` fails when the current directory does not exist
54
+ } catch {
55
+ return true
56
+ }
57
+ }
@@ -0,0 +1,46 @@
1
+ // Add information related to an error without colliding with existing properties
2
+ export const addDefaultErrorInfo = function (error, info) {
3
+ if (!canHaveErrorInfo(error)) {
4
+ return
5
+ }
6
+
7
+ error[CUSTOM_ERROR_KEY] = { ...info, ...error[CUSTOM_ERROR_KEY] }
8
+ }
9
+
10
+ // Retrieve error information added by our system
11
+ export const addErrorInfo = function (error, info) {
12
+ if (!canHaveErrorInfo(error)) {
13
+ return
14
+ }
15
+
16
+ error[CUSTOM_ERROR_KEY] = { ...error[CUSTOM_ERROR_KEY], ...info }
17
+ }
18
+
19
+ export const getErrorInfo = function (error) {
20
+ if (!isBuildError(error)) {
21
+ return [{}, error]
22
+ }
23
+
24
+ const { [CUSTOM_ERROR_KEY]: errorInfo, ...errorA } = error
25
+ return [errorInfo, errorA]
26
+ }
27
+
28
+ // Change error type from one to another
29
+ export const changeErrorType = function (error, oldType, newType) {
30
+ const [{ type }] = getErrorInfo(error)
31
+ if (type === oldType) {
32
+ addErrorInfo(error, { type: newType })
33
+ }
34
+ }
35
+
36
+ export const isBuildError = function (error) {
37
+ return canHaveErrorInfo(error) && error[CUSTOM_ERROR_KEY] !== undefined
38
+ }
39
+
40
+ // Exceptions that are not objects (including `Error` instances) cannot have an
41
+ // `CUSTOM_ERROR_KEY` property
42
+ const canHaveErrorInfo = function (error) {
43
+ return error != null
44
+ }
45
+
46
+ export const CUSTOM_ERROR_KEY = 'customErrorInfo'
@@ -0,0 +1,21 @@
1
+ // Retrieve plugin's location and build logs
2
+ export const getLocationMetadata = function (location, envMetadata) {
3
+ const buildLogs = getBuildLogs(envMetadata)
4
+
5
+ if (buildLogs === undefined && location === undefined) {
6
+ return
7
+ }
8
+
9
+ return { buildLogs, ...location }
10
+ }
11
+
12
+ // Retrieve the URL to the build logs
13
+ const getBuildLogs = function ({ SITE_NAME, DEPLOY_ID }) {
14
+ if (SITE_NAME === undefined || DEPLOY_ID === undefined) {
15
+ return
16
+ }
17
+
18
+ return `${NETLIFY_ORIGIN}/sites/${SITE_NAME}/deploys/${DEPLOY_ID}`
19
+ }
20
+
21
+ const NETLIFY_ORIGIN = 'https://app.netlify.com'
@@ -0,0 +1,96 @@
1
+ // We group errors by `error.message`. However some `error.message` contain
2
+ // unique IDs, etc. which defeats that grouping. So we normalize those to make
3
+ // them consistent
4
+ export const normalizeGroupingMessage = function (message, type) {
5
+ const messageA = removeDependenciesLogs(message, type)
6
+ const messageB = NORMALIZE_REGEXPS.reduce(normalizeMessage, messageA)
7
+
8
+ // If this is a functions bundling error, we'll use additional normalization
9
+ // rules to group errors more aggressively.
10
+ if (type === 'functionsBundling') {
11
+ return FUNCTIONS_BUNDLING_REGEXPS.reduce(normalizeMessage, messageB)
12
+ }
13
+
14
+ return messageB
15
+ }
16
+
17
+ // Discard debug/info installation information
18
+ const removeDependenciesLogs = function (message, type) {
19
+ if (type !== 'dependencies') {
20
+ return message
21
+ }
22
+
23
+ return message.split('\n').filter(isErrorLine).join('\n')
24
+ }
25
+
26
+ const isErrorLine = function (line) {
27
+ return ERROR_LINES.some((errorLine) => line.startsWith(errorLine))
28
+ }
29
+
30
+ const ERROR_LINES = [
31
+ // Prefix for npm
32
+ 'npm ERR!',
33
+ // Prefix for Yarn
34
+ 'error',
35
+ ]
36
+
37
+ const normalizeMessage = function (message, [regExp, replacement]) {
38
+ return message.replace(regExp, replacement)
39
+ }
40
+
41
+ const NORMALIZE_REGEXPS = [
42
+ // Base64 URL
43
+ [/(data:[^;]+;base64),[\w+/-=]+/g, 'dataURI'],
44
+ // File paths
45
+ [/(["'`, ]|^)([^"'`, \n]*[/\\][^"'`, \n]*)(?=["'`, ]|$)/gm, '$1/file/path'],
46
+ // Semantic versions
47
+ [/\d+\.\d+\.\d+(-\d+)?/g, '1.0.0'],
48
+ [/version "[^"]+"/g, 'version "1.0.0"'],
49
+ // Cypress plugin prints the user's platform
50
+ [/^Platform: .*/gm, ''],
51
+ // URLs
52
+ [/https?:[\w.+~!$&'()*,;=:@/?#]+/g, 'https://domain.com'],
53
+ // Numbers, e.g. number of issues/problems
54
+ [/\d+/g, '0'],
55
+ // Hexadecimal strings
56
+ [/[\da-fA-F-]{6,}/g, 'hex'],
57
+ // On unknown inputs, we print the inputs
58
+ [/(does not accept any inputs but you specified: ).*/, '$1'],
59
+ [/(Unknown inputs for plugin).*/, '$1'],
60
+ [/(Plugin inputs should be one of: ).*/, '$1'],
61
+ // On required inputs, we print the inputs
62
+ [/^Plugin inputs[^]*/gm, ''],
63
+ [/(Required inputs for plugin).*/gm, '$1'],
64
+ // Netlify Functions validation check
65
+ [/(should target a directory, not a regular file):.*/, '$1'],
66
+ // zip-it-and-ship-it error when there is an `import()` but dependencies
67
+ // were not installed
68
+ [/(Cannot find module) '([^']+)'/g, "$1 'moduleName'"],
69
+ [/(A Netlify Function is using) "[^"]+"/g, '$1 "moduleName"'],
70
+ // Deploy upload errors include the filename
71
+ [/(Upload cancelled).*/g, '$1'],
72
+ [/(aborting upload of file).*/g, '$1'],
73
+ // netlify-plugin-inline-critical-css errors prints a list of file paths
74
+ [/Searched in: .*/g, ''],
75
+ // netlify-plugin-subfont prints font name in errors
76
+ [/(is not supported yet): .*/, '$1'],
77
+ // netlify-plugin-subfont prints generic information in every error that
78
+ // is highly build-specific
79
+ [/^(vers?ions|Plugin configuration|Subfont called with): {[^}]+}/gm, ''],
80
+ [/^Resolved entry points: \[[^\]]+]/gm, ''],
81
+ // netlify-plugin-minify-html parse error
82
+ [/(Parse Error):[^]*/, '$1'],
83
+ // Multiple empty lines
84
+ [/^\s*$/gm, ''],
85
+ ]
86
+
87
+ const FUNCTIONS_BUNDLING_REGEXPS = [
88
+ // String literals and identifiers
89
+ [/"([^"]+)"/g, '""'],
90
+ [/'([^']+)'/g, "''"],
91
+ [/`([^`]+)`/g, '``'],
92
+
93
+ // Rust crates
94
+ [/(?:Downloaded \S+ v[\d.]+\s*)+/gm, 'Downloaded crates'],
95
+ [/(?:Compiling \S+ v[\d.]+\s*)+/gm, 'Compiled crates'],
96
+ ]
@@ -0,0 +1,42 @@
1
+ import { log } from '../../log/logger.js'
2
+
3
+ // Print event payload instead of sending actual request during tests
4
+ export const printEventForTest = function (
5
+ { name: errorClass, message: errorMessage },
6
+ {
7
+ context,
8
+ groupingHash,
9
+ severity,
10
+ unhandled,
11
+ _metadata: {
12
+ location,
13
+ plugin: { packageName, homepage } = {},
14
+ pluginPackageJson,
15
+ tsConfig,
16
+ env: { BUILD_ID } = {},
17
+ other,
18
+ },
19
+ },
20
+ logs,
21
+ ) {
22
+ const eventString = JSON.stringify(
23
+ {
24
+ errorClass,
25
+ errorMessage,
26
+ context,
27
+ groupingHash,
28
+ severity,
29
+ unhandled,
30
+ location,
31
+ packageName,
32
+ pluginPackageJson: pluginPackageJson !== undefined,
33
+ homepage,
34
+ tsConfig,
35
+ BUILD_ID,
36
+ other,
37
+ },
38
+ null,
39
+ 2,
40
+ )
41
+ log(logs, `\nError monitoring payload:\n${eventString}`)
42
+ }
@@ -0,0 +1,138 @@
1
+ import { type as osType, freemem, totalmem } from 'os'
2
+ import { promisify } from 'util'
3
+
4
+ import osName from 'os-name'
5
+
6
+ import { getEnvMetadata } from '../../env/metadata.js'
7
+ import { log } from '../../log/logger.js'
8
+ import { parseErrorInfo } from '../parse/parse.js'
9
+ import { getHomepage } from '../parse/plugin.js'
10
+
11
+ import { getLocationMetadata } from './location.js'
12
+ import { normalizeGroupingMessage } from './normalize.js'
13
+ import { printEventForTest } from './print.js'
14
+
15
+ // Report a build failure for monitoring purpose
16
+ export const reportBuildError = async function ({ error, errorMonitor, childEnv, logs, testOpts }) {
17
+ if (errorMonitor === undefined) {
18
+ return
19
+ }
20
+
21
+ const { errorInfo, type, severity, title, group = title } = parseErrorInfo(error)
22
+ const severityA = getSeverity(severity, errorInfo)
23
+ const groupA = getGroup(group, errorInfo)
24
+ const groupingHash = getGroupingHash(groupA, error, type, errorInfo)
25
+ const metadata = getMetadata(errorInfo, childEnv, groupingHash)
26
+ const app = getApp()
27
+ const eventProps = getEventProps({ severity: severityA, group: groupA, groupingHash, metadata, app })
28
+
29
+ const errorName = updateErrorName(error, type)
30
+ try {
31
+ await reportError({ errorMonitor, error, logs, testOpts, eventProps })
32
+ } finally {
33
+ error.name = errorName
34
+ }
35
+ }
36
+
37
+ // Plugin authors test their plugins as local plugins. Errors there are more
38
+ // like development errors, and should be reported as `info` only.
39
+ const getSeverity = function (severity, { location: { loadedFrom } = {} }) {
40
+ if (loadedFrom === 'local' || severity === 'none') {
41
+ return 'info'
42
+ }
43
+
44
+ return severity
45
+ }
46
+
47
+ const getGroup = function (group, errorInfo) {
48
+ if (typeof group !== 'function') {
49
+ return group
50
+ }
51
+
52
+ return group(errorInfo)
53
+ }
54
+
55
+ const getGroupingHash = function (group, error, type, errorInfo = {}) {
56
+ // If the error has a `normalizedMessage`, we use it as the grouping hash.
57
+ if (errorInfo.normalizedMessage) {
58
+ return errorInfo.normalizedMessage
59
+ }
60
+
61
+ const message = error instanceof Error && typeof error.message === 'string' ? error.message : String(error)
62
+ const messageA = normalizeGroupingMessage(message, type)
63
+ return `${group}\n${messageA}`
64
+ }
65
+
66
+ const getMetadata = function ({ location, plugin, tsConfig }, childEnv, groupingHash) {
67
+ const pluginMetadata = getPluginMetadata({ location, plugin })
68
+ const envMetadata = getEnvMetadata(childEnv)
69
+ const locationMetadata = getLocationMetadata(location, envMetadata)
70
+ return { location: locationMetadata, ...pluginMetadata, tsConfig, env: envMetadata, other: { groupingHash } }
71
+ }
72
+
73
+ const getPluginMetadata = function ({ location, plugin }) {
74
+ if (plugin === undefined) {
75
+ return {}
76
+ }
77
+
78
+ const { pluginPackageJson, ...pluginA } = plugin
79
+ const homepage = getHomepage(pluginPackageJson, location)
80
+ return { plugin: { ...pluginA, homepage }, pluginPackageJson }
81
+ }
82
+
83
+ const getApp = function () {
84
+ return {
85
+ osName: osType(),
86
+ osVersion: osName(),
87
+ freeMemory: freemem(),
88
+ totalMemory: totalmem(),
89
+ }
90
+ }
91
+
92
+ // `error.name` is shown proeminently in the Bugsnag UI. We need to update it to
93
+ // match error `type` since it is more granular and useful.
94
+ // But we change it back after Bugsnag is done reporting.
95
+ const updateErrorName = function (error, type) {
96
+ const { name } = error
97
+ // This might fail if `name` is a getter or is non-writable.
98
+ try {
99
+ error.name = type
100
+ } catch {}
101
+ return name
102
+ }
103
+
104
+ const reportError = async function ({ errorMonitor, error, logs, testOpts, eventProps }) {
105
+ if (testOpts.errorMonitor) {
106
+ printEventForTest(error, eventProps, logs)
107
+ return
108
+ }
109
+
110
+ try {
111
+ await promisify(errorMonitor.notify)(error, (event) => onError(event, eventProps))
112
+ // Failsafe
113
+ } catch {
114
+ log(logs, `Error monitor could not notify\n${error.stack}`)
115
+ }
116
+ }
117
+
118
+ const getEventProps = function ({ severity, group, groupingHash, metadata, app }) {
119
+ // `unhandled` is used to calculate Releases "stabiity score", which is
120
+ // basically the percentage of unhandled errors. Since we handle all errors,
121
+ // we need to implement this according to error types.
122
+ const unhandled = severity === 'error'
123
+ return { severity, context: group, groupingHash, _metadata: metadata, app, unhandled }
124
+ }
125
+
126
+ // Add more information to Bugsnag events
127
+ const onError = function (event, eventProps) {
128
+ // Bugsnag client requires directly mutating the `event`
129
+ // eslint-disable-next-line fp/no-mutating-assign
130
+ Object.assign(event, {
131
+ ...eventProps,
132
+ unhandled: event.unhandled || eventProps.unhandled,
133
+ // eslint-disable-next-line no-underscore-dangle
134
+ _metadata: { ...event._metadata, ...eventProps._metadata },
135
+ app: { ...event.app, ...eventProps.app },
136
+ })
137
+ return true
138
+ }
@@ -0,0 +1,69 @@
1
+ import { fileURLToPath } from 'url'
2
+
3
+ import Bugsnag from '@bugsnag/js'
4
+ import memoizeOne from 'memoize-one'
5
+
6
+ import { log } from '../../log/logger.js'
7
+ import { ROOT_PACKAGE_JSON } from '../../utils/json.js'
8
+
9
+ const projectRoot = fileURLToPath(new URL('../../..', import.meta.url))
10
+
11
+ // Start a client to monitor errors
12
+ export const startErrorMonitor = function ({ flags: { mode }, logs, bugsnagKey }) {
13
+ if (!bugsnagKey) {
14
+ return
15
+ }
16
+
17
+ const isTest = isBugsnagTest(bugsnagKey)
18
+ const releaseStage = getReleaseStage(mode)
19
+ const logger = getLogger(logs, isTest)
20
+ try {
21
+ const errorMonitor = startBugsnag({
22
+ apiKey: bugsnagKey,
23
+ appVersion: `${ROOT_PACKAGE_JSON.name} ${ROOT_PACKAGE_JSON.version}`,
24
+ appType: ROOT_PACKAGE_JSON.name,
25
+ releaseStage,
26
+ logger,
27
+ projectRoot,
28
+ })
29
+
30
+ // Allows knowing the percentage of failed builds per release
31
+ if (!isTest) {
32
+ errorMonitor.startSession()
33
+ }
34
+
35
+ return errorMonitor
36
+ // Failsafe
37
+ } catch (error) {
38
+ log(logs, `Error monitor could not start\n${error.stack}`)
39
+ }
40
+ }
41
+
42
+ const isBugsnagTest = function (bugsnagKey) {
43
+ return bugsnagKey === BUGSNAG_TEST_KEY
44
+ }
45
+
46
+ const BUGSNAG_TEST_KEY = '00000000000000000000000000000000'
47
+
48
+ // Bugsnag.start() caches a global instance and warns on duplicate calls.
49
+ // This ensures the warning message is not shown when calling the main function
50
+ // several times.
51
+ const startBugsnag = memoizeOne(Bugsnag.start.bind(Bugsnag), () => true)
52
+
53
+ // Based the release stage on the `mode`
54
+ const getReleaseStage = function (mode = DEFAULT_RELEASE_STAGE) {
55
+ return mode
56
+ }
57
+
58
+ const DEFAULT_RELEASE_STAGE = 'unknown'
59
+
60
+ // We don't want Bugsnag logs except on warnings/errors.
61
+ // We also want to use our own `log` utility, unprefixed.
62
+ // In tests, we don't print Bugsnag because it sometimes randomly fails to
63
+ // send sessions, which prints warning messags in test snapshots.
64
+ const getLogger = function (logs, isTest) {
65
+ const logFunc = isTest ? noop : log.bind(null, logs)
66
+ return { debug: noop, info: noop, warn: logFunc, error: logFunc }
67
+ }
68
+
69
+ const noop = function () {}
@@ -0,0 +1,87 @@
1
+ import { cwd } from 'process'
2
+
3
+ import cleanStack from 'clean-stack'
4
+ import stripAnsi from 'strip-ansi'
5
+
6
+ // Clean stack traces:
7
+ // - remove our internal code, e.g. the logic spawning plugins
8
+ // - remove node modules and Node.js internals
9
+ // - strip process.cwd()
10
+ // - remove colors
11
+ // Keep non stack trace lines as is.
12
+ // We do not use libraries that patch `Error.prepareStackTrace()` because they
13
+ // tend to create issues.
14
+ export const cleanStacks = function ({ stack, rawStack, debug }) {
15
+ if (stack === undefined) {
16
+ return
17
+ }
18
+
19
+ // Internal errors / bugs keep their full stack trace
20
+ // Same in debug mode
21
+ if (rawStack || debug) {
22
+ return stack
23
+ }
24
+
25
+ return stack.split('\n').reduce(cleanStackLine, '').replace(INITIAL_NEWLINES, '')
26
+ }
27
+
28
+ const cleanStackLine = function (lines, line) {
29
+ const lineA = line.replace(getCwd(), '')
30
+ const lineB = stripAnsi(lineA)
31
+
32
+ if (!STACK_LINE_REGEXP.test(lineB)) {
33
+ return `${lines}\n${lineA}`
34
+ }
35
+
36
+ if (shouldRemoveStackLine(lineB)) {
37
+ return lines
38
+ }
39
+
40
+ const lineC = cleanStack(lineB)
41
+
42
+ if (lineC === '') {
43
+ return lines
44
+ }
45
+
46
+ return `${lines}\n${lineC}`
47
+ }
48
+
49
+ // `process.cwd()` can sometimes fail: directory name too long, current
50
+ // directory has been removed, access denied.
51
+ const getCwd = function () {
52
+ try {
53
+ return cwd()
54
+ } catch {
55
+ return ''
56
+ }
57
+ }
58
+
59
+ // Check if a line is part of a stack trace
60
+ const STACK_LINE_REGEXP = /^\s+at /
61
+
62
+ const shouldRemoveStackLine = function (line) {
63
+ const lineA = normalizePathSlashes(line)
64
+ return INTERNAL_STACK_STRINGS.some((stackString) => lineA.includes(stackString)) || INTERNAL_STACK_REGEXP.test(lineA)
65
+ }
66
+
67
+ const INTERNAL_STACK_STRINGS = [
68
+ // Anonymous function
69
+ '<anonymous>',
70
+ '(index 0)',
71
+ // nyc internal code
72
+ 'node_modules/append-transform',
73
+ 'node_modules/signal-exit',
74
+ // Node internals
75
+ '(node:',
76
+ ]
77
+
78
+ // This is only needed for local builds and tests
79
+ const INTERNAL_STACK_REGEXP = /(packages|@netlify)\/build\/(src\/|tests\/helpers\/|tests\/.*\/tests.js|node_modules)/
80
+
81
+ const INITIAL_NEWLINES = /^\n+/
82
+
83
+ const normalizePathSlashes = function (line) {
84
+ return line.replace(BACKLASH_REGEXP, '/')
85
+ }
86
+
87
+ const BACKLASH_REGEXP = /\\/g
@@ -0,0 +1,62 @@
1
+ import { getBuildCommandDescription, getPluginOrigin } from '../../log/description.js'
2
+
3
+ // Retrieve an error's location to print in logs.
4
+ // Each error type has its own logic (or none if there's no location to print).
5
+ export const getLocationInfo = function ({ stack, location, locationType }) {
6
+ // No location to print
7
+ if (locationType === undefined && stack === undefined) {
8
+ return
9
+ }
10
+
11
+ // The location is only the stack trace
12
+ if (locationType === undefined) {
13
+ return stack
14
+ }
15
+
16
+ const locationString = LOCATIONS[locationType](location)
17
+ return [locationString, stack].filter(Boolean).join('\n')
18
+ }
19
+
20
+ const getBuildCommandLocation = function ({ buildCommand, buildCommandOrigin }) {
21
+ const description = getBuildCommandDescription(buildCommandOrigin)
22
+ return `In ${description}:
23
+ ${buildCommand}`
24
+ }
25
+
26
+ const getFunctionsBundlingLocation = function ({ functionName, functionType }) {
27
+ if (functionType === 'edge') {
28
+ return 'While bundling edge function'
29
+ }
30
+
31
+ return `While bundling function "${functionName}"`
32
+ }
33
+
34
+ const getCoreStepLocation = function ({ coreStepName }) {
35
+ return `During ${coreStepName}`
36
+ }
37
+
38
+ const getBuildFailLocation = function ({ event, packageName, loadedFrom, origin }) {
39
+ const eventMessage = getEventMessage(event)
40
+ const pluginOrigin = getPluginOrigin(loadedFrom, origin)
41
+ return `${eventMessage} "${packageName}" ${pluginOrigin}`
42
+ }
43
+
44
+ const getEventMessage = function (event) {
45
+ if (event === 'load') {
46
+ return `While loading`
47
+ }
48
+
49
+ return `In "${event}" event in`
50
+ }
51
+
52
+ const getApiLocation = function ({ endpoint, parameters }) {
53
+ return `While calling the Netlify API endpoint '${endpoint}' with:\n${JSON.stringify(parameters, null, 2)}`
54
+ }
55
+
56
+ const LOCATIONS = {
57
+ buildCommand: getBuildCommandLocation,
58
+ functionsBundling: getFunctionsBundlingLocation,
59
+ coreStep: getCoreStepLocation,
60
+ buildFail: getBuildFailLocation,
61
+ api: getApiLocation,
62
+ }
@@ -0,0 +1,29 @@
1
+ // Ensure error is an `Error` instance.
2
+ // If is an `Error` instance but is missing usual `Error` properties, we make
3
+ // sure its static properties are preserved.
4
+ export const normalizeError = function (error) {
5
+ if (Array.isArray(error)) {
6
+ return normalizeArray(error)
7
+ }
8
+
9
+ if (!(error instanceof Error)) {
10
+ return new Error(String(error))
11
+ }
12
+
13
+ if (typeof error.message !== 'string') {
14
+ error.message = String(error)
15
+ }
16
+
17
+ if (typeof error.stack !== 'string') {
18
+ Error.captureStackTrace(error, normalizeError)
19
+ }
20
+
21
+ return error
22
+ }
23
+
24
+ // Some libraries throw arrays of Errors
25
+ const normalizeArray = function (errorArray) {
26
+ const [error, ...errors] = errorArray.map(normalizeError)
27
+ error.errors = errors
28
+ return error
29
+ }