@netlify/build 29.38.2 → 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.
@@ -13,16 +13,18 @@ import { PluginVersion } from './list.js';
13
13
  * - This is only used to print a warning message when the `compatibleVersion`
14
14
  * is older than the currently used version.
15
15
  */
16
- export declare const getExpectedVersion: ({ versions, nodeVersion, packageJson, packagePath, buildDir, pinnedVersion, featureFlags, systemLog, }: {
16
+ export declare const getExpectedVersion: ({ versions, nodeVersion, packageJson, packageName, packagePath, buildDir, pinnedVersion, featureFlags, systemLog, authoritative, }: {
17
17
  versions: PluginVersion[];
18
18
  /** The package.json of the repository */
19
19
  packageJson: PackageJson;
20
+ packageName: string;
20
21
  packagePath?: string | undefined;
21
22
  buildDir: string;
22
23
  nodeVersion: string;
23
24
  pinnedVersion?: string | undefined;
24
25
  featureFlags?: FeatureFlags | undefined;
25
26
  systemLog: SystemLogger;
27
+ authoritative?: boolean | undefined;
26
28
  }) => Promise<{
27
29
  version: string;
28
30
  compatWarning: string;
@@ -15,16 +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, featureFlags, systemLog, }) {
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,
26
27
  featureFlags,
27
- systemLog,
28
+ systemLog: authoritative ? systemLog : undefined,
28
29
  });
29
30
  // Retrieve warning message shown when using an older version with `compatibility`
30
31
  const compatWarning = conditions.map(({ type, condition }) => CONDITIONS[type].warning(condition)).join(', ');
