@vercel/build-utils 2.12.3-canary.39 → 2.12.3-canary.42

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.
@@ -70,16 +70,15 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
70
70
  }
71
71
  const pages = {};
72
72
  const pluginName = packageName.replace('vercel-plugin-', '');
73
- const traceDir = path_1.join(workPath, `.output`, `inputs`,
73
+ const outputPath = path_1.join(workPath, '.output');
74
+ const traceDir = path_1.join(outputPath, `inputs`,
74
75
  // Legacy Runtimes can only provide API Routes, so that's
75
76
  // why we can use this prefix for all of them. Here, we have to
76
77
  // make sure to not use a cryptic hash name, because people
77
78
  // need to be able to easily inspect the output.
78
79
  `api-routes-${pluginName}`);
79
80
  await fs_extra_1.default.ensureDir(traceDir);
80
- let newPathsRuntime = new Set();
81
- const entryDir = path_1.join('.output', 'server', 'pages');
82
- const entryRoot = path_1.join(workPath, entryDir);
81
+ const entryRoot = path_1.join(outputPath, 'server', 'pages');
83
82
  for (const entrypoint of Object.keys(entrypoints)) {
84
83
  const { output } = await buildRuntime({
85
84
  files: sourceFilesPreBuild,
@@ -93,13 +92,6 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
93
92
  skipDownload: true,
94
93
  },
95
94
  });
96
- // Legacy Runtimes tend to pollute the `workPath` with compiled results,
97
- // because the `workPath` used to be a place that was a place where they could
98
- // just put anything, but nowadays it's the working directory of the `vercel build`
99
- // command, which is the place where the developer keeps their source files,
100
- // so we don't want to pollute this space unnecessarily. That means we have to clean
101
- // up files that were created by the build, which is done further below.
102
- const sourceFilesAfterBuild = await getSourceFiles(workPath, ignoreFilter);
103
95
  // @ts-ignore This symbol is a private API
104
96
  const lambdaFiles = output[lambda_1.FILES_SYMBOL];
105
97
  // When deploying, the `files` that are passed to the Legacy Runtimes already
@@ -113,6 +105,7 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
113
105
  }
114
106
  let handlerFileBase = output.handler;
115
107
  let handlerFile = lambdaFiles[handlerFileBase];
108
+ let handlerHasImport = false;
116
109
  const { handler } = output;
117
110
  const handlerMethod = handler.split('.').pop();
118
111
  const handlerFileName = handler.replace(`.${handlerMethod}`, '');
@@ -124,6 +117,7 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
124
117
  if (!handlerFile) {
125
118
  handlerFileBase = handlerFileName + ext;
126
119
  handlerFile = lambdaFiles[handlerFileBase];
120
+ handlerHasImport = true;
127
121
  }
