netlify-cli 16.9.3 → 17.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.
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
- "version": "16.9.3",
3
+ "version": "17.0.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "netlify-cli",
9
- "version": "16.9.3",
9
+ "version": "17.0.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
13
13
  "@bugsnag/js": "7.20.2",
14
14
  "@fastify/static": "6.10.2",
15
+ "@netlify/blobs": "^4.0.0",
15
16
  "@netlify/build": "29.23.4",
16
17
  "@netlify/build-info": "7.10.1",
17
18
  "@netlify/config": "20.9.0",
@@ -123,7 +124,7 @@
123
124
  "ntl": "bin/run.mjs"
124
125
  },
125
126
  "engines": {
126
- "node": ">=16.16.0"
127
+ "node": ">=18.18.2"
127
128
  }
128
129
  },
129
130
  "node_modules/@babel/code-frame": {
@@ -801,6 +802,14 @@
801
802
  "resolved": "https://registry.npmjs.org/@netlify/binary-info/-/binary-info-1.0.0.tgz",
802
803
  "integrity": "sha512-4wMPu9iN3/HL97QblBsBay3E1etIciR84izI3U+4iALY+JHCrI+a2jO0qbAZ/nxKoegypYEaiiqWXylm+/zfrw=="
803
804
  },
805
+ "node_modules/@netlify/blobs": {
806
+ "version": "4.0.0",
807
+ "resolved": "https://registry.npmjs.org/@netlify/blobs/-/blobs-4.0.0.tgz",
808
+ "integrity": "sha512-jjAzsH5WCceUz8ubVlYppfhUKuTR4E6OBNherIdH7tYHWy4NnLQ5FQgVP9kR7Ps5HOxl3aPsr5ygu1KQY0mdTQ==",
809
+ "engines": {
810
+ "node": "^14.16.0 || >=16.0.0"
811
+ }
812
+ },
804
813
  "node_modules/@netlify/build": {
805
814
  "version": "29.23.4",
806
815
  "resolved": "https://registry.npmjs.org/@netlify/build/-/build-29.23.4.tgz",
@@ -16905,6 +16914,11 @@
16905
16914
  "resolved": "https://registry.npmjs.org/@netlify/binary-info/-/binary-info-1.0.0.tgz",
16906
16915
  "integrity": "sha512-4wMPu9iN3/HL97QblBsBay3E1etIciR84izI3U+4iALY+JHCrI+a2jO0qbAZ/nxKoegypYEaiiqWXylm+/zfrw=="
16907
16916
  },