@@ -46,7 +47,9 @@ export const getExpectedVersion = async function ({ versions, nodeVersion, packa
46
47
  * - If there is a `pinnedVersion`, use it unless `latestVersion` matches it
47
48
  * - Otherwise, use `latestVersion`
48
49
  */
49
- const getCompatibleEntry = async function ({ versions, nodeVersion, packageJson, packagePath, buildDir, pinnedVersion, featureFlags, systemLog, }) {
50
+ const getCompatibleEntry = async function ({ versions, nodeVersion, packageJson, packageName, packagePath, buildDir, pinnedVersion, featureFlags, systemLog = () => {
51
+ // no-op
52
+ }, }) {
50
53
  const compatibleEntry = await pLocate(versions, async ({ version, overridePinnedVersion, conditions }) => {
51
54
  // When there's a `pinnedVersion`, we typically pick the first version that
52
55
  // matches that range. The exception is if `overridePinnedVersion` is also
@@ -66,21 +69,23 @@ const getCompatibleEntry = async function ({ versions, nodeVersion, packageJson,
66
69
  return await pEvery(conditions, async ({ type, condition }) => CONDITIONS[type].test(condition, { nodeVersion, packageJson, packagePath, buildDir }));
67
70
  });
68
71
  if (compatibleEntry) {
72
+ systemLog(`Used compatible version '${compatibleEntry.version}' for plugin '${packageName}' (pinned version is ${pinnedVersion})`);
69
73
  return compatibleEntry;
70
74
  }
71
75
  if (pinnedVersion) {
76
+ systemLog(`Used pinned version '${pinnedVersion}' for plugin '${packageName}'`);
72
77
  return { version: pinnedVersion, conditions: [] };
73
78
  }
74
79
  const legacyFallback = { version: versions[0].version, conditions: [] };
75
80
  const fallback = await getFirstCompatibleEntry({ versions, nodeVersion, packageJson, packagePath, buildDir });
76
81
  if (featureFlags?.netlify_build_updated_plugin_compatibility) {
77
82
  if (legacyFallback.version !== fallback.version) {
78
- systemLog(`Detected mismatch in selected version for plugin '${packageJson?.name}': used new version of '${fallback.version}' over legacy version '${legacyFallback.version}'`);
83
+ systemLog(`Detected mismatch in selected version for plugin '${packageName}': used new version of '${fallback.version}' over legacy version '${legacyFallback.version}'`);
79
84
  }
80
85
  return fallback;
81
86
  }
82
87
  if (legacyFallback.version !== fallback.version) {
83
- systemLog(`Detected mismatch in selected version for plugin '${packageJson?.name}': used legacy version '${legacyFallback.version}' over new version '${fallback.version}'`);
88
+ systemLog(`Detected mismatch in selected version for plugin '${packageName}': used legacy version '${legacyFallback.version}' over new version '${fallback.version}'`);
84
89
  }
85
90
  return legacyFallback;
86
91
  };
@@ -13,6 +13,7 @@ describe(`getExpectedVersion`, () => {
13
13
  versions,
14
14
  nodeVersion: '18.19.0',
15
15
  packageJson: {},
16
+ packageName: '@netlify/cool-plugin',
16
17
  buildDir: '/some/path',
17
18
  pinnedVersion: '4',
18
19
  systemLog: noopSystemLog,
@@ -29,6 +30,7 @@ describe(`getExpectedVersion`, () => {
29
30
  versions,
30
31
  nodeVersion: '18.19.0',
31
32
  packageJson: {},
33
+ packageName: '@netlify/cool-plugin',
32
34
  buildDir: '/some/path',
33
35
  systemLog: noopSystemLog,
34
36
  });
@@ -36,6 +38,7 @@ describe(`getExpectedVersion`, () => {
36
38
  versions,
37
39
  nodeVersion: '18.19.0',
38
40
  packageJson: {},
41
+ packageName: '@netlify/cool-plugin',
39
42
  buildDir: '/some/path',
40
43
  pinnedVersion: '4',
41
44
  systemLog: noopSystemLog,
@@ -52,6 +55,7 @@ describe(`getExpectedVersion`, () => {
52
55
  versions,
53
56
  nodeVersion: '18.19.0',
54
57
  packageJson: {},
58
+ packageName: '@netlify/cool-plugin',
55
59
  buildDir: '/some/path',
56
60
  pinnedVersion: '4',
57
61
  systemLog: noopSystemLog,
@@ -70,6 +74,7 @@ describe(`getExpectedVersion`, () => {
70
74
  versions,
71
75
  nodeVersion: '17.19.0',
72
76
  packageJson: {},
77
+ packageName: '@netlify/cool-plugin',
73
78
  buildDir: '/some/path',
74
79
  pinnedVersion: '4',
75
80
  systemLog: noopSystemLog,
@@ -100,6 +105,7 @@ describe(`getExpectedVersion`, () => {
100
105
  versions,
101
106
  nodeVersion: '17.19.0',
102
107
  packageJson: { dependencies: { next: '10.0.8' } },
108
+ packageName: '@netlify/cool-plugin',
103
109
  buildDir: '/some/path',
104
110
  pinnedVersion: '3',
105
111
  systemLog: noopSystemLog,
@@ -109,6 +115,7 @@ describe(`getExpectedVersion`, () => {
109
115
  versions,
110
116
  nodeVersion: '17.19.0',
111
117
  packageJson: { dependencies: { next: '11.0.0' } },
118
+ packageName: '@netlify/cool-plugin',
112
119
  buildDir: '/some/path',
113
120
  pinnedVersion: '4',
114
121
  systemLog: noopSystemLog,
@@ -118,6 +125,7 @@ describe(`getExpectedVersion`, () => {
118
125
  versions,
119
126
  nodeVersion: '18.19.0',
120
127
  packageJson: { dependencies: { next: '13.5.0' } },
128
+ packageName: '@netlify/cool-plugin',
121
129
  buildDir: '/some/path',
122
130
  pinnedVersion: '4',
123
131
  systemLog: noopSystemLog,
@@ -148,6 +156,7 @@ describe(`getExpectedVersion`, () => {
148
156
  versions,
149
157
  nodeVersion: '18.19.0',
150
158
  packageJson: { dependencies: { next: '14.1.1-canary.36' } },
159
+ packageName: '@netlify/cool-plugin',
151
160
  buildDir: '/some/path',
152
161
  pinnedVersion: '4',
153
162
  systemLog: noopSystemLog,
@@ -174,6 +183,7 @@ describe(`getExpectedVersion`, () => {
174
183
  versions,
175
184
  nodeVersion: '20.0.0',
176
185
  packageJson: { dependencies: { next: '14.0.0' } },
186
+ packageName: '@netlify/cool-plugin',
177
187
  buildDir: '/some/path',
178
188
  pinnedVersion: '4',
179
189
  systemLog: noopSystemLog,
@@ -184,6 +194,7 @@ describe(`getExpectedVersion`, () => {
184
194
  versions,
185
195
  nodeVersion: '20.0.0',
186
196
  packageJson: { dependencies: { next: '13.0.0' } },
197
+ packageName: '@netlify/cool-plugin',
187
198
  buildDir: '/some/path',
188
199
  pinnedVersion: '4',
189
200
  systemLog: noopSystemLog,
@@ -210,6 +221,7 @@ describe(`getExpectedVersion`, () => {
210
221
  versions,
211
222
  nodeVersion: '20.0.0',
212
223
  packageJson: { dependencies: { next: '14.0.0' } },
224
+ packageName: '@netlify/cool-plugin',
213
225
  buildDir: '/some/path',
214
226
  pinnedVersion: '4',
215
227
  systemLog: noopSystemLog,
@@ -236,14 +248,16 @@ describe(`getExpectedVersion`, () => {
236
248
  const { version } = await getExpectedVersion({
237
249
  versions,
238
250
  nodeVersion: '20.0.0',
239
- packageJson: { name: '@netlify/a-cool-plugin', dependencies: { next: '12.0.0' } },
251
+ packageJson: { dependencies: { next: '12.0.0' } },
252
+ packageName: '@netlify/cool-plugin',
240
253
  buildDir: '/some/path',
241
254
  systemLog: (message) => {
242
255
  logMessages.push(message);
243
256
  },
257
+ authoritative: true,
244
258
  });
245
259
  expect(logMessages.length).toBe(1);
246
- expect(logMessages[0]).toBe(`Detected mismatch in selected version for plugin '@netlify/a-cool-plugin': used legacy version '5.0.0-beta.1' over new version '4.41.2'`);
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'`);
247
261
  expect(version).toBe('5.0.0-beta.1');
248
262
  });
249
263
  test('if no pinned version is set, it matches the first version whose requirements match the conditions', async () => {
@@ -266,7 +280,8 @@ describe(`getExpectedVersion`, () => {
266
280
  const { version } = await getExpectedVersion({
267
281
  versions,
268
282
  nodeVersion: '20.0.0',
269
- packageJson: { name: '@netlify/a-cool-plugin', dependencies: { next: '12.0.0' } },
283
+ packageJson: { dependencies: { next: '12.0.0' } },
284
+ packageName: '@netlify/cool-plugin',
270
285
  buildDir: '/some/path',
271
286
  systemLog: (message) => {
272
287
  logMessages.push(message);
@@ -274,9 +289,10 @@ describe(`getExpectedVersion`, () => {
274
289
  featureFlags: {
275
290
  netlify_build_updated_plugin_compatibility: true,
276
291
  },
292
+ authoritative: true,
277
293
  });
278
294
  expect(logMessages.length).toBe(1);
279
- expect(logMessages[0]).toBe(`Detected mismatch in selected version for plugin '@netlify/a-cool-plugin': used new version of '4.41.2' over legacy version '5.0.0-beta.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'`);
280
296
  expect(version).toBe('4.41.2');
281
297
  });
282
298
  });
@@ -41,13 +41,24 @@ const addExpectedVersion = async function ({ pluginsList, autoPluginsDir, packag
41
41
  versions,
42
42
  nodeVersion,
43
43
  packageJson,
44
+ packageName,
44
45
  packagePath,
45
46
  buildDir,
46
47
  pinnedVersion,
47
48
  featureFlags,
48
49
  systemLog,
50
+ authoritative: true,
51
+ }),
52
+ getExpectedVersion({
53
+ versions,
54
+ nodeVersion,
55
+ packageJson,
56
+ packageName,
57
+ packagePath,
58
+ buildDir,
59
+ featureFlags,
60
+ systemLog,
49
61
  }),
50
- getExpectedVersion({ versions, nodeVersion, packageJson, packagePath, buildDir, featureFlags, systemLog }),
51
62
  ]);
52
63
  const isMissing = await isMissingVersion({ autoPluginsDir, packageName, pluginPath, loadedFrom, expectedVersion });
53
64
  return {
@@ -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
- // @ts-expect-error The types between `node-fetch` and the native `fetch`
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, uploadBlob, { concurrency: 10 });
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,2 @@
1
+ import { type CoreStep } from '../types.js';
2
+ export declare const devUploadBlobs: CoreStep;
@@ -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
  };
@@ -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
+ }>;
@@ -1,11 +1,12 @@
1
- import { resolve } from 'node:path';
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.38.2",
3
+ "version": "29.39.0",
4
4
  "description": "Netlify build module",
5
5
  "type": "module",
6
6
  "exports": "./lib/index.js",
@@ -164,5 +164,5 @@
164
164
  "engines": {
165
165
  "node": "^14.16.0 || >=16.0.0"
166
166
  },
167
- "gitHead": "f172805567a263a78956981106526411a9050644"
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
- }