@netlify/build 29.38.1 → 29.39.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/lib/core/feature_flags.js +1 -0
- package/lib/plugins/compatibility.d.ts +7 -1
- package/lib/plugins/compatibility.js +54 -7
- package/lib/plugins/compatibility.test.js +286 -165
- package/lib/plugins/expected_version.d.ts +2 -1
- package/lib/plugins/expected_version.js +25 -4
- package/lib/plugins/resolve.js +1 -0
- package/lib/plugins_core/blobs_upload/index.d.ts +1 -1
- package/lib/plugins_core/blobs_upload/index.js +10 -14
- package/lib/plugins_core/dev_blobs_upload/index.d.ts +2 -0
- package/lib/plugins_core/dev_blobs_upload/index.js +75 -0
- package/lib/steps/get.js +2 -1
- package/lib/utils/blobs.d.ts +8 -0
- package/lib/utils/blobs.js +49 -5
- package/package.json +5 -5
- package/lib/plugins_core/blobs_upload/utils.d.ts +0 -8
- package/lib/plugins_core/blobs_upload/utils.js +0 -46
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { PackageJson } from 'read-pkg-up';
|
|
2
|
+
import { FeatureFlags } from '../core/feature_flags.js';
|
|
3
|
+
import { SystemLogger } from '../plugins_core/types.js';
|
|
2
4
|
import { PluginVersion } from './list.js';
|
|
3
5
|
/**
|
|
4
6
|
* Retrieve the `expectedVersion` of a plugin:
|
|
@@ -11,14 +13,18 @@ import { PluginVersion } from './list.js';
|
|
|
11
13
|
* - This is only used to print a warning message when the `compatibleVersion`
|
|
12
14
|
* is older than the currently used version.
|
|
13
15
|
*/
|
|
14
|
-
export declare const getExpectedVersion: ({ versions, nodeVersion, packageJson, packagePath, buildDir, pinnedVersion, }: {
|
|
16
|
+
export declare const getExpectedVersion: ({ versions, nodeVersion, packageJson, packageName, packagePath, buildDir, pinnedVersion, featureFlags, systemLog, authoritative, }: {
|
|
15
17
|
versions: PluginVersion[];
|
|
16
18
|
/** The package.json of the repository */
|
|
17
19
|
packageJson: PackageJson;
|
|
20
|
+
packageName: string;
|
|
18
21
|
packagePath?: string | undefined;
|
|
19
22
|
buildDir: string;
|
|
20
23
|
nodeVersion: string;
|
|
21
24
|
pinnedVersion?: string | undefined;
|
|
25
|
+
featureFlags?: FeatureFlags | undefined;
|
|
26
|
+
systemLog: SystemLogger;
|
|
27
|
+
authoritative?: boolean | undefined;
|
|
22
28
|
}) => Promise<{
|
|
23
29
|
version: string;
|
|
24
30
|
compatWarning: string;
|
|
@@ -15,14 +15,17 @@ const pEvery = _pEvery;
|
|
|
15
15
|
* - This is only used to print a warning message when the `compatibleVersion`
|
|
16
16
|
* is older than the currently used version.
|
|
17
17
|
*/
|
|
18
|
-
export const getExpectedVersion = async function ({ versions, nodeVersion, packageJson, packagePath, buildDir, pinnedVersion, }) {
|
|
18
|
+
export const getExpectedVersion = async function ({ versions, nodeVersion, packageJson, packageName, packagePath, buildDir, pinnedVersion, featureFlags, systemLog, authoritative, }) {
|
|
19
19
|
const { version, conditions = [] } = await getCompatibleEntry({
|
|
20
20
|
versions,
|
|
21
21
|
nodeVersion,
|
|
22
22
|
packageJson,
|
|
23
|
+
packageName,
|
|
23
24
|
packagePath,
|
|
24
25
|
buildDir,
|
|
25
26
|
pinnedVersion,
|
|
27
|
+
featureFlags,
|
|
28
|
+
systemLog: authoritative ? systemLog : undefined,
|
|
26
29
|
});
|
|
27
30
|
// Retrieve warning message shown when using an older version with `compatibility`
|
|
28
31
|
const compatWarning = conditions.map(({ type, condition }) => CONDITIONS[type].warning(condition)).join(', ');
|
|
@@ -44,12 +47,19 @@ export const getExpectedVersion = async function ({ versions, nodeVersion, packa
|
|
|
44
47
|
* - If there is a `pinnedVersion`, use it unless `latestVersion` matches it
|
|
45
48
|
* - Otherwise, use `latestVersion`
|
|
46
49
|
*/
|
|
47
|
-
const getCompatibleEntry = async function ({ versions, nodeVersion, packageJson, packagePath, buildDir, pinnedVersion,
|
|
50
|
+
const getCompatibleEntry = async function ({ versions, nodeVersion, packageJson, packageName, packagePath, buildDir, pinnedVersion, featureFlags, systemLog = () => {
|
|
51
|
+
// no-op
|
|
52
|
+
}, }) {
|
|
48
53
|
const compatibleEntry = await pLocate(versions, async ({ version, overridePinnedVersion, conditions }) => {
|
|
49
|
-
//
|
|
54
|
+
// When there's a `pinnedVersion`, we typically pick the first version that
|
|
55
|
+
// matches that range. The exception is if `overridePinnedVersion` is also
|
|
56
|
+
// present. This property says that if the pinned version is within a given
|
|
57
|
+
// range, the entry that has this property can be used instead, even if its
|
|
58
|
+
// own version doesn't satisfy the pinned version.
|
|
50
59
|
const overridesPin = Boolean(pinnedVersion && overridePinnedVersion && semver.intersects(overridePinnedVersion, pinnedVersion));
|
|
51
|
-
//
|
|
52
|
-
|
|
60
|
+
// If there's a pinned version and this entry doesn't satisfy that range,
|
|
61
|
+
// discard it. The exception is if this entry overrides the pinned version.
|
|
62
|
+
if (pinnedVersion && !overridesPin && !semver.satisfies(version, pinnedVersion, { includePrerelease: true })) {
|
|
53
63
|
return false;
|
|
54
64
|
}
|
|
55
65
|
// no conditions means nothing to filter
|
|
@@ -58,6 +68,43 @@ const getCompatibleEntry = async function ({ versions, nodeVersion, packageJson,
|
|
|
58
68
|
}
|
|
59
69
|
return await pEvery(conditions, async ({ type, condition }) => CONDITIONS[type].test(condition, { nodeVersion, packageJson, packagePath, buildDir }));
|
|
60
70
|
});
|
|
61
|
-
|
|
62
|
-
(
|
|
71
|
+
if (compatibleEntry) {
|
|
72
|
+
systemLog(`Used compatible version '${compatibleEntry.version}' for plugin '${packageName}' (pinned version is ${pinnedVersion})`);
|
|
73
|
+
return compatibleEntry;
|
|
74
|
+
}
|
|
75
|
+
if (pinnedVersion) {
|
|
76
|
+
systemLog(`Used pinned version '${pinnedVersion}' for plugin '${packageName}'`);
|
|
77
|
+
return { version: pinnedVersion, conditions: [] };
|
|
78
|
+
}
|
|
79
|
+
const legacyFallback = { version: versions[0].version, conditions: [] };
|
|
80
|
+
const fallback = await getFirstCompatibleEntry({ versions, nodeVersion, packageJson, packagePath, buildDir });
|
|
81
|
+
if (featureFlags?.netlify_build_updated_plugin_compatibility) {
|
|
82
|
+
if (legacyFallback.version !== fallback.version) {
|
|
83
|
+
systemLog(`Detected mismatch in selected version for plugin '${packageName}': used new version of '${fallback.version}' over legacy version '${legacyFallback.version}'`);
|
|
84
|
+
}
|
|
85
|
+
return fallback;
|
|
86
|
+
}
|
|
87
|
+
if (legacyFallback.version !== fallback.version) {
|
|
88
|
+
systemLog(`Detected mismatch in selected version for plugin '${packageName}': used legacy version '${legacyFallback.version}' over new version '${fallback.version}'`);
|
|
89
|
+
}
|
|
90
|
+
return legacyFallback;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Takes a list of plugin versions and returns the first entry that satisfies
|
|
94
|
+
* the conditions (if any), without taking into account the pinned version.
|
|
95
|
+
*/
|
|
96
|
+
const getFirstCompatibleEntry = async function ({ versions, nodeVersion, packageJson, packagePath, buildDir, }) {
|
|
97
|
+
const compatibleEntry = await pLocate(versions, async ({ conditions }) => {
|
|
98
|
+
if (conditions.length === 0) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return await pEvery(conditions, async ({ type, condition }) => CONDITIONS[type].test(condition, { nodeVersion, packageJson, packagePath, buildDir }));
|
|
102
|
+
});
|
|
103
|
+
if (compatibleEntry) {
|
|
104
|
+
return compatibleEntry;
|
|
105
|
+
}
|
|
106
|
+
// We should never get here, because it means there are no plugin versions
|
|
107
|
+
// that we can install. We're keeping this here because it has been the
|
|
108
|
+
// default behavior for a long time, but we should look to remove it.
|
|
109
|
+
return { version: versions[0].version, conditions: [] };
|
|
63
110
|
};
|
|
@@ -1,177 +1,298 @@
|
|
|
1
|
-
import { expect, test } from 'vitest';
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
2
|
import { getExpectedVersion } from './compatibility.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
const noopSystemLog = () => {
|
|
4
|
+
// no-op
|
|
5
|
+
};
|
|
6
|
+
describe(`getExpectedVersion`, () => {
|
|
7
|
+
test('should ignore the new major version if the version is pinned', async () => {
|
|
8
|
+
const versions = [
|
|
9
|
+
{ version: '5.0.0', conditions: [] },
|
|
10
|
+
{ version: '4.41.2', conditions: [] },
|
|
11
|
+
];
|
|
12
|
+
const { version } = await getExpectedVersion({
|
|
13
|
+
versions,
|
|
14
|
+
nodeVersion: '18.19.0',
|
|
15
|
+
packageJson: {},
|
|
16
|
+
packageName: '@netlify/cool-plugin',
|
|
17
|
+
buildDir: '/some/path',
|
|
18
|
+
pinnedVersion: '4',
|
|
19
|
+
systemLog: noopSystemLog,
|
|
20
|
+
});
|
|
21
|
+
expect(version).toBe('4.41.2');
|
|
14
22
|
});
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
{ version:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
test('matches prerelease versions', async () => {
|
|
24
|
+
const versions = [
|
|
25
|
+
{ version: '5.0.0', conditions: [] },
|
|
26
|
+
{ version: '4.42.0-alpha.1', conditions: [] },
|
|
27
|
+
{ version: '4.41.2', conditions: [] },
|
|
28
|
+
];
|
|
29
|
+
const { version: version1 } = await getExpectedVersion({
|
|
30
|
+
versions,
|
|
31
|
+
nodeVersion: '18.19.0',
|
|
32
|
+
packageJson: {},
|
|
33
|
+
packageName: '@netlify/cool-plugin',
|
|
34
|
+
buildDir: '/some/path',
|
|
35
|
+
systemLog: noopSystemLog,
|
|
36
|
+
});
|
|
37
|
+
const { version: version2 } = await getExpectedVersion({
|
|
38
|
+
versions,
|
|
39
|
+
nodeVersion: '18.19.0',
|
|
40
|
+
packageJson: {},
|
|
41
|
+
packageName: '@netlify/cool-plugin',
|
|
42
|
+
buildDir: '/some/path',
|
|
43
|
+
pinnedVersion: '4',
|
|
44
|
+
systemLog: noopSystemLog,
|
|
45
|
+
});
|
|
46
|
+
expect(version1).toBe('5.0.0');
|
|
47
|
+
expect(version2).toBe('4.42.0-alpha.1');
|
|
28
48
|
});
|
|
29
|
-
|
|
30
|
-
versions
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
49
|
+
test('should retrieve a new major version if the overridePinnedVersion is specified', async () => {
|
|
50
|
+
const versions = [
|
|
51
|
+
{ version: '5.0.0', conditions: [], overridePinnedVersion: '>=4.0.0' },
|
|
52
|
+
{ version: '4.41.2', conditions: [] },
|
|
53
|
+
];
|
|
54
|
+
const { version } = await getExpectedVersion({
|
|
55
|
+
versions,
|
|
56
|
+
nodeVersion: '18.19.0',
|
|
57
|
+
packageJson: {},
|
|
58
|
+
packageName: '@netlify/cool-plugin',
|
|
59
|
+
buildDir: '/some/path',
|
|
60
|
+
pinnedVersion: '4',
|
|
61
|
+
systemLog: noopSystemLog,
|
|
62
|
+
});
|
|
63
|
+
expect(version).toBe('5.0.0');
|
|
35
64
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
test('should retrieve the plugin based on the condition of a nodeVersion', async () => {
|
|
66
|
+
const versions = [
|
|
67
|
+
{
|
|
68
|
+
version: '4.42.0',
|
|
69
|
+
conditions: [{ type: 'nodeVersion', condition: '>=18.0.0' }],
|
|
70
|
+
},
|
|
71
|
+
{ version: '4.41.2', conditions: [] },
|
|
72
|
+
];
|
|
73
|
+
const { version } = await getExpectedVersion({
|
|
74
|
+
versions,
|
|
75
|
+
nodeVersion: '17.19.0',
|
|
76
|
+
packageJson: {},
|
|
77
|
+
packageName: '@netlify/cool-plugin',
|
|
78
|
+
buildDir: '/some/path',
|
|
79
|
+
pinnedVersion: '4',
|
|
80
|
+
systemLog: noopSystemLog,
|
|
81
|
+
});
|
|
82
|
+
expect(version).toBe('4.41.2');
|
|
50
83
|
});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
84
|
+
test('should retrieve the plugin based on conditions and feature flag due to pinned version', async () => {
|
|
85
|
+
const versions = [
|
|
86
|
+
{
|
|
87
|
+
version: '5.0.0-beta.1',
|
|
88
|
+
conditions: [
|
|
89
|
+
{ type: 'nodeVersion', condition: '>= 18.0.0' },
|
|
90
|
+
{ type: 'siteDependencies', condition: { next: '>=13.5.0' } },
|
|
91
|
+
],
|
|
92
|
+
overridePinnedVersion: '>=4.0.0',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
version: '4.42.0',
|
|
96
|
+
conditions: [{ type: 'siteDependencies', condition: { next: '>=10.0.9' } }],
|
|
97
|
+
},
|
|
98
|
+
{ version: '4.41.2', conditions: [] },
|
|
99
|
+
{
|
|
100
|
+
version: '3.9.2',
|
|
101
|
+
conditions: [{ type: 'siteDependencies', condition: { next: '<10.0.9' } }],
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
const { version: version1 } = await getExpectedVersion({
|
|
105
|
+
versions,
|
|
106
|
+
nodeVersion: '17.19.0',
|
|
107
|
+
packageJson: { dependencies: { next: '10.0.8' } },
|
|
108
|
+
packageName: '@netlify/cool-plugin',
|
|
109
|
+
buildDir: '/some/path',
|
|
110
|
+
pinnedVersion: '3',
|
|
111
|
+
systemLog: noopSystemLog,
|
|
112
|
+
});
|
|
113
|
+
expect(version1).toBe('3.9.2');
|
|
114
|
+
const { version: version2 } = await getExpectedVersion({
|
|
115
|
+
versions,
|
|
116
|
+
nodeVersion: '17.19.0',
|
|
117
|
+
packageJson: { dependencies: { next: '11.0.0' } },
|
|
118
|
+
packageName: '@netlify/cool-plugin',
|
|
119
|
+
buildDir: '/some/path',
|
|
120
|
+
pinnedVersion: '4',
|
|
121
|
+
systemLog: noopSystemLog,
|
|
122
|
+
});
|
|
123
|
+
expect(version2).toBe('4.42.0');
|
|
124
|
+
const { version: version3 } = await getExpectedVersion({
|
|
125
|
+
versions,
|
|
126
|
+
nodeVersion: '18.19.0',
|
|
127
|
+
packageJson: { dependencies: { next: '13.5.0' } },
|
|
128
|
+
packageName: '@netlify/cool-plugin',
|
|
129
|
+
buildDir: '/some/path',
|
|
130
|
+
pinnedVersion: '4',
|
|
131
|
+
systemLog: noopSystemLog,
|
|
132
|
+
});
|
|
133
|
+
expect(version3).toBe('5.0.0-beta.1');
|
|
67
134
|
});
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
135
|
+
test('should work with rc versions inside the siteDependencies constraints', async () => {
|
|
136
|
+
const versions = [
|
|
137
|
+
{
|
|
138
|
+
version: '5.0.0-beta.1',
|
|
139
|
+
conditions: [
|
|
140
|
+
{ type: 'nodeVersion', condition: '>= 18.0.0' },
|
|
141
|
+
{ type: 'siteDependencies', condition: { next: '>=13.5.0' } },
|
|
142
|
+
],
|
|
143
|
+
overridePinnedVersion: '>=4.0.0',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
version: '4.42.0',
|
|
147
|
+
conditions: [{ type: 'siteDependencies', condition: { next: '>=10.0.9' } }],
|
|
148
|
+
},
|
|
149
|
+
{ version: '4.41.2', conditions: [] },
|
|
150
|
+
{
|
|
151
|
+
version: '3.9.2',
|
|
152
|
+
conditions: [{ type: 'siteDependencies', condition: { next: '<10.0.9' } }],
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
const { version } = await getExpectedVersion({
|
|
156
|
+
versions,
|
|
157
|
+
nodeVersion: '18.19.0',
|
|
158
|
+
packageJson: { dependencies: { next: '14.1.1-canary.36' } },
|
|
159
|
+
packageName: '@netlify/cool-plugin',
|
|
160
|
+
buildDir: '/some/path',
|
|
161
|
+
pinnedVersion: '4',
|
|
162
|
+
systemLog: noopSystemLog,
|
|
163
|
+
});
|
|
164
|
+
expect(version).toBe('5.0.0-beta.1');
|
|
96
165
|
});
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
166
|
+
test('should retrieve the plugin based on conditions and feature flag due to pinned version', async () => {
|
|
167
|
+
const versions = [
|
|
168
|
+
{
|
|
169
|
+
version: '5.0.0-beta.1',
|
|
170
|
+
conditions: [
|
|
171
|
+
{ type: 'nodeVersion', condition: '>= 18.0.0' },
|
|
172
|
+
{ type: 'siteDependencies', condition: { next: '>=13.5.0' } },
|
|
173
|
+
],
|
|
174
|
+
overridePinnedVersion: '>=4.0.0',
|
|
175
|
+
},
|
|
176
|
+
{ version: '4.41.2', conditions: [] },
|
|
177
|
+
{
|
|
178
|
+
version: '3.9.2',
|
|
179
|
+
conditions: [{ type: 'siteDependencies', condition: { next: '<10.0.9' } }],
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
const { version: version1 } = await getExpectedVersion({
|
|
183
|
+
versions,
|
|
184
|
+
nodeVersion: '20.0.0',
|
|
185
|
+
packageJson: { dependencies: { next: '14.0.0' } },
|
|
186
|
+
packageName: '@netlify/cool-plugin',
|
|
187
|
+
buildDir: '/some/path',
|
|
188
|
+
pinnedVersion: '4',
|
|
189
|
+
systemLog: noopSystemLog,
|
|
190
|
+
});
|
|
191
|
+
expect(version1).toBe('5.0.0-beta.1');
|
|
192
|
+
// out of range
|
|
193
|
+
const { version: version2 } = await getExpectedVersion({
|
|
194
|
+
versions,
|
|
195
|
+
nodeVersion: '20.0.0',
|
|
196
|
+
packageJson: { dependencies: { next: '13.0.0' } },
|
|
197
|
+
packageName: '@netlify/cool-plugin',
|
|
198
|
+
buildDir: '/some/path',
|
|
199
|
+
pinnedVersion: '4',
|
|
200
|
+
systemLog: noopSystemLog,
|
|
201
|
+
});
|
|
202
|
+
expect(version2).toBe('4.41.2');
|
|
104
203
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
204
|
+
test('matches the first entry that satisfies the constraints, even if it also matches another entry further down with more specific constraints', async () => {
|
|
205
|
+
const versions = [
|
|
206
|
+
{ version: '4.41.2', conditions: [] },
|
|
207
|
+
{
|
|
208
|
+
version: '5.0.0-beta.1',
|
|
209
|
+
conditions: [
|
|
210
|
+
{ type: 'nodeVersion', condition: '>= 18.0.0' },
|
|
211
|
+
{ type: 'siteDependencies', condition: { next: '>=13.5.0' } },
|
|
212
|
+
],
|
|
213
|
+
overridePinnedVersion: '>=4.0.0',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
version: '3.9.2',
|
|
217
|
+
conditions: [{ type: 'siteDependencies', condition: { next: '<10.0.9' } }],
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
const { version } = await getExpectedVersion({
|
|
221
|
+
versions,
|
|
222
|
+
nodeVersion: '20.0.0',
|
|
223
|
+
packageJson: { dependencies: { next: '14.0.0' } },
|
|
224
|
+
packageName: '@netlify/cool-plugin',
|
|
225
|
+
buildDir: '/some/path',
|
|
226
|
+
pinnedVersion: '4',
|
|
227
|
+
systemLog: noopSystemLog,
|
|
228
|
+
});
|
|
229
|
+
expect(version).toBe('4.41.2');
|
|
112
230
|
});
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
test('`getExpectedVersion` should retrieve the plugin based on conditions and feature flag due to pinned version', async () => {
|
|
145
|
-
const versions = [
|
|
146
|
-
{
|
|
147
|
-
version: '5.0.0-beta.1',
|
|
148
|
-
conditions: [
|
|
149
|
-
{ type: 'nodeVersion', condition: '>= 18.0.0' },
|
|
150
|
-
{ type: 'siteDependencies', condition: { next: '>=13.5.0' } },
|
|
151
|
-
],
|
|
152
|
-
overridePinnedVersion: '>=4.0.0',
|
|
153
|
-
},
|
|
154
|
-
{ version: '4.41.2', conditions: [] },
|
|
155
|
-
{
|
|
156
|
-
version: '3.9.2',
|
|
157
|
-
conditions: [{ type: 'siteDependencies', condition: { next: '<10.0.9' } }],
|
|
158
|
-
},
|
|
159
|
-
];
|
|
160
|
-
const { version: version1 } = await getExpectedVersion({
|
|
161
|
-
versions,
|
|
162
|
-
nodeVersion: '20.0.0',
|
|
163
|
-
packageJson: { dependencies: { next: '14.0.0' } },
|
|
164
|
-
buildDir: '/some/path',
|
|
165
|
-
pinnedVersion: '4',
|
|
231
|
+
test('if no pinned version is set, it matches the first version regardless of whether its requirements match the conditions (legacy behavior)', async () => {
|
|
232
|
+
const versions = [
|
|
233
|
+
{
|
|
234
|
+
version: '5.0.0-beta.1',
|
|
235
|
+
conditions: [
|
|
236
|
+
{ type: 'nodeVersion', condition: '>= 18.0.0' },
|
|
237
|
+
{ type: 'siteDependencies', condition: { next: '>=13.5.0' } },
|
|
238
|
+
],
|
|
239
|
+
overridePinnedVersion: '>=4.0.0',
|
|
240
|
+
},
|
|
241
|
+
{ version: '4.41.2', conditions: [] },
|
|
242
|
+
{
|
|
243
|
+
version: '3.9.2',
|
|
244
|
+
conditions: [{ type: 'siteDependencies', condition: { next: '<10.0.9' } }],
|
|
245
|
+
},
|
|
246
|
+
];
|
|
247
|
+
const logMessages = [];
|
|
248
|
+
const { version } = await getExpectedVersion({
|
|
249
|
+
versions,
|
|
250
|
+
nodeVersion: '20.0.0',
|
|
251
|
+
packageJson: { dependencies: { next: '12.0.0' } },
|
|
252
|
+
packageName: '@netlify/cool-plugin',
|
|
253
|
+
buildDir: '/some/path',
|
|
254
|
+
systemLog: (message) => {
|
|
255
|
+
logMessages.push(message);
|
|
256
|
+
},
|
|
257
|
+
authoritative: true,
|
|
258
|
+
});
|
|
259
|
+
expect(logMessages.length).toBe(1);
|
|
260
|
+
expect(logMessages[0]).toBe(`Detected mismatch in selected version for plugin '@netlify/cool-plugin': used legacy version '5.0.0-beta.1' over new version '4.41.2'`);
|
|
261
|
+
expect(version).toBe('5.0.0-beta.1');
|
|
166
262
|
});
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
263
|
+
test('if no pinned version is set, it matches the first version whose requirements match the conditions', async () => {
|
|
264
|
+
const versions = [
|
|
265
|
+
{
|
|
266
|
+
version: '5.0.0-beta.1',
|
|
267
|
+
conditions: [
|
|
268
|
+
{ type: 'nodeVersion', condition: '>= 18.0.0' },
|
|
269
|
+
{ type: 'siteDependencies', condition: { next: '>=13.5.0' } },
|
|
270
|
+
],
|
|
271
|
+
overridePinnedVersion: '>=4.0.0',
|
|
272
|
+
},
|
|
273
|
+
{ version: '4.41.2', conditions: [] },
|
|
274
|
+
{
|
|
275
|
+
version: '3.9.2',
|
|
276
|
+
conditions: [{ type: 'siteDependencies', condition: { next: '<10.0.9' } }],
|
|
277
|
+
},
|
|
278
|
+
];
|
|
279
|
+
const logMessages = [];
|
|
280
|
+
const { version } = await getExpectedVersion({
|
|
281
|
+
versions,
|
|
282
|
+
nodeVersion: '20.0.0',
|
|
283
|
+
packageJson: { dependencies: { next: '12.0.0' } },
|
|
284
|
+
packageName: '@netlify/cool-plugin',
|
|
285
|
+
buildDir: '/some/path',
|
|
286
|
+
systemLog: (message) => {
|
|
287
|
+
logMessages.push(message);
|
|
288
|
+
},
|
|
289
|
+
featureFlags: {
|
|
290
|
+
netlify_build_updated_plugin_compatibility: true,
|
|
291
|
+
},
|
|
292
|
+
authoritative: true,
|
|
293
|
+
});
|
|
294
|
+
expect(logMessages.length).toBe(1);
|
|
295
|
+
expect(logMessages[0]).toBe(`Detected mismatch in selected version for plugin '@netlify/cool-plugin': used new version of '4.41.2' over legacy version '5.0.0-beta.1'`);
|
|
296
|
+
expect(version).toBe('4.41.2');
|
|
175
297
|
});
|
|
176
|
-
expect(version2).toBe('4.41.2');
|
|
177
298
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const addExpectedVersions: ({ pluginsOptions, autoPluginsDir, packageJson, packagePath, debug, logs, buildDir, testOpts, featureFlags, }: {
|
|
1
|
+
export declare const addExpectedVersions: ({ pluginsOptions, autoPluginsDir, packageJson, packagePath, debug, logs, buildDir, testOpts, featureFlags, systemLog, }: {
|
|
2
2
|
pluginsOptions: any;
|
|
3
3
|
autoPluginsDir: any;
|
|
4
4
|
packageJson: any;
|
|
@@ -8,4 +8,5 @@ export declare const addExpectedVersions: ({ pluginsOptions, autoPluginsDir, pac
|
|
|
8
8
|
buildDir: any;
|
|
9
9
|
testOpts: any;
|
|
10
10
|
featureFlags: any;
|
|
11
|
+
systemLog: any;
|
|
11
12
|
}) => Promise<any>;
|
|
@@ -7,7 +7,7 @@ import { getPluginsList } from './list.js';
|
|
|
7
7
|
// When using plugins in our official list, those are installed in .netlify/plugins/
|
|
8
8
|
// We ensure that the last version that's been approved is always the one being used.
|
|
9
9
|
// We also ensure that the plugin is our official list.
|
|
10
|
-
export const addExpectedVersions = async function ({ pluginsOptions, autoPluginsDir, packageJson, packagePath, debug, logs, buildDir, testOpts, featureFlags, }) {
|
|
10
|
+
export const addExpectedVersions = async function ({ pluginsOptions, autoPluginsDir, packageJson, packagePath, debug, logs, buildDir, testOpts, featureFlags, systemLog, }) {
|
|
11
11
|
if (!pluginsOptions.some(needsExpectedVersion)) {
|
|
12
12
|
return pluginsOptions;
|
|
13
13
|
}
|
|
@@ -21,10 +21,11 @@ export const addExpectedVersions = async function ({ pluginsOptions, autoPlugins
|
|
|
21
21
|
buildDir,
|
|
22
22
|
featureFlags,
|
|
23
23
|
testOpts,
|
|
24
|
+
systemLog,
|
|
24
25
|
})));
|
|
25
26
|
};
|
|
26
27
|
/** Any `pluginOptions` with `expectedVersion` set will be automatically installed */
|
|
27
|
-
const addExpectedVersion = async function ({ pluginsList, autoPluginsDir, packageJson, packagePath, pluginOptions, pluginOptions: { packageName, pluginPath, loadedFrom, nodeVersion, pinnedVersion }, buildDir, featureFlags, testOpts, }) {
|
|
28
|
+
const addExpectedVersion = async function ({ pluginsList, autoPluginsDir, packageJson, packagePath, pluginOptions, pluginOptions: { packageName, pluginPath, loadedFrom, nodeVersion, pinnedVersion }, buildDir, featureFlags, testOpts, systemLog, }) {
|
|
28
29
|
if (!needsExpectedVersion(pluginOptions)) {
|
|
29
30
|
return pluginOptions;
|
|
30
31
|
}
|
|
@@ -36,8 +37,28 @@ const addExpectedVersion = async function ({ pluginsList, autoPluginsDir, packag
|
|
|
36
37
|
const versions = filterVersions(unfilteredVersions, featureFlags);
|
|
37
38
|
const [{ version: latestVersion, migrationGuide }] = versions;
|
|
38
39
|
const [{ version: expectedVersion }, { version: compatibleVersion, compatWarning }] = await Promise.all([
|
|
39
|
-
getExpectedVersion({
|
|
40
|
-
|
|
40
|
+
getExpectedVersion({
|
|
41
|
+
versions,
|
|
42
|
+
nodeVersion,
|
|
43
|
+
packageJson,
|
|
44
|
+
packageName,
|
|
45
|
+
packagePath,
|
|
46
|
+
buildDir,
|
|
47
|
+
pinnedVersion,
|
|
48
|
+
featureFlags,
|
|
49
|
+
systemLog,
|
|
50
|
+
authoritative: true,
|
|
51
|
+
}),
|
|
52
|
+
getExpectedVersion({
|
|
53
|
+
versions,
|
|
54
|
+
nodeVersion,
|
|
55
|
+
packageJson,
|
|
56
|
+
packageName,
|
|
57
|
+
packagePath,
|
|
58
|
+
buildDir,
|
|
59
|
+
featureFlags,
|
|
60
|
+
systemLog,
|
|
61
|
+
}),
|
|
41
62
|
]);
|
|
42
63
|
const isMissing = await isMissingVersion({ autoPluginsDir, packageName, pluginPath, loadedFrom, expectedVersion });
|
|
43
64
|
return {
|
package/lib/plugins/resolve.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { CoreStep } from '../types.js';
|
|
1
|
+
import { type CoreStep } from '../types.js';
|
|
2
2
|
export declare const uploadBlobs: CoreStep;
|
|
@@ -3,8 +3,7 @@ import { getDeployStore } from '@netlify/blobs';
|
|
|
3
3
|
import pMap from 'p-map';
|
|
4
4
|
import semver from 'semver';
|
|
5
5
|
import { log, logError } from '../../log/logger.js';
|
|
6
|
-
import { scanForBlobs } from '../../utils/blobs.js';
|
|
7
|
-
import { getKeysToUpload, getFileWithMetadata } from './utils.js';
|
|
6
|
+
import { getFileWithMetadata, getKeysToUpload, scanForBlobs } from '../../utils/blobs.js';
|
|
8
7
|
const coreStep = async function ({ debug, logs, deployId, buildDir, quiet, packagePath, constants: { SITE_ID, NETLIFY_API_TOKEN, NETLIFY_API_HOST }, }) {
|
|
9
8
|
// This should never happen due to the condition check
|
|
10
9
|
if (!deployId || !NETLIFY_API_TOKEN) {
|
|
@@ -20,10 +19,8 @@ const coreStep = async function ({ debug, logs, deployId, buildDir, quiet, packa
|
|
|
20
19
|
};
|
|
21
20
|
// If we don't have native `fetch` in the global scope, add a polyfill.
|
|
22
21
|
if (semver.lt(nodeVersion, '18.0.0')) {
|
|
23
|
-
const nodeFetch = await import('node-fetch');
|
|
24
|
-
|
|
25
|
-
// are not a 100% match, even though the APIs are mostly compatible.
|
|
26
|
-
storeOpts.fetch = nodeFetch.default;
|
|
22
|
+
const nodeFetch = (await import('node-fetch')).default;
|
|
23
|
+
storeOpts.fetch = nodeFetch;
|
|
27
24
|
}
|
|
28
25
|
const blobs = await scanForBlobs(buildDir, packagePath);
|
|
29
26
|
// We checked earlier, but let's be extra safe
|
|
@@ -49,15 +46,14 @@ const coreStep = async function ({ debug, logs, deployId, buildDir, quiet, packa
|
|
|
49
46
|
if (!quiet) {
|
|
50
47
|
log(logs, `Uploading ${keys.length} blobs to deploy store...`);
|
|
51
48
|
}
|
|
52
|
-
const uploadBlob = async (key) => {
|
|
53
|
-
if (debug && !quiet) {
|
|
54
|
-
log(logs, `- Uploading blob ${key}`, { indent: true });
|
|
55
|
-
}
|
|
56
|
-
const { data, metadata } = await getFileWithMetadata(blobs.directory, key);
|
|
57
|
-
await blobStore.set(key, data, { metadata });
|
|
58
|
-
};
|
|
59
49
|
try {
|
|
60
|
-
await pMap(keys,
|
|
50
|
+
await pMap(keys, async (key) => {
|
|
51
|
+
if (debug && !quiet) {
|
|
52
|
+
log(logs, `- Uploading blob ${key}`, { indent: true });
|
|
53
|
+
}
|
|
54
|
+
const { data, metadata } = await getFileWithMetadata(blobs.directory, key);
|
|
55
|
+
await blobStore.set(key, data, { metadata });
|
|
56
|
+
}, { concurrency: 10 });
|
|
61
57
|
}
|
|
62
58
|
catch (err) {
|
|
63
59
|
logError(logs, `Error uploading blobs to deploy store: ${err.message}`);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { version as nodeVersion } from 'node:process';
|
|
2
|
+
import { getDeployStore } from '@netlify/blobs';
|
|
3
|
+
import pMap from 'p-map';
|
|
4
|
+
import semver from 'semver';
|
|
5
|
+
import { log, logError } from '../../log/logger.js';
|
|
6
|
+
import { getFileWithMetadata, getKeysToUpload, scanForBlobs } from '../../utils/blobs.js';
|
|
7
|
+
const coreStep = async function ({ debug, logs, deployId, buildDir, quiet, packagePath, constants: { SITE_ID, NETLIFY_API_TOKEN, NETLIFY_API_HOST }, }) {
|
|
8
|
+
// This should never happen due to the condition check
|
|
9
|
+
if (!deployId || !NETLIFY_API_TOKEN) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
// for cli deploys with `netlify deploy --build` the `NETLIFY_API_HOST` is undefined
|
|
13
|
+
const apiHost = NETLIFY_API_HOST || 'api.netlify.com';
|
|
14
|
+
const storeOpts = {
|
|
15
|
+
siteID: SITE_ID,
|
|
16
|
+
deployID: deployId,
|
|
17
|
+
token: NETLIFY_API_TOKEN,
|
|
18
|
+
apiURL: `https://${apiHost}`,
|
|
19
|
+
};
|
|
20
|
+
// If we don't have native `fetch` in the global scope, add a polyfill.
|
|
21
|
+
if (semver.lt(nodeVersion, '18.0.0')) {
|
|
22
|
+
const nodeFetch = (await import('node-fetch')).default;
|
|
23
|
+
storeOpts.fetch = nodeFetch;
|
|
24
|
+
}
|
|
25
|
+
const blobs = await scanForBlobs(buildDir, packagePath);
|
|
26
|
+
// We checked earlier, but let's be extra safe
|
|
27
|
+
if (blobs === null) {
|
|
28
|
+
if (!quiet) {
|
|
29
|
+
log(logs, 'No blobs to upload to deploy store.');
|
|
30
|
+
}
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
// If using the deploy config API, configure the store to use the region that
|
|
34
|
+
// was configured for the deploy.
|
|
35
|
+
if (!blobs.isLegacyDirectory) {
|
|
36
|
+
storeOpts.experimentalRegion = 'auto';
|
|
37
|
+
}
|
|
38
|
+
const blobStore = getDeployStore(storeOpts);
|
|
39
|
+
const keys = await getKeysToUpload(blobs.directory);
|
|
40
|
+
if (keys.length === 0) {
|
|
41
|
+
if (!quiet) {
|
|
42
|
+
log(logs, 'No blobs to upload to deploy store.');
|
|
43
|
+
}
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
if (!quiet) {
|
|
47
|
+
log(logs, `Uploading ${keys.length} blobs to deploy store...`);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
await pMap(keys, async (key) => {
|
|
51
|
+
if (debug && !quiet) {
|
|
52
|
+
log(logs, `- Uploading blob ${key}`, { indent: true });
|
|
53
|
+
}
|
|
54
|
+
const { data, metadata } = await getFileWithMetadata(blobs.directory, key);
|
|
55
|
+
await blobStore.set(key, data, { metadata });
|
|
56
|
+
}, { concurrency: 10 });
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
logError(logs, `Error uploading blobs to deploy store: ${err.message}`);
|
|
60
|
+
throw new Error(`Failed while uploading blobs to deploy store`);
|
|
61
|
+
}
|
|
62
|
+
if (!quiet) {
|
|
63
|
+
log(logs, `Done uploading blobs to deploy store.`);
|
|
64
|
+
}
|
|
65
|
+
return {};
|
|
66
|
+
};
|
|
67
|
+
const deployAndBlobsPresent = async ({ deployId, buildDir, packagePath, constants: { NETLIFY_API_TOKEN }, }) => Boolean(NETLIFY_API_TOKEN && deployId && (await scanForBlobs(buildDir, packagePath)));
|
|
68
|
+
export const devUploadBlobs = {
|
|
69
|
+
event: 'onDev',
|
|
70
|
+
coreStep,
|
|
71
|
+
coreStepId: 'dev_blobs_upload',
|
|
72
|
+
coreStepName: 'Uploading blobs',
|
|
73
|
+
coreStepDescription: () => 'Uploading blobs to development deploy store',
|
|
74
|
+
condition: deployAndBlobsPresent,
|
|
75
|
+
};
|
package/lib/steps/get.js
CHANGED
|
@@ -4,6 +4,7 @@ import { uploadBlobs } from '../plugins_core/blobs_upload/index.js';
|
|
|
4
4
|
import { buildCommandCore } from '../plugins_core/build_command.js';
|
|
5
5
|
import { deploySite } from '../plugins_core/deploy/index.js';
|
|
6
6
|
import { applyDeployConfig } from '../plugins_core/deploy_config/index.js';
|
|
7
|
+
import { devUploadBlobs } from '../plugins_core/dev_blobs_upload/index.js';
|
|
7
8
|
import { bundleEdgeFunctions } from '../plugins_core/edge_functions/index.js';
|
|
8
9
|
import { bundleFunctions } from '../plugins_core/functions/index.js';
|
|
9
10
|
import { preCleanup } from '../plugins_core/pre_cleanup/index.js';
|
|
@@ -32,7 +33,7 @@ export const getDevSteps = function (command, steps, eventHandlers) {
|
|
|
32
33
|
coreStepDescription: () => 'Run command for local development',
|
|
33
34
|
};
|
|
34
35
|
const eventSteps = getEventSteps(eventHandlers);
|
|
35
|
-
const sortedSteps = sortSteps([preDevCleanup, ...steps, eventSteps, devCommandStep], DEV_EVENTS);
|
|
36
|
+
const sortedSteps = sortSteps([preDevCleanup, ...steps, devUploadBlobs, eventSteps, devCommandStep], DEV_EVENTS);
|
|
36
37
|
const events = getEvents(sortedSteps);
|
|
37
38
|
return { steps: sortedSteps, events };
|
|
38
39
|
};
|
package/lib/utils/blobs.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
1
2
|
/** Retrieve the absolute path of the deploy scoped internal blob directories */
|
|
2
3
|
export declare const getBlobsDirs: (buildDir: string, packagePath?: string) => string[];
|
|
3
4
|
/**
|
|
@@ -13,3 +14,10 @@ export declare const scanForBlobs: (buildDir: string, packagePath?: string) => P
|
|
|
13
14
|
directory: string;
|
|
14
15
|
isLegacyDirectory: boolean;
|
|
15
16
|
} | null>;
|
|
17
|
+
/** Given output directory, find all file paths to upload excluding metadata files */
|
|
18
|
+
export declare const getKeysToUpload: (blobsDir: string) => Promise<string[]>;
|
|
19
|
+
/** Read a file and its metadata file from the blobs directory */
|
|
20
|
+
export declare const getFileWithMetadata: (blobsDir: string, key: string) => Promise<{
|
|
21
|
+
data: Buffer;
|
|
22
|
+
metadata: Record<string, string>;
|
|
23
|
+
}>;
|
package/lib/utils/blobs.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { fdir } from 'fdir';
|
|
3
4
|
const LEGACY_BLOBS_PATH = '.netlify/blobs/deploy';
|
|
4
5
|
const DEPLOY_CONFIG_BLOBS_PATH = '.netlify/deploy/v1/blobs/deploy';
|
|
5
6
|
/** Retrieve the absolute path of the deploy scoped internal blob directories */
|
|
6
7
|
export const getBlobsDirs = (buildDir, packagePath) => [
|
|
7
|
-
resolve(buildDir, packagePath || '', DEPLOY_CONFIG_BLOBS_PATH),
|
|
8
|
-
resolve(buildDir, packagePath || '', LEGACY_BLOBS_PATH),
|
|
8
|
+
path.resolve(buildDir, packagePath || '', DEPLOY_CONFIG_BLOBS_PATH),
|
|
9
|
+
path.resolve(buildDir, packagePath || '', LEGACY_BLOBS_PATH),
|
|
9
10
|
];
|
|
10
11
|
/**
|
|
11
12
|
* Detect if there are any blobs to upload, and if so, what directory they're
|
|
@@ -17,7 +18,7 @@ export const getBlobsDirs = (buildDir, packagePath) => [
|
|
|
17
18
|
* @returns
|
|
18
19
|
*/
|
|
19
20
|
export const scanForBlobs = async function (buildDir, packagePath) {
|
|
20
|
-
const blobsDir = resolve(buildDir, packagePath || '', DEPLOY_CONFIG_BLOBS_PATH);
|
|
21
|
+
const blobsDir = path.resolve(buildDir, packagePath || '', DEPLOY_CONFIG_BLOBS_PATH);
|
|
21
22
|
const blobsDirScan = await new fdir().onlyCounts().crawl(blobsDir).withPromise();
|
|
22
23
|
if (blobsDirScan.files > 0) {
|
|
23
24
|
return {
|
|
@@ -25,7 +26,7 @@ export const scanForBlobs = async function (buildDir, packagePath) {
|
|
|
25
26
|
isLegacyDirectory: false,
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
|
-
const legacyBlobsDir = resolve(buildDir, packagePath || '', LEGACY_BLOBS_PATH);
|
|
29
|
+
const legacyBlobsDir = path.resolve(buildDir, packagePath || '', LEGACY_BLOBS_PATH);
|
|
29
30
|
const legacyBlobsDirScan = await new fdir().onlyCounts().crawl(legacyBlobsDir).withPromise();
|
|
30
31
|
if (legacyBlobsDirScan.files > 0) {
|
|
31
32
|
return {
|
|
@@ -35,3 +36,46 @@ export const scanForBlobs = async function (buildDir, packagePath) {
|
|
|
35
36
|
}
|
|
36
37
|
return null;
|
|
37
38
|
};
|
|
39
|
+
const METADATA_PREFIX = '$';
|
|
40
|
+
const METADATA_SUFFIX = '.json';
|
|
41
|
+
/** Given output directory, find all file paths to upload excluding metadata files */
|
|
42
|
+
export const getKeysToUpload = async (blobsDir) => {
|
|
43
|
+
const files = await new fdir()
|
|
44
|
+
.withRelativePaths() // we want the relative path from the blobsDir
|
|
45
|
+
.filter((fpath) => !path.basename(fpath).startsWith(METADATA_PREFIX))
|
|
46
|
+
.crawl(blobsDir)
|
|
47
|
+
.withPromise();
|
|
48
|
+
// normalize the path separators to all use the forward slash
|
|
49
|
+
return files.map((f) => f.split(path.sep).join('/'));
|
|
50
|
+
};
|
|
51
|
+
/** Read a file and its metadata file from the blobs directory */
|
|
52
|
+
export const getFileWithMetadata = async (blobsDir, key) => {
|
|
53
|
+
const contentPath = path.join(blobsDir, key);
|
|
54
|
+
const dirname = path.dirname(key);
|
|
55
|
+
const basename = path.basename(key);
|
|
56
|
+
const metadataPath = path.join(blobsDir, dirname, `${METADATA_PREFIX}${basename}${METADATA_SUFFIX}`);
|
|
57
|
+
const [data, metadata] = await Promise.all([readFile(contentPath), readMetadata(metadataPath)]).catch((err) => {
|
|
58
|
+
throw new Error(`Failed while reading '${key}' and its metadata: ${err.message}`);
|
|
59
|
+
});
|
|
60
|
+
return { data, metadata };
|
|
61
|
+
};
|
|
62
|
+
const readMetadata = async (metadataPath) => {
|
|
63
|
+
let metadataFile;
|
|
64
|
+
try {
|
|
65
|
+
metadataFile = await readFile(metadataPath, { encoding: 'utf8' });
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
if (err.code === 'ENOENT') {
|
|
69
|
+
// no metadata file found, that's ok
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(metadataFile);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Normalize the error message
|
|
79
|
+
throw new Error(`Error parsing metadata file '${metadataPath}'`);
|
|
80
|
+
}
|
|
81
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/build",
|
|
3
|
-
"version": "29.
|
|
3
|
+
"version": "29.39.0",
|
|
4
4
|
"description": "Netlify build module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./lib/index.js",
|
|
@@ -68,17 +68,17 @@
|
|
|
68
68
|
"license": "MIT",
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@bugsnag/js": "^7.0.0",
|
|
71
|
-
"@netlify/blobs": "^7.
|
|
71
|
+
"@netlify/blobs": "^7.3.0",
|
|
72
72
|
"@netlify/cache-utils": "^5.1.5",
|
|
73
73
|
"@netlify/config": "^20.12.1",
|
|
74
74
|
"@netlify/edge-bundler": "11.3.0",
|
|
75
75
|
"@netlify/framework-info": "^9.8.11",
|
|
76
|
-
"@netlify/functions-utils": "^5.2.
|
|
76
|
+
"@netlify/functions-utils": "^5.2.53",
|
|
77
77
|
"@netlify/git-utils": "^5.1.1",
|
|
78
78
|
"@netlify/opentelemetry-utils": "^1.1.0",
|
|
79
79
|
"@netlify/plugins-list": "^6.77.0",
|
|
80
80
|
"@netlify/run-utils": "^5.1.1",
|
|
81
|
-
"@netlify/zip-it-and-ship-it": "9.31.
|
|
81
|
+
"@netlify/zip-it-and-ship-it": "9.31.1",
|
|
82
82
|
"@sindresorhus/slugify": "^2.0.0",
|
|
83
83
|
"ansi-escapes": "^6.0.0",
|
|
84
84
|
"chalk": "^5.0.0",
|
|
@@ -164,5 +164,5 @@
|
|
|
164
164
|
"engines": {
|
|
165
165
|
"node": "^14.16.0 || >=16.0.0"
|
|
166
166
|
},
|
|
167
|
-
"gitHead": "
|
|
167
|
+
"gitHead": "1594f6b29e2fcaf9b57fc0938af0d352d74374c1"
|
|
168
168
|
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
/** Given output directory, find all file paths to upload excluding metadata files */
|
|
3
|
-
export declare function getKeysToUpload(blobsDir: string): Promise<string[]>;
|
|
4
|
-
/** Read a file and its metadata file from the blobs directory */
|
|
5
|
-
export declare function getFileWithMetadata(blobsDir: string, key: string): Promise<{
|
|
6
|
-
data: Buffer;
|
|
7
|
-
metadata: Record<string, string>;
|
|
8
|
-
}>;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fdir } from 'fdir';
|
|
4
|
-
const METADATA_PREFIX = '$';
|
|
5
|
-
const METADATA_SUFFIX = '.json';
|
|
6
|
-
/** Given output directory, find all file paths to upload excluding metadata files */
|
|
7
|
-
export async function getKeysToUpload(blobsDir) {
|
|
8
|
-
const files = await new fdir()
|
|
9
|
-
.withRelativePaths() // we want the relative path from the blobsDir
|
|
10
|
-
.filter((fpath) => !path.basename(fpath).startsWith(METADATA_PREFIX))
|
|
11
|
-
.crawl(blobsDir)
|
|
12
|
-
.withPromise();
|
|
13
|
-
// normalize the path separators to all use the forward slash
|
|
14
|
-
return files.map((f) => f.split(path.sep).join('/'));
|
|
15
|
-
}
|
|
16
|
-
/** Read a file and its metadata file from the blobs directory */
|
|
17
|
-
export async function getFileWithMetadata(blobsDir, key) {
|
|
18
|
-
const contentPath = path.join(blobsDir, key);
|
|
19
|
-
const dirname = path.dirname(key);
|
|
20
|
-
const basename = path.basename(key);
|
|
21
|
-
const metadataPath = path.join(blobsDir, dirname, `${METADATA_PREFIX}${basename}${METADATA_SUFFIX}`);
|
|
22
|
-
const [data, metadata] = await Promise.all([readFile(contentPath), readMetadata(metadataPath)]).catch((err) => {
|
|
23
|
-
throw new Error(`Failed while reading '${key}' and its metadata: ${err.message}`);
|
|
24
|
-
});
|
|
25
|
-
return { data, metadata };
|
|
26
|
-
}
|
|
27
|
-
async function readMetadata(metadataPath) {
|
|
28
|
-
let metadataFile;
|
|
29
|
-
try {
|
|
30
|
-
metadataFile = await readFile(metadataPath, { encoding: 'utf8' });
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
if (err.code === 'ENOENT') {
|
|
34
|
-
// no metadata file found, that's ok
|
|
35
|
-
return {};
|
|
36
|
-
}
|
|
37
|
-
throw err;
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
return JSON.parse(metadataFile);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
// Normalize the error message
|
|
44
|
-
throw new Error(`Error parsing metadata file '${metadataPath}'`);
|
|
45
|
-
}
|
|
46
|
-
}
|