16917
+ "@netlify/blobs": {
16918
+ "version": "4.0.0",
16919
+ "resolved": "https://registry.npmjs.org/@netlify/blobs/-/blobs-4.0.0.tgz",
16920
+ "integrity": "sha512-jjAzsH5WCceUz8ubVlYppfhUKuTR4E6OBNherIdH7tYHWy4NnLQ5FQgVP9kR7Ps5HOxl3aPsr5ygu1KQY0mdTQ=="
16921
+ },
16908
16922
  "@netlify/build": {
16909
16923
  "version": "29.23.4",
16910
16924
  "resolved": "https://registry.npmjs.org/@netlify/build/-/build-29.23.4.tgz",
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
3
  "description": "Netlify command line tool",
4
- "version": "16.9.3",
4
+ "version": "17.0.0",
5
5
  "author": "Netlify Inc.",
6
6
  "type": "module",
7
7
  "engines": {
8
- "node": ">=16.16.0"
8
+ "node": ">=18.18.2"
9
9
  },
10
10
  "files": [
11
11
  "/bin",
@@ -44,6 +44,7 @@
44
44
  "dependencies": {
45
45
  "@bugsnag/js": "7.20.2",
46
46
  "@fastify/static": "6.10.2",
47
+ "@netlify/blobs": "^4.0.0",
47
48
  "@netlify/build": "29.23.4",
48
49
  "@netlify/build-info": "7.10.1",
49
50
  "@netlify/config": "20.9.0",
@@ -3,6 +3,7 @@ import process from 'process'
3
3
 
4
4
  import { Option } from 'commander'
5
5
 
6
+ import { getBlobsContext } from '../../lib/blobs/blobs.mjs'
6
7
  import { promptEditorHelper } from '../../lib/edge-functions/editor-helper.mjs'
7
8
  import { startFunctionsServer } from '../../lib/functions/server.mjs'
8
9
  import { printBanner } from '../../utils/banner.mjs'
@@ -161,8 +162,15 @@ const dev = async (options, command) => {
161
162
  },
162
163
  })
163
164
 
165
+ const blobsContext = await getBlobsContext({
166
+ debug: options.debug,
167
+ projectRoot: command.workingDir,
168
+ siteID: site.id ?? 'unknown-site-id',
169
+ })
170
+
164
171
  const functionsRegistry = await startFunctionsServer({
165
172
  api,
173
+ blobsContext,
166
174
  command,
167
175
  config,
168
176
  debug: options.debug,
@@ -202,6 +210,7 @@ const dev = async (options, command) => {
202
210
 
203
211
  await startProxyServer({
204
212
  addonsUrls,
213
+ blobsContext,
205
214
  config,
206
215
  configPath: configPathOverride,
207
216
  debug: options.debug,
@@ -0,0 +1,50 @@
1
+ import path from 'path'
2
+
3
+ import { BlobsServer } from '@netlify/blobs'
4
+ import { v4 as uuidv4 } from 'uuid'
5
+
6
+ import { getPathInProject } from '../settings.mjs'
7
+
8
+ /**
9
+ * @typedef BlobsContext
10
+ * @type {object}
11
+ * @property {string} edgeURL
12
+ * @property {string} deployID
13
+ * @property {string} siteID
14
+ * @property {string} token
15
+ */
16
+
17
+ /**
18
+ * Starts a local Blobs server and returns a context object that lets functions
19
+ * connect to it.
20
+ *
21
+ * @param {object} options
22
+ * @param {boolean} options.debug
23
+ * @param {string} options.projectRoot
24
+ * @param {string} options.siteID
25
+ * @returns {Promise<BlobsContext>}
26
+ */
27
+ export const getBlobsContext = async ({ debug, projectRoot, siteID }) => {
28
+ const token = uuidv4()
29
+ const { port } = await startBlobsServer({ debug, projectRoot, token })
30
+ const context = {
31
+ deployID: '0',
32
+ edgeURL: `http://localhost:${port}`,
33
+ siteID,
34
+ token,
35
+ }
36
+
37
+ return context
38
+ }
39
+
40
+ const startBlobsServer = async ({ debug, projectRoot, token }) => {
41
+ const directory = path.resolve(projectRoot, getPathInProject(['blobs']))
42
+ const server = new BlobsServer({
43
+ debug,
44
+ directory,
45
+ token,
46
+ })
47
+ const { port } = await server.start()
48
+
49
+ return { port }
50
+ }
@@ -1,5 +1,5 @@
1
1
  import { env } from 'process'
2
2
 
3
- const latestBootstrapURL = 'https://650bfd807b21ed000893e25c--edge.netlify.com/bootstrap/index-combined.ts'
3
+ const latestBootstrapURL = 'https://6539213a19a93a000876a033--edge.netlify.com/bootstrap/index-combined.ts'
4
4
 
5
5
  export const getBootstrapURL = () => env.NETLIFY_EDGE_BOOTSTRAP || latestBootstrapURL
@@ -2,6 +2,7 @@
2
2
  import { Buffer } from 'buffer'
3
3
 
4
4
  export const headers = {
5
+ BlobsInfo: 'x-nf-blobs-info',
5
6
  DeployID: 'x-nf-deploy-id',
6
7
  FeatureFlags: 'x-nf-feature-flags',
7
8
  ForwardedHost: 'x-forwarded-host',
@@ -66,6 +66,7 @@ export const createAccountInfoHeader = (accountInfo = {}) => {
66
66
  *
67
67
  * @param {object} config
68
68
  * @param {*} config.accountId
69
+ * @param {import("../blobs/blobs.mjs").BlobsContext} config.blobsContext
69
70
  * @param {*} config.config
70
71
  * @param {*} config.configPath
71
72
  * @param {*} config.debug
@@ -85,6 +86,7 @@ export const createAccountInfoHeader = (accountInfo = {}) => {
85
86
  */
86
87
  export const initializeProxy = async ({
87
88
  accountId,
89
+ blobsContext,
88
90
  config,
89
91
  configPath,
90
92
  debug,
@@ -151,6 +153,12 @@ export const initializeProxy = async ({
151
153
  req.headers[headers.Site] = createSiteInfoHeader(siteInfo)
152
154
  req.headers[headers.Account] = createAccountInfoHeader({ id: accountId })
153
155
 
156
+ if (blobsContext?.edgeURL && blobsContext?.token) {
157
+ req.headers[headers.BlobsInfo] = Buffer.from(
158
+ JSON.stringify({ url: blobsContext.edgeURL, token: blobsContext.token }),
159
+ ).toString('base64')
160
+ }
161
+
154
162
  await registry.initialize()
155
163
 
156
164
  const url = new URL(req.url, `http://${LOCAL_HOST}:${mainPort}`)
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ import { Buffer } from 'buffer'
2
3
  import { basename, extname } from 'path'
3
4
  import { version as nodeVersion } from 'process'
4
5
 
@@ -23,6 +24,7 @@ const getNextRun = function (schedule) {
23
24
 
24
25
  export default class NetlifyFunction {
25
26
  constructor({
27
+ blobsContext,
26
28
  config,
27
29
  directory,
28
30
  displayName,
@@ -34,6 +36,7 @@ export default class NetlifyFunction {
34
36
  timeoutBackground,
35
37
  timeoutSynchronous,
36
38
  }) {
39
+ this.blobsContext = blobsContext
37
40
  this.buildError = null
38
41
  this.config = config
39
42
  this.directory = directory
@@ -181,7 +184,7 @@ export default class NetlifyFunction {
181
184
  }
182
185
 
183
186
  // Invokes the function and returns its response object.
184
- async invoke(event, context) {
187
+ async invoke(event, context = {}) {
185
188
  await this.buildQueue
186
189
 
187
190
  if (this.buildError) {
@@ -189,10 +192,24 @@ export default class NetlifyFunction {
189
192
  }
190
193
 
191
194
  const timeout = this.isBackground ? this.timeoutBackground : this.timeoutSynchronous
195
+ const environment = {}
196
+
197
+ if (this.blobsContext) {
198
+ const payload = JSON.stringify({
199
+ url: this.blobsContext.edgeURL,
200
+ token: this.blobsContext.token,
201
+ })
202
+
203
+ context.custom = {
204
+ ...context?.custom,
205
+ blobs: Buffer.from(payload).toString('base64'),
206
+ }
207
+ }
192
208
 
193
209
  try {
194
210
  const result = await this.runtime.invokeFunction({
195
211
  context,
212
+ environment,
196
213
  event,
197
214
  func: this,
198
215
  timeout,
@@ -34,6 +34,7 @@ const ZIP_EXTENSION = '.zip'
34
34
 
35
35
  export class FunctionsRegistry {
36
36
  constructor({
37
+ blobsContext,
37
38
  capabilities,
38
39
  config,
39
40
  debug = false,
@@ -52,6 +53,13 @@ export class FunctionsRegistry {
52
53
  this.timeouts = timeouts
53
54
  this.settings = settings
54
55
 
56
+ /**
57
+ * Context object for Netlify Blobs
58
+ *
59
+ * @type {import("../blobs/blobs.mjs").BlobsContext}
60
+ */
61
+ this.blobsContext = blobsContext
62
+
55
63
  /**
56
64
  * An object to be shared among all functions in the registry. It can be
57
65
  * used to cache the results of the build function — e.g. it's used in
@@ -493,6 +501,7 @@ export class FunctionsRegistry {
493
501
  }
494
502
 
495
503
  const func = new NetlifyFunction({
504
+ blobsContext: this.blobsContext,
496
505
  config: this.config,
497
506
  directory: directories.find((directory) => mainFile.startsWith(directory)),
498
507
  mainFile,
@@ -51,13 +51,14 @@ export const getBuildFunction = async ({ config, directory, errorExit, func, pro
51
51
 
52
52
  const workerURL = new URL('worker.mjs', import.meta.url)
53
53
 
54
- export const invokeFunction = async ({ context, event, func, timeout }) => {
54
+ export const invokeFunction = async ({ context, environment, event, func, timeout }) => {
55
55
  if (func.buildData.runtimeAPIVersion !== 2) {
56
56
  return await invokeFunctionDirectly({ context, event, func, timeout })
57
57
  }
58
58
 
59
59
  const workerData = {
60
60
  clientContext: JSON.stringify(context),
61
+ environment,
61
62
  event,
62
63
  // If a function builder has defined a `buildPath` property, we use it.
63
64
  // Otherwise, we'll invoke the function's main file.
@@ -1,4 +1,5 @@
1
1
  import { createServer } from 'net'
2
+ import process from 'process'
2
3
  import { isMainThread, workerData, parentPort } from 'worker_threads'
3
4
 
4
5
  import { isStream } from 'is-stream'
@@ -13,7 +14,12 @@ sourceMapSupport.install()
13
14
 
14
15
  lambdaLocal.getLogger().level = 'alert'
15
16
 
16
- const { clientContext, entryFilePath, event, timeoutMs } = workerData
17
+ const { clientContext, entryFilePath, environment = {}, event, timeoutMs } = workerData
18
+
19
+ // Injecting into the environment any properties passed in by the parent.
20
+ for (const key in environment) {
21
+ process.env[key] = environment[key]
22
+ }
17
23
 
18
24
  const lambdaFunc = await import(entryFilePath)
19
25
 
@@ -137,6 +137,7 @@ export const createHandler = function (options) {
137
137
  'client-ip': [remoteAddress],
138
138
  'x-nf-client-connection-ip': [remoteAddress],
139
139
  'x-nf-account-id': [options.accountId],
140
+ 'x-nf-site-id': [options?.siteInfo?.id],
140
141
  [efHeaders.Geo]: Buffer.from(JSON.stringify(geoLocation)).toString('base64'),
141
142
  }).reduce((prev, [key, value]) => ({ ...prev, [key]: Array.isArray(value) ? value : [value] }), {})
142
143
  const rawQuery = new URLSearchParams(requestQuery).toString()
@@ -245,6 +246,7 @@ const getFunctionsServer = (options) => {
245
246
  /**
246
247
  *
247
248
  * @param {object} options
249
+ * @param {import("../blobs/blobs.mjs").BlobsContext} options.blobsContext
248
250
  * @param {import('../../commands/base-command.mjs').default} options.command
249
251
  * @param {*} options.capabilities
250
252
  * @param {*} options.config
@@ -258,8 +260,19 @@ const getFunctionsServer = (options) => {
258
260
  * @returns {Promise<import('./registry.mjs').FunctionsRegistry | undefined>}
259
261
  */
260
262
  export const startFunctionsServer = async (options) => {
261
- const { capabilities, command, config, debug, loadDistFunctions, settings, site, siteInfo, siteUrl, timeouts } =
262
- options
263
+ const {
264
+ blobsContext,
265
+ capabilities,
266
+ command,
267
+ config,
268
+ debug,
269
+ loadDistFunctions,
270
+ settings,
271
+ site,
272
+ siteInfo,
273
+ siteUrl,
274
+ timeouts,
275
+ } = options
263
276
  const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root })
264
277
  const functionsDirectories = []
265
278
  let manifest
@@ -306,6 +319,7 @@ export const startFunctionsServer = async (options) => {
306
319
  }
307
320
 
308
321
  const functionsRegistry = new FunctionsRegistry({
322
+ blobsContext,
309
323
  capabilities,
310
324
  config,
311
325
  debug,
@@ -38,6 +38,7 @@ export const generateInspectSettings = (edgeInspect, edgeInspectBrk) => {
38
38
  * @param {object} params
39
39
  * @param {string=} params.accountId
40
40
  * @param {*} params.addonsUrls
41
+ * @param {import("../lib/blobs/blobs.mjs").BlobsContext} blobsContext
41
42
  * @param {import('../commands/types.js').NetlifyOptions["config"]} params.config
42
43
  * @param {string} [params.configPath] An override for the Netlify config path
43
44
  * @param {boolean} params.debug
@@ -58,6 +59,7 @@ export const generateInspectSettings = (edgeInspect, edgeInspectBrk) => {
58
59
  export const startProxyServer = async ({
59
60
  accountId,
60
61
  addonsUrls,
62
+ blobsContext,
61
63
  config,
62
64
  configPath,
63
65
  debug,
@@ -76,6 +78,7 @@ export const startProxyServer = async ({
76
78
  }) => {
77
79
  const url = await startProxy({
78
80
  addonsUrls,
81
+ blobsContext,
79
82
  config,
80
83
  configPath: configPath || site.configPath,
81
84
  debug,
@@ -675,6 +675,7 @@ export const getProxyUrl = function (settings) {
675
675
  export const startProxy = async function ({
676
676
  accountId,
677
677
  addonsUrls,
678
+ blobsContext,
678
679
  config,
679
680
  configPath,
680
681
  debug,
@@ -693,6 +694,7 @@ export const startProxy = async function ({
693
694
  const secondaryServerPort = settings.https ? await getAvailablePort() : null
694
695
  const functionsServer = settings.functionsPort ? `http://127.0.0.1:${settings.functionsPort}` : null
695
696
  const edgeFunctionsProxy = await initializeEdgeFunctionsProxy({
697
+ blobsContext,
696
698
  config,
697
699
  configPath,
698
700
  debug,