netlify-cli 17.0.1 → 17.2.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/README.md +1 -15
- package/npm-shrinkwrap.json +2470 -1223
- package/package.json +5 -4
- package/src/commands/addons/addons-auth.mjs +1 -1
- package/src/commands/addons/addons-config.mjs +1 -1
- package/src/commands/addons/addons-create.mjs +1 -1
- package/src/commands/addons/addons-delete.mjs +1 -1
- package/src/commands/addons/addons-list.mjs +1 -1
- package/src/commands/addons/addons.mjs +2 -2
- package/src/commands/dev/dev.mjs +1 -0
- package/src/commands/functions/functions-serve.mjs +1 -1
- package/src/commands/lm/lm-info.mjs +1 -1
- package/src/commands/lm/lm-setup.mjs +1 -1
- package/src/lib/edge-functions/proxy.mjs +7 -17
- package/src/lib/edge-functions/registry.mjs +179 -110
- package/src/lib/images/proxy.mjs +115 -0
- package/src/utils/lm/install.mjs +1 -1
- package/src/utils/proxy-server.mjs +3 -0
- package/src/utils/proxy.mjs +43 -6
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import { createIPX, ipxFSStorage, ipxHttpStorage, createIPXNodeServer } from 'ipx'
|
|
3
|
+
|
|
4
|
+
import { log, NETLIFYDEVERR } from '../../utils/command-helpers.mjs'
|
|
5
|
+
|
|
6
|
+
export const IMAGE_URL_PATTERN = '/.netlify/images'
|
|
7
|
+
|
|
8
|
+
export const parseAllDomains = function (config) {
|
|
9
|
+
const domains = config?.images?.remote_images
|
|
10
|
+
if (!domains) {
|
|
11
|
+
return { errors: [], remoteDomains: [] }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const remoteDomains = []
|
|
15
|
+
const errors = []
|
|
16
|
+
|
|
17
|
+
for (const patternString of domains) {
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(patternString)
|
|
20
|
+
if (url.hostname) {
|
|
21
|
+
remoteDomains.push(url.hostname)
|
|
22
|
+
} else {
|
|
23
|
+
errors.push(`The URL '${patternString}' does not have a valid hostname.`)
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
errors.push(`Invalid URL '${patternString}': ${error.message}`)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { errors, remoteDomains }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const getErrorMessage = function ({ message }) {
|
|
34
|
+
return message
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const handleImageDomainsErrors = async function (errors) {
|
|
38
|
+
if (errors.length === 0) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const errorMessage = await errors.map(getErrorMessage).join('\n\n')
|
|
43
|
+
log(NETLIFYDEVERR, `Image domains syntax errors:\n${errorMessage}`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const parseRemoteImageDomains = async function ({ config }) {
|
|
47
|
+
if (!config) {
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { errors, remoteDomains } = await parseAllDomains(config)
|
|
52
|
+
await handleImageDomainsErrors(errors)
|
|
53
|
+
|
|
54
|
+
return remoteDomains
|
|
55
|
+
}
|
|
56
|
+
export const isImageRequest = function (req) {
|
|
57
|
+
return req.url.startsWith(IMAGE_URL_PATTERN)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const transformImageParams = function (query) {
|
|
61
|
+
const params = {}
|
|
62
|
+
// eslint-disable-next-line id-length
|
|
63
|
+
params.w = query.w || query.width || null
|
|
64
|
+
// eslint-disable-next-line id-length
|
|
65
|
+
params.h = query.h || query.height || null
|
|
66
|
+
params.quality = query.q || query.quality || null
|
|
67
|
+
params.format = query.fm || null
|
|
68
|
+
params.fit = mapImgixToFitIpx(query.fit, query.crop)
|
|
69
|
+
params.position = query.crop || null
|
|
70
|
+
|
|
71
|
+
return Object.entries(params)
|
|
72
|
+
.filter(([, value]) => value !== null)
|
|
73
|
+
.map(([key, value]) => `${key}_${value}`)
|
|
74
|
+
.join(',')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function mapImgixToFitIpx(fit, crop) {
|
|
78
|
+
if (crop) {
|
|
79
|
+
return 'cover'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const fitMapping = {
|
|
83
|
+
// IPX doesn't have exact equivalent.
|
|
84
|
+
clamp: null,
|
|
85
|
+
clip: 'contain',
|
|
86
|
+
crop: 'cover',
|
|
87
|
+
max: 'inside',
|
|
88
|
+
min: 'outside',
|
|
89
|
+
scale: 'fill',
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return fitMapping[fit] ?? 'contain'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const initializeProxy = async function ({ config }) {
|
|
96
|
+
const remoteDomains = await parseRemoteImageDomains({ config })
|
|
97
|
+
|
|
98
|
+
const ipx = createIPX({
|
|
99
|
+
storage: ipxFSStorage({ dir: config?.build?.publish ?? './public' }),
|
|
100
|
+
httpStorage: ipxHttpStorage({ domains: remoteDomains }),
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const handler = createIPXNodeServer(ipx)
|
|
104
|
+
const app = express()
|
|
105
|
+
|
|
106
|
+
app.use(IMAGE_URL_PATTERN, async (req, res) => {
|
|
107
|
+
const { url, ...query } = req.query
|
|
108
|
+
const modifiers = await transformImageParams(query)
|
|
109
|
+
const path = `/${modifiers}/${encodeURIComponent(url)}`
|
|
110
|
+
req.url = path
|
|
111
|
+
handler(req, res)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return app
|
|
115
|
+
}
|
package/src/utils/lm/install.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url'
|
|
|
7
7
|
|
|
8
8
|
import execa from 'execa'
|
|
9
9
|
import hasbin from 'hasbin'
|
|
10
|
-
import Listr from '
|
|
10
|
+
import { Listr } from 'listr2'
|
|
11
11
|
import pathKey from 'path-key'
|
|
12
12
|
|
|
13
13
|
import { fetchLatestVersion, shouldFetchLatestVersion } from '../../lib/exec-fetcher.mjs'
|
|
@@ -52,6 +52,7 @@ export const generateInspectSettings = (edgeInspect, edgeInspectBrk) => {
|
|
|
52
52
|
* @param {object} params.site
|
|
53
53
|
* @param {*} params.siteInfo
|
|
54
54
|
* @param {string} params.projectDir
|
|
55
|
+
* @param {string} params.repositoryRoot
|
|
55
56
|
* @param {import('./state-config.mjs').default} params.state
|
|
56
57
|
* @param {import('../lib/functions/registry.mjs').FunctionsRegistry=} params.functionsRegistry
|
|
57
58
|
* @returns
|
|
@@ -71,6 +72,7 @@ export const startProxyServer = async ({
|
|
|
71
72
|
inspectSettings,
|
|
72
73
|
offline,
|
|
73
74
|
projectDir,
|
|
75
|
+
repositoryRoot,
|
|
74
76
|
settings,
|
|
75
77
|
site,
|
|
76
78
|
siteInfo,
|
|
@@ -94,6 +96,7 @@ export const startProxyServer = async ({
|
|
|
94
96
|
state,
|
|
95
97
|
siteInfo,
|
|
96
98
|
accountId,
|
|
99
|
+
repositoryRoot,
|
|
97
100
|
})
|
|
98
101
|
if (!url) {
|
|
99
102
|
log(NETLIFYDEVERR, `Unable to start proxy server on port '${settings.port}'`)
|
package/src/utils/proxy.mjs
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '../lib/edge-functions/proxy.mjs'
|
|
29
29
|
import { fileExistsAsync, isFileAsync } from '../lib/fs.mjs'
|
|
30
30
|
import { DEFAULT_FUNCTION_URL_EXPRESSION } from '../lib/functions/registry.mjs'
|
|
31
|
+
import { initializeProxy as initializeImageProxy, isImageRequest } from '../lib/images/proxy.mjs'
|
|
31
32
|
import renderErrorTemplate from '../lib/render-error-template.mjs'
|
|
32
33
|
|
|
33
34
|
import { NETLIFYDEVLOG, NETLIFYDEVWARN, log, chalk } from './command-helpers.mjs'
|
|
@@ -182,7 +183,17 @@ const alternativePathsFor = function (url) {
|
|
|
182
183
|
return paths
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
const serveRedirect = async function ({
|
|
186
|
+
const serveRedirect = async function ({
|
|
187
|
+
env,
|
|
188
|
+
functionsRegistry,
|
|
189
|
+
imageProxy,
|
|
190
|
+
match,
|
|
191
|
+
options,
|
|
192
|
+
proxy,
|
|
193
|
+
req,
|
|
194
|
+
res,
|
|
195
|
+
siteInfo,
|
|
196
|
+
}) {
|
|
186
197
|
if (!match) return proxy.web(req, res, options)
|
|
187
198
|
|
|
188
199
|
options = options || req.proxyOptions || {}
|
|
@@ -355,7 +366,9 @@ const serveRedirect = async function ({ env, functionsRegistry, match, options,
|
|
|
355
366
|
|
|
356
367
|
return proxy.web(req, res, { headers: functionHeaders, target: options.functionsServer })
|
|
357
368
|
}
|
|
358
|
-
|
|
369
|
+
if (isImageRequest(req)) {
|
|
370
|
+
return imageProxy(req, res)
|
|
371
|
+
}
|
|
359
372
|
const addonUrl = getAddonUrl(options.addonsUrls, req)
|
|
360
373
|
if (addonUrl) {
|
|
361
374
|
return handleAddonUrl({ req, res, addonUrl })
|
|
@@ -378,7 +391,7 @@ const reqToURL = function (req, pathname) {
|
|
|
378
391
|
|
|
379
392
|
const MILLISEC_TO_SEC = 1e3
|
|
380
393
|
|
|
381
|
-
const initializeProxy = async function ({ configPath, distDir, env, host, port, projectDir, siteInfo }) {
|
|
394
|
+
const initializeProxy = async function ({ configPath, distDir, env, host, imageProxy, port, projectDir, siteInfo }) {
|
|
382
395
|
const proxy = httpProxy.createProxyServer({
|
|
383
396
|
selfHandleResponse: true,
|
|
384
397
|
target: {
|
|
@@ -386,7 +399,6 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
|
|
|
386
399
|
port,
|
|
387
400
|
},
|
|
388
401
|
})
|
|
389
|
-
|
|
390
402
|
const headersFiles = [...new Set([path.resolve(projectDir, '_headers'), path.resolve(distDir, '_headers')])]
|
|
391
403
|
|
|
392
404
|
let headers = await parseHeaders({ headersFiles, configPath })
|
|
@@ -466,6 +478,7 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
|
|
|
466
478
|
req,
|
|
467
479
|
res,
|
|
468
480
|
proxy: handlers,
|
|
481
|
+
imageProxy,
|
|
469
482
|
match: req.proxyOptions.match,
|
|
470
483
|
options: req.proxyOptions,
|
|
471
484
|
siteInfo,
|
|
@@ -484,6 +497,7 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
|
|
|
484
497
|
req,
|
|
485
498
|
res,
|
|
486
499
|
proxy: handlers,
|
|
500
|
+
imageProxy,
|
|
487
501
|
match: null,
|
|
488
502
|
options: req.proxyOptions,
|
|
489
503
|
siteInfo,
|
|
@@ -586,7 +600,18 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
|
|
|
586
600
|
}
|
|
587
601
|
|
|
588
602
|
const onRequest = async (
|
|
589
|
-
{
|
|
603
|
+
{
|
|
604
|
+
addonsUrls,
|
|
605
|
+
edgeFunctionsProxy,
|
|
606
|
+
env,
|
|
607
|
+
functionsRegistry,
|
|
608
|
+
functionsServer,
|
|
609
|
+
imageProxy,
|
|
610
|
+
proxy,
|
|
611
|
+
rewriter,
|
|
612
|
+
settings,
|
|
613
|
+
siteInfo,
|
|
614
|
+
},
|
|
590
615
|
req,
|
|
591
616
|
res,
|
|
592
617
|
) => {
|
|
@@ -642,7 +667,7 @@ const onRequest = async (
|
|
|
642
667
|
// We don't want to generate an ETag for 3xx redirects.
|
|
643
668
|
req[shouldGenerateETag] = ({ statusCode }) => statusCode < 300 || statusCode >= 400
|
|
644
669
|
|
|
645
|
-
return serveRedirect({ req, res, proxy, match, options, siteInfo, env, functionsRegistry })
|
|
670
|
+
return serveRedirect({ req, res, proxy, imageProxy, match, options, siteInfo, env, functionsRegistry })
|
|
646
671
|
}
|
|
647
672
|
|
|
648
673
|
// The request will be served by the framework server, which means we want to
|
|
@@ -660,6 +685,10 @@ const onRequest = async (
|
|
|
660
685
|
return proxy.web(req, res, { target: functionsServer })
|
|
661
686
|
}
|
|
662
687
|
|
|
688
|
+
if (isImageRequest(req)) {
|
|
689
|
+
return imageProxy(req, res)
|
|
690
|
+
}
|
|
691
|
+
|
|
663
692
|
proxy.web(req, res, options)
|
|
664
693
|
}
|
|
665
694
|
|
|
@@ -687,6 +716,7 @@ export const startProxy = async function ({
|
|
|
687
716
|
inspectSettings,
|
|
688
717
|
offline,
|
|
689
718
|
projectDir,
|
|
719
|
+
repositoryRoot,
|
|
690
720
|
settings,
|
|
691
721
|
siteInfo,
|
|
692
722
|
state,
|
|
@@ -708,10 +738,15 @@ export const startProxy = async function ({
|
|
|
708
738
|
passthroughPort: secondaryServerPort || settings.port,
|
|
709
739
|
settings,
|
|
710
740
|
projectDir,
|
|
741
|
+
repositoryRoot,
|
|
711
742
|
siteInfo,
|
|
712
743
|
accountId,
|
|
713
744
|
state,
|
|
714
745
|
})
|
|
746
|
+
|
|
747
|
+
const imageProxy = await initializeImageProxy({
|
|
748
|
+
config,
|
|
749
|
+
})
|
|
715
750
|
const proxy = await initializeProxy({
|
|
716
751
|
env,
|
|
717
752
|
host: settings.frameworkHost,
|
|
@@ -720,6 +755,7 @@ export const startProxy = async function ({
|
|
|
720
755
|
projectDir,
|
|
721
756
|
configPath,
|
|
722
757
|
siteInfo,
|
|
758
|
+
imageProxy,
|
|
723
759
|
})
|
|
724
760
|
|
|
725
761
|
const rewriter = await createRewriter({
|
|
@@ -739,6 +775,7 @@ export const startProxy = async function ({
|
|
|
739
775
|
functionsRegistry,
|
|
740
776
|
functionsServer,
|
|
741
777
|
edgeFunctionsProxy,
|
|
778
|
+
imageProxy,
|
|
742
779
|
siteInfo,
|
|
743
780
|
env,
|
|
744
781
|
})
|