128
122
  if (!handlerFile || !handlerFile.fsPath) {
129
123
  throw new Error(`Could not find a handler file. Please ensure that \`files\` for the returned \`Lambda\` contains an \`FileFsRef\` named "${handlerFileBase}" with a valid \`fsPath\`.`);
@@ -132,58 +126,69 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
132
126
  const entryBase = path_1.basename(entrypoint).replace(ext, handlerExtName);
133
127
  const entryPath = path_1.join(path_1.dirname(entrypoint), entryBase);
134
128
  const entry = path_1.join(entryRoot, entryPath);
135
- // We never want to link here, only copy, because the launcher
136
- // file often has the same name for every entrypoint, which means that
137
- // every build for every entrypoint overwrites the launcher of the previous
138
- // one, so linking would end with a broken reference.
129
+ // Create the parent directory of the API Route that will be created
130
+ // for the current entrypoint inside of `.output/server/pages/api`.
139
131
  await fs_extra_1.default.ensureDir(path_1.dirname(entry));
140
- await fs_extra_1.default.copy(handlerFile.fsPath, entry);
141
- const newFilesEntrypoint = [];
142
- const newDirectoriesEntrypoint = [];
143
- const preBuildFiles = Object.values(sourceFilesPreBuild).map(file => {
144
- return file.fsPath;
145
- });
146
- // Generate a list of directories and files that weren't present
147
- // before the entrypoint was processed by the Legacy Runtime, so
148
- // that we can perform a cleanup later. We need to divide into files
149
- // and directories because only cleaning up files might leave empty
150
- // directories, and listing directories separately also speeds up the
151
- // build because we can just delete them, which wipes all of their nested
152
- // paths, instead of iterating through all files that should be deleted.
153
- for (const file in sourceFilesAfterBuild) {
154
- if (!sourceFilesPreBuild[file]) {
155
- const path = sourceFilesAfterBuild[file].fsPath;
156
- const dirPath = path_1.dirname(path);
157
- // If none of the files that were present before the entrypoint
158
- // was processed are contained within the directory we're looking
159
- // at right now, then we know it's a newly added directory
160
- // and it can therefore be removed later on.
161
- const isNewDir = !preBuildFiles.some(filePath => {
162
- return path_1.dirname(filePath).startsWith(dirPath);
163
- });
164
- // Check out the list of tracked directories that were
165
- // newly added and see if one of them contains the path
166
- // we're looking at.
167
- const hasParentDir = newDirectoriesEntrypoint.some(dir => {
168
- return path.startsWith(dir);
132
+ // For compiled languages, the launcher file will be binary and therefore
133
+ // won't try to import a user-provided request handler (instead, it will
134
+ // contain it). But for interpreted languages, the launcher might try to
135
+ // load a user-provided request handler from the source file instead of bundling
136
+ // it, so we have to adjust the import statement inside the launcher to point
137
+ // to the respective source file. Previously, Legacy Runtimes simply expected
138
+ // the user-provided request-handler to be copied right next to the launcher,
139
+ // but with the new File System API, files won't be moved around unnecessarily.
140
+ if (handlerHasImport) {
141
+ const { fsPath } = handlerFile;
142
+ const encoding = 'utf-8';
143
+ // This is the true directory of the user-provided request handler in the
144
+ // source files, so that's what we will use as an import path in the launcher.
145
+ const locationPrefix = path_1.relative(entry, outputPath);
146
+ let handlerContent = await fs_extra_1.default.readFile(fsPath, encoding);
147
+ const importPaths = [
148
+ // This is the full entrypoint path, like `./api/test.py`. In our tests
149
+ // Python didn't support importing from a parent directory without using different
150
+ // code in the launcher that registers it as a location for modules and then changing
151
+ // the importing syntax, but continuing to import it like before seems to work. If
152
+ // other languages need this, we should consider excluding Python explicitly.
153
+ // `./${entrypoint}`,
154
+ // This is the entrypoint path without extension, like `api/test`
155
+ entrypoint.slice(0, -ext.length),
156
+ ];
157
+ // Generate a list of regular expressions that we can use for
158
+ // finding matches, but only allow matches if the import path is
159
+ // wrapped inside single (') or double quotes (").
160
+ const patterns = importPaths.map(path => {
161
+ // eslint-disable-next-line no-useless-escape
162
+ return new RegExp(`('|")(${path.replace(/\./g, '\\.')})('|")`, 'g');
163
+ });
164
+ let replacedMatch = null;
165
+ for (const pattern of patterns) {
166
+ const newContent = handlerContent.replace(pattern, (_, p1, p2, p3) => {
167
+ return `${p1}${path_1.join(locationPrefix, p2)}${p3}`;
169
168
  });
170
- // If we have already tracked a directory that was newly
171
- // added that sits above the file or directory that we're
172
- // looking at, we don't need to add more entries to the list
173
- // because when the parent will get removed in the future,
174
- // all of its children (and therefore the path we're looking at)
175
- // will automatically get removed anyways.
176
- if (hasParentDir) {
177
- continue;
178
- }
179
- if (isNewDir) {
180
- newDirectoriesEntrypoint.push(dirPath);
181
- }
182
- else {
183
- newFilesEntrypoint.push(path);
169
+ if (newContent !== handlerContent) {
170
+ _1.debug(`Replaced "${pattern}" inside "${entry}" to ensure correct import of user-provided request handler`);
171
+ handlerContent = newContent;
172
+ replacedMatch = true;
184
173
  }
185
174
  }
175
+ if (!replacedMatch) {
176
+ new Error(`No replacable matches for "${importPaths[0]}" or "${importPaths[1]}" found in "${fsPath}"`);
177
+ }
178
+ await fs_extra_1.default.writeFile(entry, handlerContent, encoding);
179
+ }
180
+ else {
181
+ await fs_extra_1.default.copy(handlerFile.fsPath, entry);
186
182
  }
183
+ // Legacy Runtimes based on interpreted languages will create a new launcher file
184
+ // for every entrypoint, but they will create each one inside `workPath`, which means that
185
+ // the launcher for one entrypoint will overwrite the launcher provided for the previous
186
+ // entrypoint. That's why, above, we copy the file contents into the new destination (and
187
+ // optionally transform them along the way), instead of linking. We then also want to remove
188
+ // the copy origin right here, so that the `workPath` doesn't contain a useless launcher file
189
+ // once the build has finished running.
190
+ await fs_extra_1.default.remove(handlerFile.fsPath);
191
+ _1.debug(`Removed temporary file "${handlerFile.fsPath}"`);
187
192
  const nft = `${entry}.nft.json`;
188
193
  const json = JSON.stringify({
189
194
  version: 2,
@@ -201,16 +206,7 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
201
206
  })
202
207
  .filter(Boolean),
203
208
  });
204
- await fs_extra_1.default.ensureDir(path_1.dirname(nft));
205
209
  await fs_extra_1.default.writeFile(nft, json);
206
- // Extend the list of directories and files that were created by the
207
- // Legacy Runtime with the list of directories and files that were
208
- // created for the entrypoint that was just processed above.
209
- newPathsRuntime = new Set([
210
- ...newPathsRuntime,
211
- ...newFilesEntrypoint,
212
- ...newDirectoriesEntrypoint,
213
- ]);
214
210
  // Add an entry that will later on be added to the `functions-manifest.json`
215
211
  // file that is placed inside of the `.output` directory.
216
212
  pages[normalize_path_1.normalizePath(entryPath)] = {
@@ -226,18 +222,6 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
226
222
  allowQuery: output.allowQuery,
227
223
  };
228
224
  }
229
- // A list of all the files that were created by the Legacy Runtime,
230
- // which we'd like to remove from the File System.
231
- const toRemove = Array.from(newPathsRuntime).map(path => {
232
- _1.debug(`Removing ${path} as part of cleanup`);
233
- return fs_extra_1.default.remove(path);
234
- });
235
- // Once all the entrypoints have been processed, we'd like to
236
- // remove all the files from `workPath` that originally weren't present
237
- // before the Legacy Runtime began running, because the `workPath`
238
- // is nowadays the directory in which the user keeps their source code, since
239
- // we're no longer running separate parallel builds for every Legacy Runtime.
240
- await Promise.all(toRemove);
241
225
  // Add any Serverless Functions that were exposed by the Legacy Runtime
242
226
  // to the `functions-manifest.json` file provided in `.output`.
243
227
  await updateFunctionsManifest({ workPath, pages });
package/dist/index.js CHANGED
@@ -32752,16 +32752,15 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
32752
32752
  }
32753
32753
  const pages = {};
32754
32754
  const pluginName = packageName.replace('vercel-plugin-', '');
32755
- const traceDir = path_1.join(workPath, `.output`, `inputs`,
32755
+ const outputPath = path_1.join(workPath, '.output');
32756
+ const traceDir = path_1.join(outputPath, `inputs`,
32756
32757
  // Legacy Runtimes can only provide API Routes, so that's
32757
32758
  // why we can use this prefix for all of them. Here, we have to
32758
32759
  // make sure to not use a cryptic hash name, because people
32759
32760
  // need to be able to easily inspect the output.
32760
32761
  `api-routes-${pluginName}`);
32761
32762
  await fs_extra_1.default.ensureDir(traceDir);
32762
- let newPathsRuntime = new Set();
32763
- const entryDir = path_1.join('.output', 'server', 'pages');
32764
- const entryRoot = path_1.join(workPath, entryDir);
32763
+ const entryRoot = path_1.join(outputPath, 'server', 'pages');
32765
32764
  for (const entrypoint of Object.keys(entrypoints)) {
32766
32765
  const { output } = await buildRuntime({
32767
32766
  files: sourceFilesPreBuild,
@@ -32775,13 +32774,6 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
32775
32774
  skipDownload: true,
32776
32775
  },
32777
32776
  });
32778
- // Legacy Runtimes tend to pollute the `workPath` with compiled results,
32779
- // because the `workPath` used to be a place that was a place where they could
32780
- // just put anything, but nowadays it's the working directory of the `vercel build`
32781
- // command, which is the place where the developer keeps their source files,
32782
- // so we don't want to pollute this space unnecessarily. That means we have to clean
32783
- // up files that were created by the build, which is done further below.
32784
- const sourceFilesAfterBuild = await getSourceFiles(workPath, ignoreFilter);
32785
32777
  // @ts-ignore This symbol is a private API
32786
32778
  const lambdaFiles = output[lambda_1.FILES_SYMBOL];
32787
32779
  // When deploying, the `files` that are passed to the Legacy Runtimes already
@@ -32795,6 +32787,7 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
32795
32787
  }
32796
32788
  let handlerFileBase = output.handler;
32797
32789
  let handlerFile = lambdaFiles[handlerFileBase];
32790
+ let handlerHasImport = false;
32798
32791
  const { handler } = output;
32799
32792
  const handlerMethod = handler.split('.').pop();
32800
32793
  const handlerFileName = handler.replace(`.${handlerMethod}`, '');
@@ -32806,6 +32799,7 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
32806
32799
  if (!handlerFile) {
32807
32800
  handlerFileBase = handlerFileName + ext;
32808
32801
  handlerFile = lambdaFiles[handlerFileBase];
32802
+ handlerHasImport = true;
32809
32803
  }
32810
32804
  if (!handlerFile || !handlerFile.fsPath) {
32811
32805
  throw new Error(`Could not find a handler file. Please ensure that \`files\` for the returned \`Lambda\` contains an \`FileFsRef\` named "${handlerFileBase}" with a valid \`fsPath\`.`);
@@ -32814,58 +32808,69 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
32814
32808
  const entryBase = path_1.basename(entrypoint).replace(ext, handlerExtName);
32815
32809
  const entryPath = path_1.join(path_1.dirname(entrypoint), entryBase);
32816
32810
  const entry = path_1.join(entryRoot, entryPath);
32817
- // We never want to link here, only copy, because the launcher
32818
- // file often has the same name for every entrypoint, which means that
32819
- // every build for every entrypoint overwrites the launcher of the previous
32820
- // one, so linking would end with a broken reference.
32811
+ // Create the parent directory of the API Route that will be created
32812
+ // for the current entrypoint inside of `.output/server/pages/api`.
32821
32813
  await fs_extra_1.default.ensureDir(path_1.dirname(entry));
32822
- await fs_extra_1.default.copy(handlerFile.fsPath, entry);
32823
- const newFilesEntrypoint = [];
32824
- const newDirectoriesEntrypoint = [];
32825
- const preBuildFiles = Object.values(sourceFilesPreBuild).map(file => {
32826
- return file.fsPath;
32827
- });
32828
- // Generate a list of directories and files that weren't present
32829
- // before the entrypoint was processed by the Legacy Runtime, so
32830
- // that we can perform a cleanup later. We need to divide into files
32831
- // and directories because only cleaning up files might leave empty
32832
- // directories, and listing directories separately also speeds up the
32833
- // build because we can just delete them, which wipes all of their nested
32834
- // paths, instead of iterating through all files that should be deleted.
32835
- for (const file in sourceFilesAfterBuild) {
32836
- if (!sourceFilesPreBuild[file]) {
32837
- const path = sourceFilesAfterBuild[file].fsPath;
32838
- const dirPath = path_1.dirname(path);
32839
- // If none of the files that were present before the entrypoint
32840
- // was processed are contained within the directory we're looking
32841
- // at right now, then we know it's a newly added directory
32842
- // and it can therefore be removed later on.
32843
- const isNewDir = !preBuildFiles.some(filePath => {
32844
- return path_1.dirname(filePath).startsWith(dirPath);
32845
- });
32846
- // Check out the list of tracked directories that were
32847
- // newly added and see if one of them contains the path
32848
- // we're looking at.
32849
- const hasParentDir = newDirectoriesEntrypoint.some(dir => {
32850
- return path.startsWith(dir);
32814
+ // For compiled languages, the launcher file will be binary and therefore
32815
+ // won't try to import a user-provided request handler (instead, it will
32816
+ // contain it). But for interpreted languages, the launcher might try to
32817
+ // load a user-provided request handler from the source file instead of bundling
32818
+ // it, so we have to adjust the import statement inside the launcher to point
32819
+ // to the respective source file. Previously, Legacy Runtimes simply expected
32820
+ // the user-provided request-handler to be copied right next to the launcher,
32821
+ // but with the new File System API, files won't be moved around unnecessarily.
32822
+ if (handlerHasImport) {
32823
+ const { fsPath } = handlerFile;
32824
+ const encoding = 'utf-8';
32825
+ // This is the true directory of the user-provided request handler in the
32826
+ // source files, so that's what we will use as an import path in the launcher.
32827
+ const locationPrefix = path_1.relative(entry, outputPath);
32828
+ let handlerContent = await fs_extra_1.default.readFile(fsPath, encoding);
32829
+ const importPaths = [
32830
+ // This is the full entrypoint path, like `./api/test.py`. In our tests
32831
+ // Python didn't support importing from a parent directory without using different
32832
+ // code in the launcher that registers it as a location for modules and then changing
32833
+ // the importing syntax, but continuing to import it like before seems to work. If
32834
+ // other languages need this, we should consider excluding Python explicitly.
32835
+ // `./${entrypoint}`,
32836
+ // This is the entrypoint path without extension, like `api/test`
32837
+ entrypoint.slice(0, -ext.length),
32838
+ ];
32839
+ // Generate a list of regular expressions that we can use for
32840
+ // finding matches, but only allow matches if the import path is
32841
+ // wrapped inside single (') or double quotes (").
32842
+ const patterns = importPaths.map(path => {
32843
+ // eslint-disable-next-line no-useless-escape
32844
+ return new RegExp(`('|")(${path.replace(/\./g, '\\.')})('|")`, 'g');
32845
+ });
32846
+ let replacedMatch = null;
32847
+ for (const pattern of patterns) {
32848
+ const newContent = handlerContent.replace(pattern, (_, p1, p2, p3) => {
32849
+ return `${p1}${path_1.join(locationPrefix, p2)}${p3}`;
32851
32850
  });
32852
- // If we have already tracked a directory that was newly
32853
- // added that sits above the file or directory that we're
32854
- // looking at, we don't need to add more entries to the list
32855
- // because when the parent will get removed in the future,
32856
- // all of its children (and therefore the path we're looking at)
32857
- // will automatically get removed anyways.
32858
- if (hasParentDir) {
32859
- continue;
32860
- }
32861
- if (isNewDir) {
32862
- newDirectoriesEntrypoint.push(dirPath);
32863
- }
32864
- else {
32865
- newFilesEntrypoint.push(path);
32851
+ if (newContent !== handlerContent) {
32852
+ _1.debug(`Replaced "${pattern}" inside "${entry}" to ensure correct import of user-provided request handler`);
32853
+ handlerContent = newContent;
32854
+ replacedMatch = true;
32866
32855
  }
32867
32856
  }
32857
+ if (!replacedMatch) {
32858
+ new Error(`No replacable matches for "${importPaths[0]}" or "${importPaths[1]}" found in "${fsPath}"`);
32859
+ }
32860
+ await fs_extra_1.default.writeFile(entry, handlerContent, encoding);
32861
+ }
32862
+ else {
32863
+ await fs_extra_1.default.copy(handlerFile.fsPath, entry);
32868
32864
  }
32865
+ // Legacy Runtimes based on interpreted languages will create a new launcher file
32866
+ // for every entrypoint, but they will create each one inside `workPath`, which means that
32867
+ // the launcher for one entrypoint will overwrite the launcher provided for the previous
32868
+ // entrypoint. That's why, above, we copy the file contents into the new destination (and
32869
+ // optionally transform them along the way), instead of linking. We then also want to remove
32870
+ // the copy origin right here, so that the `workPath` doesn't contain a useless launcher file
32871
+ // once the build has finished running.
32872
+ await fs_extra_1.default.remove(handlerFile.fsPath);
32873
+ _1.debug(`Removed temporary file "${handlerFile.fsPath}"`);
32869
32874
  const nft = `${entry}.nft.json`;
32870
32875
  const json = JSON.stringify({
32871
32876
  version: 2,
@@ -32883,16 +32888,7 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
32883
32888
  })
32884
32889
  .filter(Boolean),
32885
32890
  });
32886
- await fs_extra_1.default.ensureDir(path_1.dirname(nft));
32887
32891
  await fs_extra_1.default.writeFile(nft, json);
32888
- // Extend the list of directories and files that were created by the
32889
- // Legacy Runtime with the list of directories and files that were
32890
- // created for the entrypoint that was just processed above.
32891
- newPathsRuntime = new Set([
32892
- ...newPathsRuntime,
32893
- ...newFilesEntrypoint,
32894
- ...newDirectoriesEntrypoint,
32895
- ]);
32896
32892
  // Add an entry that will later on be added to the `functions-manifest.json`
32897
32893
  // file that is placed inside of the `.output` directory.
32898
32894
  pages[normalize_path_1.normalizePath(entryPath)] = {
@@ -32908,18 +32904,6 @@ function convertRuntimeToPlugin(buildRuntime, packageName, ext) {
32908
32904
  allowQuery: output.allowQuery,
32909
32905
  };
32910
32906
  }
32911
- // A list of all the files that were created by the Legacy Runtime,
32912
- // which we'd like to remove from the File System.
32913
- const toRemove = Array.from(newPathsRuntime).map(path => {
32914
- _1.debug(`Removing ${path} as part of cleanup`);
32915
- return fs_extra_1.default.remove(path);
32916
- });
32917
- // Once all the entrypoints have been processed, we'd like to
32918
- // remove all the files from `workPath` that originally weren't present
32919
- // before the Legacy Runtime began running, because the `workPath`
32920
- // is nowadays the directory in which the user keeps their source code, since
32921
- // we're no longer running separate parallel builds for every Legacy Runtime.
32922
- await Promise.all(toRemove);
32923
32907
  // Add any Serverless Functions that were exposed by the Legacy Runtime
32924
32908
  // to the `functions-manifest.json` file provided in `.output`.
32925
32909
  await updateFunctionsManifest({ workPath, pages });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/build-utils",
3
- "version": "2.12.3-canary.39",
3
+ "version": "2.12.3-canary.42",
4
4
  "license": "MIT",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.js",
@@ -49,5 +49,5 @@
49
49
  "typescript": "4.3.4",
50
50
  "yazl": "2.4.3"
51
51
  },
52
- "gitHead": "2c86ac654ccde97426127fd0a6ffd04253227c50"
52
+ "gitHead": "d3ef240f6e01fff3d11d0499c9aaf892968748e3"
53
53
  }