@jsenv/core 41.0.3 → 41.0.4

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.
@@ -3809,2481 +3809,2481 @@ const bufferToEtag = (buffer) => {
3809
3809
  return `"${length.toString(16)}-${hashBase64StringSubset}"`;
3810
3810
  };
3811
3811
 
3812
- // https://nodejs.org/api/packages.html#resolving-user-conditions
3813
- const readCustomConditionsFromProcessArgs = () => {
3814
- if (process.env.IGNORE_PACKAGE_CONDITIONS) {
3815
- return [];
3812
+ const assertImportMap = (value) => {
3813
+ if (value === null) {
3814
+ throw new TypeError(`an importMap must be an object, got null`);
3816
3815
  }
3817
- const packageConditions = [];
3818
- for (const arg of process.execArgv) {
3819
- if (arg.includes("-C=")) {
3820
- const packageCondition = arg.slice(0, "-C=".length);
3821
- packageConditions.push(packageCondition);
3822
- }
3823
- if (arg.includes("--conditions=")) {
3824
- const packageCondition = arg.slice("--conditions=".length);
3825
- packageConditions.push(packageCondition);
3826
- }
3816
+
3817
+ const type = typeof value;
3818
+ if (type !== "object") {
3819
+ throw new TypeError(`an importMap must be an object, received ${value}`);
3827
3820
  }
3828
- return packageConditions;
3829
- };
3830
3821
 
3831
- const asDirectoryUrl = (url) => {
3832
- const { pathname } = new URL(url);
3833
- if (pathname.endsWith("/")) {
3834
- return url;
3822
+ if (Array.isArray(value)) {
3823
+ throw new TypeError(
3824
+ `an importMap must be an object, received array ${value}`,
3825
+ );
3835
3826
  }
3836
- return new URL("./", url).href;
3837
3827
  };
3838
3828
 
3839
- const getParentUrl = (url) => {
3840
- if (url.startsWith("file://")) {
3841
- // With node.js new URL('../', 'file:///C:/').href
3842
- // returns "file:///C:/" instead of "file:///"
3843
- const resource = url.slice("file://".length);
3844
- const slashLastIndex = resource.lastIndexOf("/");
3845
- if (slashLastIndex === -1) {
3846
- return url;
3847
- }
3848
- const lastCharIndex = resource.length - 1;
3849
- if (slashLastIndex === lastCharIndex) {
3850
- const slashBeforeLastIndex = resource.lastIndexOf(
3851
- "/",
3852
- slashLastIndex - 1,
3853
- );
3854
- if (slashBeforeLastIndex === -1) {
3855
- return url;
3856
- }
3857
- return `file://${resource.slice(0, slashBeforeLastIndex + 1)}`;
3858
- }
3829
+ // duplicated from @jsenv/log to avoid the dependency
3830
+ const createDetailedMessage = (message, details = {}) => {
3831
+ let string = `${message}`;
3859
3832
 
3860
- return `file://${resource.slice(0, slashLastIndex + 1)}`;
3861
- }
3862
- return new URL(url.endsWith("/") ? "../" : "./", url).href;
3863
- };
3833
+ Object.keys(details).forEach((key) => {
3834
+ const value = details[key];
3835
+ string += `
3836
+ --- ${key} ---
3837
+ ${
3838
+ Array.isArray(value)
3839
+ ? value.join(`
3840
+ `)
3841
+ : value
3842
+ }`;
3843
+ });
3864
3844
 
3865
- const isValidUrl = (url) => {
3866
- try {
3867
- // eslint-disable-next-line no-new
3868
- new URL(url);
3869
- return true;
3870
- } catch {
3871
- return false;
3872
- }
3845
+ return string;
3873
3846
  };
3874
3847
 
3875
- const urlToFilename = (url) => {
3876
- const { pathname } = new URL(url);
3877
- const pathnameBeforeLastSlash = pathname.endsWith("/")
3878
- ? pathname.slice(0, -1)
3879
- : pathname;
3880
- const slashLastIndex = pathnameBeforeLastSlash.lastIndexOf("/");
3881
- const filename =
3882
- slashLastIndex === -1
3883
- ? pathnameBeforeLastSlash
3884
- : pathnameBeforeLastSlash.slice(slashLastIndex + 1);
3885
- return filename;
3848
+ const hasScheme = (string) => {
3849
+ return /^[a-zA-Z]{2,}:/.test(string);
3886
3850
  };
3887
3851
 
3888
- const urlToExtension = (url) => {
3889
- const filename = urlToFilename(url);
3890
- const dotLastIndex = filename.lastIndexOf(".");
3891
- if (dotLastIndex === -1) return "";
3892
- // if (dotLastIndex === pathname.length - 1) return ""
3893
- const extension = filename.slice(dotLastIndex);
3894
- return extension;
3852
+ const pathnameToParentPathname = (pathname) => {
3853
+ const slashLastIndex = pathname.lastIndexOf("/");
3854
+ if (slashLastIndex === -1) {
3855
+ return "/";
3856
+ }
3857
+
3858
+ return pathname.slice(0, slashLastIndex + 1);
3895
3859
  };
3896
3860
 
3897
- const defaultLookupPackageScope = (url) => {
3898
- let scopeUrl = asDirectoryUrl(url);
3899
- while (scopeUrl !== "file:///") {
3900
- if (scopeUrl.endsWith("node_modules/")) {
3901
- return null;
3902
- }
3903
- const packageJsonUrlObject = new URL("package.json", scopeUrl);
3904
- if (existsSync(packageJsonUrlObject)) {
3905
- return scopeUrl;
3906
- }
3907
- scopeUrl = getParentUrl(scopeUrl);
3908
- }
3909
- return null;
3861
+ const urlToScheme = (urlString) => {
3862
+ const colonIndex = urlString.indexOf(":");
3863
+ if (colonIndex === -1) return "";
3864
+ return urlString.slice(0, colonIndex);
3910
3865
  };
3911
3866
 
3912
- const defaultReadPackageJson = (packageUrl) => {
3913
- const packageJsonFileUrl = new URL("./package.json", packageUrl);
3914
- let packageJsonFileContentBuffer;
3915
- try {
3916
- packageJsonFileContentBuffer = readFileSync(packageJsonFileUrl, "utf8");
3917
- } catch (e) {
3918
- if (e.code === "ENOENT") {
3919
- return null;
3920
- }
3921
- throw e;
3867
+ const urlToOrigin = (urlString) => {
3868
+ const scheme = urlToScheme(urlString);
3869
+
3870
+ if (scheme === "file") {
3871
+ return "file://";
3922
3872
  }
3923
- const packageJsonFileContentString = String(packageJsonFileContentBuffer);
3924
- try {
3925
- const packageJsonFileContentObject = JSON.parse(
3926
- packageJsonFileContentString,
3927
- );
3928
- return packageJsonFileContentObject;
3929
- } catch {
3930
- throw new Error(`Invalid package configuration at ${packageJsonFileUrl}`);
3873
+
3874
+ if (scheme === "http" || scheme === "https") {
3875
+ const secondProtocolSlashIndex = scheme.length + "://".length;
3876
+ const pathnameSlashIndex = urlString.indexOf("/", secondProtocolSlashIndex);
3877
+
3878
+ if (pathnameSlashIndex === -1) return urlString;
3879
+ return urlString.slice(0, pathnameSlashIndex);
3931
3880
  }
3932
- };
3933
3881
 
3934
- // https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/tools/node_modules/eslint/node_modules/%40babel/core/lib/vendor/import-meta-resolve.js#L2473
3882
+ return urlString.slice(0, scheme.length + 1);
3883
+ };
3935
3884
 
3936
- const createInvalidModuleSpecifierError = (
3937
- reason,
3938
- specifier,
3939
- { parentUrl },
3940
- ) => {
3941
- const error = new Error(
3942
- `Invalid module "${specifier}" ${reason} imported from ${fileURLToPath(
3943
- parentUrl,
3944
- )}`,
3945
- );
3946
- error.code = "INVALID_MODULE_SPECIFIER";
3947
- return error;
3885
+ const urlToPathname = (urlString) => {
3886
+ return ressourceToPathname(urlToRessource(urlString));
3948
3887
  };
3949
3888
 
3950
- const createInvalidPackageTargetError = (
3951
- reason,
3952
- target,
3953
- { parentUrl, packageDirectoryUrl, key, isImport },
3954
- ) => {
3955
- let message;
3956
- if (key === ".") {
3957
- message = `Invalid "exports" main target defined in ${fileURLToPath(
3958
- packageDirectoryUrl,
3959
- )}package.json imported from ${fileURLToPath(parentUrl)}; ${reason}`;
3960
- } else {
3961
- message = `Invalid "${
3962
- isImport ? "imports" : "exports"
3963
- }" target ${JSON.stringify(target)} defined for "${key}" in ${fileURLToPath(
3964
- packageDirectoryUrl,
3965
- )}package.json imported from ${fileURLToPath(parentUrl)}; ${reason}`;
3889
+ const urlToRessource = (urlString) => {
3890
+ const scheme = urlToScheme(urlString);
3891
+
3892
+ if (scheme === "file") {
3893
+ return urlString.slice("file://".length);
3966
3894
  }
3967
- const error = new Error(message);
3968
- error.code = "INVALID_PACKAGE_TARGET";
3969
- return error;
3970
- };
3971
3895
 
3972
- const createPackagePathNotExportedError = (
3973
- subpath,
3974
- { parentUrl, packageDirectoryUrl },
3975
- ) => {
3976
- let message;
3977
- if (subpath === ".") {
3978
- message = `No "exports" main defined in ${fileURLToPath(
3979
- packageDirectoryUrl,
3980
- )}package.json imported from ${fileURLToPath(parentUrl)}`;
3981
- } else {
3982
- message = `Package subpath "${subpath}" is not defined by "exports" in ${fileURLToPath(
3983
- packageDirectoryUrl,
3984
- )}package.json imported from ${fileURLToPath(parentUrl)}`;
3896
+ if (scheme === "https" || scheme === "http") {
3897
+ // remove origin
3898
+ const afterProtocol = urlString.slice(scheme.length + "://".length);
3899
+ const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length);
3900
+ return afterProtocol.slice(pathnameSlashIndex);
3985
3901
  }
3986
- const error = new Error(message);
3987
- error.code = "PACKAGE_PATH_NOT_EXPORTED";
3988
- return error;
3989
- };
3990
3902
 
3991
- const createModuleNotFoundError = (specifier, { parentUrl }) => {
3992
- const error = new Error(
3993
- `Cannot find "${specifier}" imported from ${fileURLToPath(parentUrl)}`,
3994
- );
3995
- error.code = "MODULE_NOT_FOUND";
3996
- return error;
3903
+ return urlString.slice(scheme.length + 1);
3997
3904
  };
3998
3905
 
3999
- const createPackageImportNotDefinedError = (
4000
- specifier,
4001
- { parentUrl, packageDirectoryUrl },
4002
- ) => {
4003
- const error = new Error(
4004
- `Package import specifier "${specifier}" is not defined in ${fileURLToPath(
4005
- packageDirectoryUrl,
4006
- )}package.json imported from ${fileURLToPath(parentUrl)}`,
4007
- );
4008
- error.code = "PACKAGE_IMPORT_NOT_DEFINED";
4009
- return error;
3906
+ const ressourceToPathname = (ressource) => {
3907
+ const searchSeparatorIndex = ressource.indexOf("?");
3908
+ return searchSeparatorIndex === -1
3909
+ ? ressource
3910
+ : ressource.slice(0, searchSeparatorIndex);
4010
3911
  };
4011
3912
 
4012
- const isSpecifierForNodeBuiltin = (specifier) => {
4013
- return (
4014
- specifier.startsWith("node:") ||
4015
- NODE_BUILTIN_MODULE_SPECIFIERS.includes(specifier)
4016
- );
4017
- };
3913
+ // could be useful: https://url.spec.whatwg.org/#url-miscellaneous
4018
3914
 
4019
- const NODE_BUILTIN_MODULE_SPECIFIERS = [
4020
- "assert",
4021
- "assert/strict",
4022
- "async_hooks",
4023
- "buffer_ieee754",
4024
- "buffer",
4025
- "child_process",
4026
- "cluster",
4027
- "console",
4028
- "constants",
4029
- "crypto",
4030
- "_debugger",
4031
- "diagnostics_channel",
4032
- "dgram",
4033
- "dns",
4034
- "domain",
4035
- "events",
4036
- "freelist",
4037
- "fs",
4038
- "fsevents",
4039
- "fs/promises",
4040
- "_http_agent",
4041
- "_http_client",
4042
- "_http_common",
4043
- "_http_incoming",
4044
- "_http_outgoing",
4045
- "_http_server",
4046
- "http",
4047
- "http2",
4048
- "https",
4049
- "inspector",
4050
- "_linklist",
4051
- "module",
4052
- "net",
4053
- "node-inspect/lib/_inspect",
4054
- "node-inspect/lib/internal/inspect_client",
4055
- "node-inspect/lib/internal/inspect_repl",
4056
- "os",
4057
- "path",
4058
- "perf_hooks",
4059
- "process",
4060
- "punycode",
4061
- "querystring",
4062
- "readline",
4063
- "repl",
4064
- "smalloc",
4065
- "sqlite",
4066
- "_stream_duplex",
4067
- "_stream_transform",
4068
- "_stream_wrap",
4069
- "_stream_passthrough",
4070
- "_stream_readable",
4071
- "_stream_writable",
4072
- "stream",
4073
- "stream/promises",
4074
- "string_decoder",
4075
- "sys",
4076
- "timers",
4077
- "_tls_common",
4078
- "_tls_legacy",
4079
- "_tls_wrap",
4080
- "tls",
4081
- "trace_events",
4082
- "tty",
4083
- "url",
4084
- "util",
4085
- "v8/tools/arguments",
4086
- "v8/tools/codemap",
4087
- "v8/tools/consarray",
4088
- "v8/tools/csvparser",
4089
- "v8/tools/logreader",
4090
- "v8/tools/profile_view",
4091
- "v8/tools/splaytree",
4092
- "v8",
4093
- "vm",
4094
- "worker_threads",
4095
- "zlib",
4096
- // global is special
4097
- "global",
4098
- ];
4099
3915
 
4100
- /*
4101
- * https://nodejs.org/api/esm.html#resolver-algorithm-specification
4102
- * https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/lib/internal/modules/esm/resolve.js#L1
4103
- * deviations from the spec:
4104
- * - take into account "browser", "module" and "jsnext"
4105
- * - the check for isDirectory -> throw is delayed is descoped to the caller
4106
- * - the call to real path ->
4107
- * delayed to the caller so that we can decide to
4108
- * maintain symlink as facade url when it's outside project directory
4109
- * or use the real path when inside
4110
- */
3916
+ const resolveUrl = (specifier, baseUrl) => {
3917
+ if (baseUrl) {
3918
+ if (typeof baseUrl !== "string") {
3919
+ throw new TypeError(writeBaseUrlMustBeAString({ baseUrl, specifier }));
3920
+ }
3921
+ if (!hasScheme(baseUrl)) {
3922
+ throw new Error(writeBaseUrlMustBeAbsolute({ baseUrl, specifier }));
3923
+ }
3924
+ }
4111
3925
 
4112
- const applyNodeEsmResolution = ({
4113
- specifier,
4114
- parentUrl,
4115
- conditions = [...readCustomConditionsFromProcessArgs(), "node", "import"],
4116
- lookupPackageScope = defaultLookupPackageScope,
4117
- readPackageJson = defaultReadPackageJson,
4118
- preservesSymlink = false,
4119
- }) => {
4120
- const resolution = applyPackageSpecifierResolution(specifier, {
4121
- parentUrl: String(parentUrl),
4122
- conditions,
4123
- lookupPackageScope,
4124
- readPackageJson,
4125
- preservesSymlink,
4126
- });
4127
- const { url } = resolution;
4128
- if (url.startsWith("file:")) {
4129
- if (url.includes("%2F") || url.includes("%5C")) {
4130
- throw createInvalidModuleSpecifierError(
4131
- `must not include encoded "/" or "\\" characters`,
4132
- specifier,
4133
- {
4134
- parentUrl,
4135
- },
4136
- );
3926
+ if (hasScheme(specifier)) {
3927
+ return specifier;
3928
+ }
3929
+
3930
+ if (!baseUrl) {
3931
+ throw new Error(writeBaseUrlRequired({ baseUrl, specifier }));
3932
+ }
3933
+
3934
+ // scheme relative
3935
+ if (specifier.slice(0, 2) === "//") {
3936
+ return `${urlToScheme(baseUrl)}:${specifier}`;
3937
+ }
3938
+
3939
+ // origin relative
3940
+ if (specifier[0] === "/") {
3941
+ return `${urlToOrigin(baseUrl)}${specifier}`;
3942
+ }
3943
+
3944
+ const baseOrigin = urlToOrigin(baseUrl);
3945
+ const basePathname = urlToPathname(baseUrl);
3946
+
3947
+ if (specifier === ".") {
3948
+ const baseDirectoryPathname = pathnameToParentPathname(basePathname);
3949
+ return `${baseOrigin}${baseDirectoryPathname}`;
3950
+ }
3951
+
3952
+ // pathname relative inside
3953
+ if (specifier.slice(0, 2) === "./") {
3954
+ const baseDirectoryPathname = pathnameToParentPathname(basePathname);
3955
+ return `${baseOrigin}${baseDirectoryPathname}${specifier.slice(2)}`;
3956
+ }
3957
+
3958
+ // pathname relative outside
3959
+ if (specifier.slice(0, 3) === "../") {
3960
+ let unresolvedPathname = specifier;
3961
+ const importerFolders = basePathname.split("/");
3962
+ importerFolders.pop();
3963
+
3964
+ while (unresolvedPathname.slice(0, 3) === "../") {
3965
+ unresolvedPathname = unresolvedPathname.slice(3);
3966
+ // when there is no folder left to resolved
3967
+ // we just ignore '../'
3968
+ if (importerFolders.length) {
3969
+ importerFolders.pop();
3970
+ }
4137
3971
  }
4138
- return resolution;
3972
+
3973
+ const resolvedPathname = `${importerFolders.join(
3974
+ "/",
3975
+ )}/${unresolvedPathname}`;
3976
+ return `${baseOrigin}${resolvedPathname}`;
4139
3977
  }
4140
- return resolution;
3978
+
3979
+ // bare
3980
+ if (basePathname === "") {
3981
+ return `${baseOrigin}/${specifier}`;
3982
+ }
3983
+ if (basePathname[basePathname.length] === "/") {
3984
+ return `${baseOrigin}${basePathname}${specifier}`;
3985
+ }
3986
+ return `${baseOrigin}${pathnameToParentPathname(basePathname)}${specifier}`;
4141
3987
  };
4142
3988
 
4143
- const createResolutionResult = (data) => {
4144
- return data;
3989
+ const writeBaseUrlMustBeAString = ({
3990
+ baseUrl,
3991
+ specifier,
3992
+ }) => `baseUrl must be a string.
3993
+ --- base url ---
3994
+ ${baseUrl}
3995
+ --- specifier ---
3996
+ ${specifier}`;
3997
+
3998
+ const writeBaseUrlMustBeAbsolute = ({
3999
+ baseUrl,
4000
+ specifier,
4001
+ }) => `baseUrl must be absolute.
4002
+ --- base url ---
4003
+ ${baseUrl}
4004
+ --- specifier ---
4005
+ ${specifier}`;
4006
+
4007
+ const writeBaseUrlRequired = ({
4008
+ baseUrl,
4009
+ specifier,
4010
+ }) => `baseUrl required to resolve relative specifier.
4011
+ --- base url ---
4012
+ ${baseUrl}
4013
+ --- specifier ---
4014
+ ${specifier}`;
4015
+
4016
+ const tryUrlResolution = (string, url) => {
4017
+ const result = resolveUrl(string, url);
4018
+ return hasScheme(result) ? result : null;
4145
4019
  };
4146
4020
 
4147
- const applyPackageSpecifierResolution = (specifier, resolutionContext) => {
4148
- const { parentUrl } = resolutionContext;
4149
- // relative specifier
4021
+ const resolveSpecifier = (specifier, importer) => {
4150
4022
  if (
4023
+ specifier === "." ||
4151
4024
  specifier[0] === "/" ||
4152
4025
  specifier.startsWith("./") ||
4153
4026
  specifier.startsWith("../")
4154
4027
  ) {
4155
- if (specifier[0] !== "/") {
4156
- const browserFieldResolution = applyBrowserFieldResolution(
4157
- specifier,
4158
- resolutionContext,
4159
- );
4160
- if (browserFieldResolution) {
4161
- return browserFieldResolution;
4162
- }
4163
- }
4164
- return createResolutionResult({
4165
- type: "relative_specifier",
4166
- url: new URL(specifier, parentUrl).href,
4167
- });
4028
+ return resolveUrl(specifier, importer);
4168
4029
  }
4169
- if (specifier[0] === "#") {
4170
- return applyPackageImportsResolution(specifier, resolutionContext);
4171
- }
4172
- try {
4173
- const urlObject = new URL(specifier);
4174
- if (specifier.startsWith("node:")) {
4175
- return createResolutionResult({
4176
- type: "node_builtin_specifier",
4177
- url: specifier,
4178
- });
4179
- }
4180
- return createResolutionResult({
4181
- type: "absolute_specifier",
4182
- url: urlObject.href,
4183
- });
4184
- } catch {
4185
- // bare specifier
4186
- const browserFieldResolution = applyBrowserFieldResolution(
4187
- specifier,
4188
- resolutionContext,
4189
- );
4190
- if (browserFieldResolution) {
4191
- return browserFieldResolution;
4192
- }
4193
- const packageResolution = applyPackageResolve(specifier, resolutionContext);
4194
- const search = new URL(specifier, "file:///").search;
4195
- if (search && !new URL(packageResolution.url).search) {
4196
- packageResolution.url = `${packageResolution.url}${search}`;
4197
- }
4198
- return packageResolution;
4030
+
4031
+ if (hasScheme(specifier)) {
4032
+ return specifier;
4199
4033
  }
4034
+
4035
+ return null;
4200
4036
  };
4201
4037
 
4202
- const applyBrowserFieldResolution = (specifier, resolutionContext) => {
4203
- const { parentUrl, conditions, lookupPackageScope, readPackageJson } =
4204
- resolutionContext;
4205
- const browserCondition = conditions.includes("browser");
4206
- if (!browserCondition) {
4207
- return null;
4208
- }
4209
- const packageDirectoryUrl = lookupPackageScope(parentUrl);
4210
- if (!packageDirectoryUrl) {
4211
- return null;
4212
- }
4213
- const packageJson = readPackageJson(packageDirectoryUrl);
4214
- if (!packageJson) {
4215
- return null;
4216
- }
4217
- const { browser } = packageJson;
4218
- if (!browser) {
4219
- return null;
4038
+ const applyImportMap = ({
4039
+ importMap,
4040
+ specifier,
4041
+ importer,
4042
+ createBareSpecifierError = ({ specifier, importer }) => {
4043
+ return new Error(
4044
+ createDetailedMessage(`Unmapped bare specifier.`, {
4045
+ specifier,
4046
+ importer,
4047
+ }),
4048
+ );
4049
+ },
4050
+ onImportMapping = () => {},
4051
+ }) => {
4052
+ assertImportMap(importMap);
4053
+ if (typeof specifier !== "string") {
4054
+ throw new TypeError(
4055
+ createDetailedMessage("specifier must be a string.", {
4056
+ specifier,
4057
+ importer,
4058
+ }),
4059
+ );
4220
4060
  }
4221
- if (typeof browser !== "object") {
4222
- return null;
4061
+ if (importer) {
4062
+ if (typeof importer !== "string") {
4063
+ throw new TypeError(
4064
+ createDetailedMessage("importer must be a string.", {
4065
+ importer,
4066
+ specifier,
4067
+ }),
4068
+ );
4069
+ }
4070
+ if (!hasScheme(importer)) {
4071
+ throw new Error(
4072
+ createDetailedMessage(`importer must be an absolute url.`, {
4073
+ importer,
4074
+ specifier,
4075
+ }),
4076
+ );
4077
+ }
4223
4078
  }
4224
- let url;
4225
- if (specifier.startsWith(".")) {
4226
- const specifierUrl = new URL(specifier, parentUrl).href;
4227
- const specifierRelativeUrl = specifierUrl.slice(packageDirectoryUrl.length);
4228
- const secifierRelativeNotation = `./${specifierRelativeUrl}`;
4229
- const browserMapping = browser[secifierRelativeNotation];
4230
- if (typeof browserMapping === "string") {
4231
- url = new URL(browserMapping, packageDirectoryUrl).href;
4232
- } else if (browserMapping === false) {
4233
- url = `file:///@ignore/${specifierUrl.slice("file:///")}`;
4079
+
4080
+ const specifierUrl = resolveSpecifier(specifier, importer);
4081
+ const specifierNormalized = specifierUrl || specifier;
4082
+
4083
+ const { scopes } = importMap;
4084
+ if (scopes && importer) {
4085
+ const scopeSpecifierMatching = Object.keys(scopes).find(
4086
+ (scopeSpecifier) => {
4087
+ return (
4088
+ scopeSpecifier === importer ||
4089
+ specifierIsPrefixOf(scopeSpecifier, importer)
4090
+ );
4091
+ },
4092
+ );
4093
+ if (scopeSpecifierMatching) {
4094
+ const scopeMappings = scopes[scopeSpecifierMatching];
4095
+ const mappingFromScopes = applyMappings(
4096
+ scopeMappings,
4097
+ specifierNormalized,
4098
+ scopeSpecifierMatching,
4099
+ onImportMapping,
4100
+ );
4101
+ if (mappingFromScopes !== null) {
4102
+ return mappingFromScopes;
4103
+ }
4234
4104
  }
4235
- } else {
4236
- const browserMapping = browser[specifier];
4237
- if (typeof browserMapping === "string") {
4238
- url = new URL(browserMapping, packageDirectoryUrl).href;
4239
- } else if (browserMapping === false) {
4240
- url = `file:///@ignore/${specifier}`;
4105
+ }
4106
+
4107
+ const { imports } = importMap;
4108
+ if (imports) {
4109
+ const mappingFromImports = applyMappings(
4110
+ imports,
4111
+ specifierNormalized,
4112
+ undefined,
4113
+ onImportMapping,
4114
+ );
4115
+ if (mappingFromImports !== null) {
4116
+ return mappingFromImports;
4241
4117
  }
4242
4118
  }
4243
- if (url) {
4244
- return createResolutionResult({
4245
- type: "field:browser",
4246
- isMain: true,
4247
- packageDirectoryUrl,
4248
- packageJson,
4249
- url,
4250
- });
4119
+
4120
+ if (specifierUrl) {
4121
+ return specifierUrl;
4251
4122
  }
4252
- return null;
4123
+
4124
+ throw createBareSpecifierError({ specifier, importer });
4253
4125
  };
4254
4126
 
4255
- const applyPackageImportsResolution = (
4256
- internalSpecifier,
4257
- resolutionContext,
4127
+ const applyMappings = (
4128
+ mappings,
4129
+ specifierNormalized,
4130
+ scope,
4131
+ onImportMapping,
4258
4132
  ) => {
4259
- const { parentUrl, lookupPackageScope, readPackageJson } = resolutionContext;
4260
- if (internalSpecifier === "#" || internalSpecifier.startsWith("#/")) {
4261
- throw createInvalidModuleSpecifierError(
4262
- "not a valid internal imports specifier name",
4263
- internalSpecifier,
4264
- resolutionContext,
4265
- );
4266
- }
4267
- const packageDirectoryUrl = lookupPackageScope(parentUrl);
4268
- if (packageDirectoryUrl !== null) {
4269
- const packageJson = readPackageJson(packageDirectoryUrl);
4270
- const { imports } = packageJson;
4271
- if (imports !== null && typeof imports === "object") {
4272
- const resolved = applyPackageImportsExportsResolution(internalSpecifier, {
4273
- ...resolutionContext,
4274
- packageDirectoryUrl,
4275
- packageJson,
4276
- isImport: true,
4133
+ const specifierCandidates = Object.keys(mappings);
4134
+
4135
+ let i = 0;
4136
+ while (i < specifierCandidates.length) {
4137
+ const specifierCandidate = specifierCandidates[i];
4138
+ i++;
4139
+ if (specifierCandidate === specifierNormalized) {
4140
+ const address = mappings[specifierCandidate];
4141
+ onImportMapping({
4142
+ scope,
4143
+ from: specifierCandidate,
4144
+ to: address,
4145
+ before: specifierNormalized,
4146
+ after: address,
4277
4147
  });
4278
- if (resolved) {
4279
- return resolved;
4280
- }
4148
+ return address;
4149
+ }
4150
+ if (specifierIsPrefixOf(specifierCandidate, specifierNormalized)) {
4151
+ const address = mappings[specifierCandidate];
4152
+ const afterSpecifier = specifierNormalized.slice(
4153
+ specifierCandidate.length,
4154
+ );
4155
+ const addressFinal = tryUrlResolution(afterSpecifier, address);
4156
+ onImportMapping({
4157
+ scope,
4158
+ from: specifierCandidate,
4159
+ to: address,
4160
+ before: specifierNormalized,
4161
+ after: addressFinal,
4162
+ });
4163
+ return addressFinal;
4281
4164
  }
4282
4165
  }
4283
- throw createPackageImportNotDefinedError(internalSpecifier, {
4284
- ...resolutionContext,
4285
- packageDirectoryUrl,
4286
- });
4166
+
4167
+ return null;
4287
4168
  };
4288
4169
 
4289
- const applyPackageResolve = (packageSpecifier, resolutionContext) => {
4290
- const { parentUrl, conditions, readPackageJson, preservesSymlink, isImport } =
4291
- resolutionContext;
4292
- if (packageSpecifier === "") {
4293
- throw new Error("invalid module specifier");
4170
+ const specifierIsPrefixOf = (specifierHref, href) => {
4171
+ return (
4172
+ specifierHref[specifierHref.length - 1] === "/" &&
4173
+ href.startsWith(specifierHref)
4174
+ );
4175
+ };
4176
+
4177
+ // https://github.com/systemjs/systemjs/blob/89391f92dfeac33919b0223bbf834a1f4eea5750/src/common.js#L136
4178
+
4179
+ const composeTwoImportMaps = (leftImportMap, rightImportMap) => {
4180
+ assertImportMap(leftImportMap);
4181
+ assertImportMap(rightImportMap);
4182
+
4183
+ const importMap = {};
4184
+
4185
+ const leftImports = leftImportMap.imports;
4186
+ const rightImports = rightImportMap.imports;
4187
+ const leftHasImports = Boolean(leftImports);
4188
+ const rightHasImports = Boolean(rightImports);
4189
+ if (leftHasImports && rightHasImports) {
4190
+ importMap.imports = composeTwoMappings(leftImports, rightImports);
4191
+ } else if (leftHasImports) {
4192
+ importMap.imports = { ...leftImports };
4193
+ } else if (rightHasImports) {
4194
+ importMap.imports = { ...rightImports };
4294
4195
  }
4295
- if (
4296
- conditions.includes("node") &&
4297
- isSpecifierForNodeBuiltin(packageSpecifier)
4298
- ) {
4299
- return createResolutionResult({
4300
- type: "node_builtin_specifier",
4301
- url: `node:${packageSpecifier}`,
4302
- });
4196
+
4197
+ const leftScopes = leftImportMap.scopes;
4198
+ const rightScopes = rightImportMap.scopes;
4199
+ const leftHasScopes = Boolean(leftScopes);
4200
+ const rightHasScopes = Boolean(rightScopes);
4201
+ if (leftHasScopes && rightHasScopes) {
4202
+ importMap.scopes = composeTwoScopes(
4203
+ leftScopes,
4204
+ rightScopes,
4205
+ importMap.imports || {},
4206
+ );
4207
+ } else if (leftHasScopes) {
4208
+ importMap.scopes = { ...leftScopes };
4209
+ } else if (rightHasScopes) {
4210
+ importMap.scopes = { ...rightScopes };
4303
4211
  }
4304
- let { packageName, packageSubpath } = parsePackageSpecifier(packageSpecifier);
4305
- if (
4306
- packageName[0] === "." ||
4307
- packageName.includes("\\") ||
4308
- packageName.includes("%")
4309
- ) {
4310
- throw createInvalidModuleSpecifierError(
4311
- `is not a valid package name`,
4312
- packageName,
4313
- resolutionContext,
4314
- );
4315
- }
4316
- if (isImport && packageSubpath.endsWith("/")) {
4317
- throw new Error("invalid module specifier");
4318
- }
4319
- const questionCharIndex = packageName.indexOf("?");
4320
- if (questionCharIndex > -1) {
4321
- packageName = packageName.slice(0, questionCharIndex);
4322
- }
4323
- const selfResolution = applyPackageSelfResolution(packageSubpath, {
4324
- ...resolutionContext,
4325
- packageName,
4326
- });
4327
- if (selfResolution) {
4328
- return selfResolution;
4329
- }
4330
- let currentUrl = parentUrl;
4331
- while (currentUrl !== "file:///") {
4332
- const packageDirectoryFacadeUrl = new URL(
4333
- `node_modules/${packageName}/`,
4334
- currentUrl,
4335
- ).href;
4336
- if (!existsSync(new URL(packageDirectoryFacadeUrl))) {
4337
- currentUrl = getParentUrl(currentUrl);
4338
- continue;
4339
- }
4340
- const packageDirectoryUrl = preservesSymlink
4341
- ? packageDirectoryFacadeUrl
4342
- : resolvePackageSymlink(packageDirectoryFacadeUrl);
4343
- const packageJson = readPackageJson(packageDirectoryUrl);
4344
- if (packageJson !== null) {
4345
- const { exports: exports$1 } = packageJson;
4346
- if (exports$1 !== null && exports$1 !== undefined) {
4347
- return applyPackageExportsResolution(packageSubpath, {
4348
- ...resolutionContext,
4349
- packageDirectoryUrl,
4350
- packageJson,
4351
- exports: exports$1,
4352
- });
4353
- }
4354
- }
4355
- return applyLegacySubpathResolution(packageSubpath, {
4356
- ...resolutionContext,
4357
- packageDirectoryUrl,
4358
- packageJson,
4359
- });
4360
- }
4361
- throw createModuleNotFoundError(packageName, resolutionContext);
4212
+
4213
+ return importMap;
4362
4214
  };
4363
4215
 
4364
- const applyPackageSelfResolution = (packageSubpath, resolutionContext) => {
4365
- const { parentUrl, packageName, lookupPackageScope, readPackageJson } =
4366
- resolutionContext;
4367
- const packageDirectoryUrl = lookupPackageScope(parentUrl);
4368
- if (!packageDirectoryUrl) {
4369
- return undefined;
4370
- }
4371
- const packageJson = readPackageJson(packageDirectoryUrl);
4372
- if (!packageJson) {
4373
- return undefined;
4374
- }
4375
- if (packageJson.name !== packageName) {
4376
- return undefined;
4377
- }
4378
- const { exports: exports$1 } = packageJson;
4379
- if (!exports$1) {
4380
- const subpathResolution = applyLegacySubpathResolution(packageSubpath, {
4381
- ...resolutionContext,
4382
- packageDirectoryUrl,
4383
- packageJson,
4384
- });
4385
- if (subpathResolution && subpathResolution.type !== "subpath") {
4386
- return subpathResolution;
4216
+ const composeTwoMappings = (leftMappings, rightMappings) => {
4217
+ const mappings = {};
4218
+
4219
+ Object.keys(leftMappings).forEach((leftSpecifier) => {
4220
+ if (objectHasKey(rightMappings, leftSpecifier)) {
4221
+ // will be overidden
4222
+ return;
4387
4223
  }
4388
- return undefined;
4389
- }
4390
- return applyPackageExportsResolution(packageSubpath, {
4391
- ...resolutionContext,
4392
- packageDirectoryUrl,
4393
- packageJson,
4224
+ const leftAddress = leftMappings[leftSpecifier];
4225
+ const rightSpecifier = Object.keys(rightMappings).find((rightSpecifier) => {
4226
+ return compareAddressAndSpecifier(leftAddress, rightSpecifier);
4227
+ });
4228
+ mappings[leftSpecifier] = rightSpecifier
4229
+ ? rightMappings[rightSpecifier]
4230
+ : leftAddress;
4231
+ });
4232
+
4233
+ Object.keys(rightMappings).forEach((rightSpecifier) => {
4234
+ mappings[rightSpecifier] = rightMappings[rightSpecifier];
4394
4235
  });
4236
+
4237
+ return mappings;
4395
4238
  };
4396
4239
 
4397
- // https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/lib/internal/modules/esm/resolve.js#L642
4398
- const applyPackageExportsResolution = (packageSubpath, resolutionContext) => {
4399
- if (packageSubpath === ".") {
4400
- const mainExport = applyMainExportResolution(resolutionContext);
4401
- if (!mainExport) {
4402
- throw createPackagePathNotExportedError(
4403
- packageSubpath,
4404
- resolutionContext,
4405
- );
4240
+ const objectHasKey = (object, key) =>
4241
+ Object.prototype.hasOwnProperty.call(object, key);
4242
+
4243
+ const compareAddressAndSpecifier = (address, specifier) => {
4244
+ const addressUrl = resolveUrl(address, "file:///");
4245
+ const specifierUrl = resolveUrl(specifier, "file:///");
4246
+ return addressUrl === specifierUrl;
4247
+ };
4248
+
4249
+ const composeTwoScopes = (leftScopes, rightScopes, imports) => {
4250
+ const scopes = {};
4251
+
4252
+ Object.keys(leftScopes).forEach((leftScopeKey) => {
4253
+ if (objectHasKey(rightScopes, leftScopeKey)) {
4254
+ // will be merged
4255
+ scopes[leftScopeKey] = leftScopes[leftScopeKey];
4256
+ return;
4406
4257
  }
4407
- const resolved = applyPackageTargetResolution(mainExport, {
4408
- ...resolutionContext,
4409
- key: ".",
4410
- });
4411
- if (resolved) {
4412
- return resolved;
4258
+ const topLevelSpecifier = Object.keys(imports).find(
4259
+ (topLevelSpecifierCandidate) => {
4260
+ return compareAddressAndSpecifier(
4261
+ leftScopeKey,
4262
+ topLevelSpecifierCandidate,
4263
+ );
4264
+ },
4265
+ );
4266
+ if (topLevelSpecifier) {
4267
+ scopes[imports[topLevelSpecifier]] = leftScopes[leftScopeKey];
4268
+ } else {
4269
+ scopes[leftScopeKey] = leftScopes[leftScopeKey];
4413
4270
  }
4414
- throw createPackagePathNotExportedError(packageSubpath, resolutionContext);
4415
- }
4416
- const packageExportsInfo = readExports(resolutionContext);
4417
- if (
4418
- packageExportsInfo.type === "object" &&
4419
- packageExportsInfo.allKeysAreRelative
4420
- ) {
4421
- const resolved = applyPackageImportsExportsResolution(packageSubpath, {
4422
- ...resolutionContext,
4423
- isImport: false,
4424
- });
4425
- if (resolved) {
4426
- return resolved;
4271
+ });
4272
+
4273
+ Object.keys(rightScopes).forEach((rightScopeKey) => {
4274
+ if (objectHasKey(scopes, rightScopeKey)) {
4275
+ scopes[rightScopeKey] = composeTwoMappings(
4276
+ scopes[rightScopeKey],
4277
+ rightScopes[rightScopeKey],
4278
+ );
4279
+ } else {
4280
+ scopes[rightScopeKey] = {
4281
+ ...rightScopes[rightScopeKey],
4282
+ };
4427
4283
  }
4428
- }
4429
- throw createPackagePathNotExportedError(packageSubpath, resolutionContext);
4284
+ });
4285
+
4286
+ return scopes;
4430
4287
  };
4431
4288
 
4432
- const applyPackageImportsExportsResolution = (matchKey, resolutionContext) => {
4433
- const { packageJson, isImport } = resolutionContext;
4434
- const matchObject = isImport ? packageJson.imports : packageJson.exports;
4289
+ const sortImports = (imports) => {
4290
+ const mappingsSorted = {};
4435
4291
 
4436
- if (!matchKey.includes("*") && matchObject.hasOwnProperty(matchKey)) {
4437
- const target = matchObject[matchKey];
4438
- return applyPackageTargetResolution(target, {
4439
- ...resolutionContext,
4440
- key: matchKey,
4441
- isImport,
4292
+ Object.keys(imports)
4293
+ .sort(compareLengthOrLocaleCompare)
4294
+ .forEach((name) => {
4295
+ mappingsSorted[name] = imports[name];
4442
4296
  });
4443
- }
4444
- const expansionKeys = Object.keys(matchObject)
4445
- .filter((key) => key.split("*").length === 2)
4446
- .sort(comparePatternKeys);
4447
- for (const expansionKey of expansionKeys) {
4448
- const [patternBase, patternTrailer] = expansionKey.split("*");
4449
- if (matchKey === patternBase) continue;
4450
- if (!matchKey.startsWith(patternBase)) continue;
4451
- if (patternTrailer.length > 0) {
4452
- if (!matchKey.endsWith(patternTrailer)) continue;
4453
- if (matchKey.length < expansionKey.length) continue;
4454
- }
4455
- const target = matchObject[expansionKey];
4456
- const subpath = matchKey.slice(
4457
- patternBase.length,
4458
- matchKey.length - patternTrailer.length,
4459
- );
4460
- return applyPackageTargetResolution(target, {
4461
- ...resolutionContext,
4462
- key: matchKey,
4463
- subpath,
4464
- pattern: true,
4465
- isImport,
4297
+
4298
+ return mappingsSorted;
4299
+ };
4300
+
4301
+ const sortScopes = (scopes) => {
4302
+ const scopesSorted = {};
4303
+
4304
+ Object.keys(scopes)
4305
+ .sort(compareLengthOrLocaleCompare)
4306
+ .forEach((scopeSpecifier) => {
4307
+ scopesSorted[scopeSpecifier] = sortImports(scopes[scopeSpecifier]);
4466
4308
  });
4467
- }
4468
- return null;
4309
+
4310
+ return scopesSorted;
4469
4311
  };
4470
4312
 
4471
- const applyPackageTargetResolution = (target, resolutionContext) => {
4472
- const {
4473
- conditions,
4474
- packageDirectoryUrl,
4475
- packageJson,
4476
- key,
4477
- subpath = "",
4478
- pattern = false,
4479
- isImport = false,
4480
- } = resolutionContext;
4313
+ const compareLengthOrLocaleCompare = (a, b) => {
4314
+ return b.length - a.length || a.localeCompare(b);
4315
+ };
4481
4316
 
4482
- if (typeof target === "string") {
4483
- if (pattern === false && subpath !== "" && !target.endsWith("/")) {
4484
- throw new Error("invalid module specifier");
4485
- }
4486
- if (target.startsWith("./")) {
4487
- const targetUrl = new URL(target, packageDirectoryUrl).href;
4488
- if (!targetUrl.startsWith(packageDirectoryUrl)) {
4489
- throw createInvalidPackageTargetError(
4490
- `target must be inside package`,
4491
- target,
4492
- resolutionContext,
4493
- );
4494
- }
4495
- return createResolutionResult({
4496
- type: isImport ? "field:imports" : "field:exports",
4497
- isMain: subpath === "" || subpath === ".",
4498
- packageDirectoryUrl,
4499
- packageJson,
4500
- url: pattern
4501
- ? targetUrl.replaceAll("*", subpath)
4502
- : new URL(subpath, targetUrl).href,
4503
- });
4504
- }
4505
- if (!isImport || target.startsWith("../") || isValidUrl(target)) {
4506
- throw createInvalidPackageTargetError(
4507
- `target must starst with "./"`,
4508
- target,
4509
- resolutionContext,
4510
- );
4511
- }
4512
- return applyPackageResolve(
4513
- pattern ? target.replaceAll("*", subpath) : `${target}${subpath}`,
4514
- {
4515
- ...resolutionContext,
4516
- parentUrl: packageDirectoryUrl,
4517
- },
4518
- );
4519
- }
4520
- if (Array.isArray(target)) {
4521
- if (target.length === 0) {
4522
- return null;
4523
- }
4524
- let lastResult;
4525
- let i = 0;
4526
- while (i < target.length) {
4527
- const targetValue = target[i];
4528
- i++;
4529
- try {
4530
- const resolved = applyPackageTargetResolution(targetValue, {
4531
- ...resolutionContext,
4532
- key: `${key}[${i}]`,
4533
- subpath,
4534
- pattern,
4535
- isImport,
4536
- });
4537
- if (resolved) {
4538
- return resolved;
4539
- }
4540
- lastResult = resolved;
4541
- } catch (e) {
4542
- if (e.code === "INVALID_PACKAGE_TARGET") {
4543
- continue;
4544
- }
4545
- lastResult = e;
4546
- }
4547
- }
4548
- if (lastResult) {
4549
- throw lastResult;
4550
- }
4551
- return null;
4552
- }
4553
- if (target === null) {
4554
- return null;
4555
- }
4556
- if (typeof target === "object") {
4557
- const keys = Object.keys(target);
4558
- for (const key of keys) {
4559
- if (Number.isInteger(key)) {
4560
- throw new Error("Invalid package configuration");
4561
- }
4562
- let matched;
4563
- if (key === "default") {
4564
- matched = true;
4565
- } else {
4566
- for (const conditionCandidate of conditions) {
4567
- if (conditionCandidate === key) {
4568
- matched = true;
4569
- break;
4570
- }
4571
- if (conditionCandidate.includes("*")) {
4572
- const conditionCandidateRegex = new RegExp(
4573
- `^${conditionCandidate.replace(/\*/g, "(.*)")}$`,
4574
- );
4575
- if (conditionCandidateRegex.test(key)) {
4576
- matched = true;
4577
- break;
4578
- }
4579
- }
4580
- }
4581
- }
4582
- if (matched) {
4583
- const targetValue = target[key];
4584
- const resolved = applyPackageTargetResolution(targetValue, {
4585
- ...resolutionContext,
4586
- key,
4587
- subpath,
4588
- pattern,
4589
- isImport,
4590
- });
4591
- if (resolved) {
4592
- return resolved;
4593
- }
4594
- }
4595
- }
4596
- return null;
4597
- }
4598
- throw createInvalidPackageTargetError(
4599
- `target must be a string, array, object or null`,
4600
- target,
4601
- resolutionContext,
4602
- );
4603
- };
4317
+ const normalizeImportMap = (importMap, baseUrl) => {
4318
+ assertImportMap(importMap);
4604
4319
 
4605
- const readExports = ({ packageDirectoryUrl, packageJson }) => {
4606
- const packageExports = packageJson.exports;
4607
- if (Array.isArray(packageExports)) {
4608
- return {
4609
- type: "array",
4610
- };
4611
- }
4612
- if (packageExports === null) {
4613
- return {};
4614
- }
4615
- if (typeof packageExports === "object") {
4616
- const keys = Object.keys(packageExports);
4617
- const relativeKeys = [];
4618
- const conditionalKeys = [];
4619
- keys.forEach((availableKey) => {
4620
- if (availableKey.startsWith(".")) {
4621
- relativeKeys.push(availableKey);
4622
- } else {
4623
- conditionalKeys.push(availableKey);
4624
- }
4625
- });
4626
- const hasRelativeKey = relativeKeys.length > 0;
4627
- if (hasRelativeKey && conditionalKeys.length > 0) {
4628
- throw new Error(
4629
- `Invalid package configuration: cannot mix relative and conditional keys in package.exports
4630
- --- unexpected keys ---
4631
- ${conditionalKeys.map((key) => `"${key}"`).join("\n")}
4632
- --- package directory url ---
4633
- ${packageDirectoryUrl}`,
4634
- );
4635
- }
4636
- return {
4637
- type: "object",
4638
- hasRelativeKey,
4639
- allKeysAreRelative: relativeKeys.length === keys.length,
4640
- };
4641
- }
4642
- if (typeof packageExports === "string") {
4643
- return { type: "string" };
4320
+ if (!isStringOrUrl(baseUrl)) {
4321
+ throw new TypeError(formulateBaseUrlMustBeStringOrUrl({ baseUrl }));
4644
4322
  }
4645
- return {};
4646
- };
4647
4323
 
4648
- const parsePackageSpecifier = (packageSpecifier) => {
4649
- if (packageSpecifier[0] === "@") {
4650
- const firstSlashIndex = packageSpecifier.indexOf("/");
4651
- if (firstSlashIndex === -1) {
4652
- throw new Error("invalid module specifier");
4653
- }
4654
- const secondSlashIndex = packageSpecifier.indexOf("/", firstSlashIndex + 1);
4655
- if (secondSlashIndex === -1) {
4656
- return {
4657
- packageName: packageSpecifier,
4658
- packageSubpath: ".",
4659
- isScoped: true,
4660
- };
4661
- }
4662
- const packageName = packageSpecifier.slice(0, secondSlashIndex);
4663
- const afterSecondSlash = packageSpecifier.slice(secondSlashIndex + 1);
4664
- const packageSubpath = `./${afterSecondSlash}`;
4665
- return {
4666
- packageName,
4667
- packageSubpath,
4668
- isScoped: true,
4669
- };
4670
- }
4671
- const firstSlashIndex = packageSpecifier.indexOf("/");
4672
- if (firstSlashIndex === -1) {
4673
- return {
4674
- packageName: packageSpecifier,
4675
- packageSubpath: ".",
4676
- };
4677
- }
4678
- const packageName = packageSpecifier.slice(0, firstSlashIndex);
4679
- const afterFirstSlash = packageSpecifier.slice(firstSlashIndex + 1);
4680
- if (afterFirstSlash === "") {
4681
- return {
4682
- packageName,
4683
- packageSubpath: "/",
4684
- };
4685
- }
4686
- const packageSubpath = `./${afterFirstSlash}`;
4324
+ const { imports, scopes } = importMap;
4325
+
4687
4326
  return {
4688
- packageName,
4689
- packageSubpath,
4327
+ imports: imports ? normalizeMappings(imports, baseUrl) : undefined,
4328
+ scopes: scopes ? normalizeScopes(scopes, baseUrl) : undefined,
4690
4329
  };
4691
4330
  };
4692
4331
 
4693
- const applyMainExportResolution = (resolutionContext) => {
4694
- const { packageJson } = resolutionContext;
4695
- const packageExportsInfo = readExports(resolutionContext);
4696
- if (
4697
- packageExportsInfo.type === "array" ||
4698
- packageExportsInfo.type === "string"
4699
- ) {
4700
- return packageJson.exports;
4332
+ const isStringOrUrl = (value) => {
4333
+ if (typeof value === "string") {
4334
+ return true;
4701
4335
  }
4702
- if (packageExportsInfo.type === "object") {
4703
- if (packageExportsInfo.hasRelativeKey) {
4704
- return packageJson.exports["."];
4705
- }
4706
- return packageJson.exports;
4336
+
4337
+ if (typeof URL === "function" && value instanceof URL) {
4338
+ return true;
4707
4339
  }
4708
- return undefined;
4340
+
4341
+ return false;
4709
4342
  };
4710
4343
 
4711
- const applyLegacySubpathResolution = (packageSubpath, resolutionContext) => {
4712
- const { packageDirectoryUrl, packageJson } = resolutionContext;
4344
+ const normalizeMappings = (mappings, baseUrl) => {
4345
+ const mappingsNormalized = {};
4713
4346
 
4714
- if (packageSubpath === ".") {
4715
- return applyLegacyMainResolution(packageSubpath, resolutionContext);
4716
- }
4717
- const browserFieldResolution = applyBrowserFieldResolution(
4718
- packageSubpath,
4719
- resolutionContext,
4720
- );
4721
- if (browserFieldResolution) {
4722
- return browserFieldResolution;
4723
- }
4724
- return createResolutionResult({
4725
- type: "subpath",
4726
- isMain: packageSubpath === ".",
4727
- packageDirectoryUrl,
4728
- packageJson,
4729
- url: new URL(packageSubpath, packageDirectoryUrl).href,
4730
- });
4731
- };
4347
+ Object.keys(mappings).forEach((specifier) => {
4348
+ const address = mappings[specifier];
4732
4349
 
4733
- const applyLegacyMainResolution = (packageSubpath, resolutionContext) => {
4734
- const { conditions, packageDirectoryUrl, packageJson } = resolutionContext;
4735
- for (const condition of conditions) {
4736
- const conditionResolver = mainLegacyResolvers[condition];
4737
- if (!conditionResolver) {
4738
- continue;
4739
- }
4740
- const resolved = conditionResolver(resolutionContext);
4741
- if (resolved) {
4742
- return createResolutionResult({
4743
- type: resolved.type,
4744
- isMain: resolved.isMain,
4745
- packageDirectoryUrl,
4746
- packageJson,
4747
- url: new URL(resolved.path, packageDirectoryUrl).href,
4748
- });
4749
- }
4750
- }
4751
- return createResolutionResult({
4752
- type: "field:main", // the absence of "main" field
4753
- isMain: true,
4754
- packageDirectoryUrl,
4755
- packageJson,
4756
- url: new URL("index.js", packageDirectoryUrl).href,
4757
- });
4758
- };
4759
- const mainLegacyResolvers = {
4760
- import: ({ packageJson }) => {
4761
- if (typeof packageJson.module === "string") {
4762
- return { type: "field:module", isMain: true, path: packageJson.module };
4763
- }
4764
- if (typeof packageJson.jsnext === "string") {
4765
- return { type: "field:jsnext", isMain: true, path: packageJson.jsnext };
4766
- }
4767
- if (typeof packageJson.main === "string") {
4768
- return { type: "field:main", isMain: true, path: packageJson.main };
4350
+ if (typeof address !== "string") {
4351
+ console.warn(
4352
+ formulateAddressMustBeAString({
4353
+ address,
4354
+ specifier,
4355
+ }),
4356
+ );
4357
+ return;
4769
4358
  }
4770
- return null;
4771
- },
4772
- browser: ({ packageDirectoryUrl, packageJson }) => {
4773
- const browserMain = (() => {
4774
- if (typeof packageJson.browser === "string") {
4775
- return packageJson.browser;
4776
- }
4777
- if (
4778
- typeof packageJson.browser === "object" &&
4779
- packageJson.browser !== null
4780
- ) {
4781
- return packageJson.browser["."];
4782
- }
4783
- return "";
4784
- })();
4785
4359
 
4786
- if (!browserMain) {
4787
- if (typeof packageJson.module === "string") {
4788
- return {
4789
- type: "field:module",
4790
- isMain: true,
4791
- path: packageJson.module,
4792
- };
4793
- }
4794
- return null;
4795
- }
4796
- if (
4797
- typeof packageJson.module !== "string" ||
4798
- packageJson.module === browserMain
4799
- ) {
4800
- return {
4801
- type: "field:browser",
4802
- isMain: true,
4803
- path: browserMain,
4804
- };
4805
- }
4806
- const browserMainUrlObject = new URL(browserMain, packageDirectoryUrl);
4807
- const content = readFileSync(browserMainUrlObject, "utf-8");
4808
- if (
4809
- (/typeof exports\s*==/.test(content) &&
4810
- /typeof module\s*==/.test(content)) ||
4811
- /module\.exports\s*=/.test(content)
4812
- ) {
4813
- return {
4814
- type: "field:module",
4815
- isMain: true,
4816
- path: packageJson.module,
4817
- };
4818
- }
4819
- return {
4820
- type: "field:browser",
4821
- isMain: true,
4822
- path: browserMain,
4823
- };
4824
- },
4825
- node: ({ packageJson, conditions }) => {
4826
- if (conditions.includes("import") && !conditions.includes("require")) {
4827
- if (typeof packageJson.module === "string") {
4828
- return { type: "field:module", isMain: true, path: packageJson.module };
4829
- }
4830
- if (typeof packageJson.jsnext === "string") {
4831
- return { type: "field:jsnext", isMain: true, path: packageJson.jsnext };
4832
- }
4833
- }
4834
- if (typeof packageJson.main === "string") {
4835
- return { type: "field:main", isMain: true, path: packageJson.main };
4360
+ const specifierResolved = resolveSpecifier(specifier, baseUrl) || specifier;
4361
+
4362
+ const addressUrl = tryUrlResolution(address, baseUrl);
4363
+ if (addressUrl === null) {
4364
+ console.warn(
4365
+ formulateAdressResolutionFailed({
4366
+ address,
4367
+ baseUrl,
4368
+ specifier,
4369
+ }),
4370
+ );
4371
+ return;
4836
4372
  }
4837
- return null;
4838
- },
4839
- };
4840
- mainLegacyResolvers.require = mainLegacyResolvers.node;
4841
4373
 
4842
- const comparePatternKeys = (keyA, keyB) => {
4843
- if (!keyA.endsWith("/") && !keyA.includes("*")) {
4844
- throw new Error("Invalid package configuration");
4845
- }
4846
- if (!keyB.endsWith("/") && !keyB.includes("*")) {
4847
- throw new Error("Invalid package configuration");
4848
- }
4849
- const aStarIndex = keyA.indexOf("*");
4850
- const baseLengthA = aStarIndex > -1 ? aStarIndex + 1 : keyA.length;
4851
- const bStarIndex = keyB.indexOf("*");
4852
- const baseLengthB = bStarIndex > -1 ? bStarIndex + 1 : keyB.length;
4853
- if (baseLengthA > baseLengthB) {
4854
- return -1;
4855
- }
4856
- if (baseLengthB > baseLengthA) {
4857
- return 1;
4858
- }
4859
- if (aStarIndex === -1) {
4860
- return 1;
4861
- }
4862
- if (bStarIndex === -1) {
4863
- return -1;
4864
- }
4865
- if (keyA.length > keyB.length) {
4866
- return -1;
4867
- }
4868
- if (keyB.length > keyA.length) {
4869
- return 1;
4870
- }
4871
- return 0;
4872
- };
4374
+ if (specifier.endsWith("/") && !addressUrl.endsWith("/")) {
4375
+ console.warn(
4376
+ formulateAddressUrlRequiresTrailingSlash({
4377
+ address,
4378
+ specifier,
4379
+ }),
4380
+ );
4381
+ return;
4382
+ }
4383
+ mappingsNormalized[specifierResolved] = addressUrl;
4384
+ });
4873
4385
 
4874
- const resolvePackageSymlink = (packageDirectoryUrl) => {
4875
- const packageDirectoryPath = realpathSync(new URL(packageDirectoryUrl));
4876
- const packageDirectoryResolvedUrl = pathToFileURL(packageDirectoryPath).href;
4877
- return `${packageDirectoryResolvedUrl}/`;
4386
+ return sortImports(mappingsNormalized);
4878
4387
  };
4879
4388
 
4880
- const applyFileSystemMagicResolution = (
4881
- fileUrl,
4882
- { fileStat, magicDirectoryIndex, magicExtensions },
4883
- ) => {
4884
- const result = {
4885
- stat: null,
4886
- url: fileUrl,
4887
- magicExtension: "",
4888
- magicDirectoryIndex: false,
4889
- lastENOENTError: null,
4890
- };
4891
-
4892
- if (fileStat === undefined) {
4893
- try {
4894
- fileStat = readEntryStatSync(new URL(fileUrl));
4895
- } catch (e) {
4896
- if (e.code === "ENOENT") {
4897
- result.lastENOENTError = e;
4898
- fileStat = null;
4899
- } else {
4900
- throw e;
4901
- }
4902
- }
4903
- }
4389
+ const normalizeScopes = (scopes, baseUrl) => {
4390
+ const scopesNormalized = {};
4904
4391
 
4905
- if (fileStat && fileStat.isFile()) {
4906
- result.stat = fileStat;
4907
- result.url = fileUrl;
4908
- return result;
4909
- }
4910
- if (fileStat && fileStat.isDirectory()) {
4911
- if (magicDirectoryIndex) {
4912
- const indexFileSuffix = fileUrl.endsWith("/") ? "index" : "/index";
4913
- const indexFileUrl = `${fileUrl}${indexFileSuffix}`;
4914
- const subResult = applyFileSystemMagicResolution(indexFileUrl, {
4915
- magicDirectoryIndex: false,
4916
- magicExtensions,
4917
- });
4918
- return {
4919
- ...result,
4920
- ...subResult,
4921
- magicDirectoryIndex: true,
4922
- };
4392
+ Object.keys(scopes).forEach((scopeSpecifier) => {
4393
+ const scopeMappings = scopes[scopeSpecifier];
4394
+ const scopeUrl = tryUrlResolution(scopeSpecifier, baseUrl);
4395
+ if (scopeUrl === null) {
4396
+ console.warn(
4397
+ formulateScopeResolutionFailed({
4398
+ scope: scopeSpecifier,
4399
+ baseUrl,
4400
+ }),
4401
+ );
4402
+ return;
4923
4403
  }
4924
- result.stat = fileStat;
4925
- result.url = fileUrl;
4926
- return result;
4927
- }
4404
+ const scopeValueNormalized = normalizeMappings(scopeMappings, baseUrl);
4405
+ scopesNormalized[scopeUrl] = scopeValueNormalized;
4406
+ });
4928
4407
 
4929
- if (magicExtensions && magicExtensions.length) {
4930
- const parentUrl = new URL("./", fileUrl).href;
4931
- const urlFilename = urlToFilename(fileUrl);
4932
- for (const extensionToTry of magicExtensions) {
4933
- const urlCandidate = `${parentUrl}${urlFilename}${extensionToTry}`;
4934
- let stat;
4935
- try {
4936
- stat = readEntryStatSync(new URL(urlCandidate));
4937
- } catch (e) {
4938
- if (e.code === "ENOENT") {
4939
- stat = null;
4940
- } else {
4941
- throw e;
4942
- }
4943
- }
4944
- if (stat) {
4945
- result.stat = stat;
4946
- result.url = `${fileUrl}${extensionToTry}`;
4947
- result.magicExtension = extensionToTry;
4948
- return result;
4949
- }
4950
- }
4951
- }
4952
- // magic extension not found
4953
- return result;
4408
+ return sortScopes(scopesNormalized);
4954
4409
  };
4955
4410
 
4956
- const getExtensionsToTry = (magicExtensions, importer) => {
4957
- if (!magicExtensions) {
4958
- return [];
4959
- }
4960
- const extensionsSet = new Set();
4961
- magicExtensions.forEach((magicExtension) => {
4962
- if (magicExtension === "inherit") {
4963
- const importerExtension = urlToExtension(importer);
4964
- extensionsSet.add(importerExtension);
4965
- } else {
4966
- extensionsSet.add(magicExtension);
4967
- }
4968
- });
4969
- return Array.from(extensionsSet.values());
4970
- };
4411
+ const formulateBaseUrlMustBeStringOrUrl = ({
4412
+ baseUrl,
4413
+ }) => `baseUrl must be a string or an url.
4414
+ --- base url ---
4415
+ ${baseUrl}`;
4971
4416
 
4972
- const versionFromValue = (value) => {
4973
- if (typeof value === "number") {
4974
- return numberToVersion(value);
4975
- }
4976
- if (typeof value === "string") {
4977
- return stringToVersion(value);
4417
+ const formulateAddressMustBeAString = ({
4418
+ specifier,
4419
+ address,
4420
+ }) => `Address must be a string.
4421
+ --- address ---
4422
+ ${address}
4423
+ --- specifier ---
4424
+ ${specifier}`;
4425
+
4426
+ const formulateAdressResolutionFailed = ({
4427
+ address,
4428
+ baseUrl,
4429
+ specifier,
4430
+ }) => `Address url resolution failed.
4431
+ --- address ---
4432
+ ${address}
4433
+ --- base url ---
4434
+ ${baseUrl}
4435
+ --- specifier ---
4436
+ ${specifier}`;
4437
+
4438
+ const formulateAddressUrlRequiresTrailingSlash = ({
4439
+ addressURL,
4440
+ address,
4441
+ specifier,
4442
+ }) => `Address must end with /.
4443
+ --- address url ---
4444
+ ${addressURL}
4445
+ --- address ---
4446
+ ${address}
4447
+ --- specifier ---
4448
+ ${specifier}`;
4449
+
4450
+ const formulateScopeResolutionFailed = ({
4451
+ scope,
4452
+ baseUrl,
4453
+ }) => `Scope url resolution failed.
4454
+ --- scope ---
4455
+ ${scope}
4456
+ --- base url ---
4457
+ ${baseUrl}`;
4458
+
4459
+ const pathnameToExtension = (pathname) => {
4460
+ const slashLastIndex = pathname.lastIndexOf("/");
4461
+ if (slashLastIndex !== -1) {
4462
+ pathname = pathname.slice(slashLastIndex + 1);
4978
4463
  }
4979
- throw new TypeError(`version must be a number or a string, got ${value}`);
4980
- };
4981
4464
 
4982
- const numberToVersion = (number) => {
4983
- return {
4984
- major: number,
4985
- minor: 0,
4986
- patch: 0,
4987
- };
4465
+ const dotLastIndex = pathname.lastIndexOf(".");
4466
+ if (dotLastIndex === -1) return "";
4467
+ // if (dotLastIndex === pathname.length - 1) return ""
4468
+ return pathname.slice(dotLastIndex);
4988
4469
  };
4989
4470
 
4990
- const stringToVersion = (string) => {
4991
- if (string.indexOf(".") > -1) {
4992
- const parts = string.split(".");
4993
- return {
4994
- major: Number(parts[0]),
4995
- minor: parts[1] ? Number(parts[1]) : 0,
4996
- patch: parts[2] ? Number(parts[2]) : 0,
4997
- };
4471
+ const resolveImport = ({
4472
+ specifier,
4473
+ importer,
4474
+ importMap,
4475
+ defaultExtension = false,
4476
+ createBareSpecifierError,
4477
+ onImportMapping = () => {},
4478
+ }) => {
4479
+ let url;
4480
+ if (importMap) {
4481
+ url = applyImportMap({
4482
+ importMap,
4483
+ specifier,
4484
+ importer,
4485
+ createBareSpecifierError,
4486
+ onImportMapping,
4487
+ });
4488
+ } else {
4489
+ url = resolveUrl(specifier, importer);
4998
4490
  }
4999
4491
 
5000
- if (isNaN(string)) {
5001
- return {
5002
- major: 0,
5003
- minor: 0,
5004
- patch: 0,
5005
- };
4492
+ if (defaultExtension) {
4493
+ url = applyDefaultExtension({ url, importer, defaultExtension });
5006
4494
  }
5007
4495
 
5008
- return {
5009
- major: Number(string),
5010
- minor: 0,
5011
- patch: 0,
5012
- };
4496
+ return url;
5013
4497
  };
5014
4498
 
5015
- const compareTwoVersions = (versionA, versionB) => {
5016
- const semanticVersionA = versionFromValue(versionA);
5017
- const semanticVersionB = versionFromValue(versionB);
5018
- const majorDiff = semanticVersionA.major - semanticVersionB.major;
5019
- if (majorDiff > 0) {
5020
- return majorDiff;
5021
- }
5022
- if (majorDiff < 0) {
5023
- return majorDiff;
5024
- }
5025
- const minorDiff = semanticVersionA.minor - semanticVersionB.minor;
5026
- if (minorDiff > 0) {
5027
- return minorDiff;
5028
- }
5029
- if (minorDiff < 0) {
5030
- return minorDiff;
4499
+ const applyDefaultExtension = ({ url, importer, defaultExtension }) => {
4500
+ if (urlToPathname(url).endsWith("/")) {
4501
+ return url;
5031
4502
  }
5032
- const patchDiff = semanticVersionA.patch - semanticVersionB.patch;
5033
- if (patchDiff > 0) {
5034
- return patchDiff;
4503
+
4504
+ if (typeof defaultExtension === "string") {
4505
+ const extension = pathnameToExtension(url);
4506
+ if (extension === "") {
4507
+ return `${url}${defaultExtension}`;
4508
+ }
4509
+ return url;
5035
4510
  }
5036
- if (patchDiff < 0) {
5037
- return patchDiff;
4511
+
4512
+ if (defaultExtension === true) {
4513
+ const extension = pathnameToExtension(url);
4514
+ if (extension === "" && importer) {
4515
+ const importerPathname = urlToPathname(importer);
4516
+ const importerExtension = pathnameToExtension(importerPathname);
4517
+ return `${url}${importerExtension}`;
4518
+ }
5038
4519
  }
5039
- return 0;
5040
- };
5041
4520
 
5042
- const versionIsBelow = (versionSupposedBelow, versionSupposedAbove) => {
5043
- return compareTwoVersions(versionSupposedBelow, versionSupposedAbove) < 0;
4521
+ return url;
5044
4522
  };
5045
4523
 
5046
- const findHighestVersion = (...values) => {
5047
- if (values.length === 0) throw new Error(`missing argument`);
5048
- return values.reduce((highestVersion, value) => {
5049
- if (versionIsBelow(highestVersion, value)) {
5050
- return value;
4524
+ const isEscaped = (i, string) => {
4525
+ let backslashBeforeCount = 0;
4526
+ while (i--) {
4527
+ const previousChar = string[i];
4528
+ if (previousChar === "\\") {
4529
+ backslashBeforeCount++;
5051
4530
  }
5052
- return highestVersion;
5053
- });
4531
+ break;
4532
+ }
4533
+ const isEven = backslashBeforeCount % 2 === 0;
4534
+ return !isEven;
5054
4535
  };
5055
4536
 
5056
- const featuresCompatMap = {
5057
- script_type_module: {
5058
- edge: "16",
5059
- firefox: "60",
5060
- chrome: "61",
5061
- safari: "10.1",
5062
- opera: "48",
5063
- ios: "10.3",
5064
- android: "61",
5065
- samsung: "8.2",
5066
- },
5067
- document_current_script: {
5068
- edge: "12",
5069
- firefox: "4",
5070
- chrome: "29",
5071
- safari: "8",
5072
- opera: "16",
5073
- android: "4.4",
5074
- samsung: "4",
5075
- },
5076
- // https://caniuse.com/?search=import.meta
5077
- import_meta: {
5078
- android: "9",
5079
- chrome: "64",
5080
- edge: "79",
5081
- firefox: "62",
5082
- ios: "12",
5083
- opera: "51",
5084
- safari: "11.1",
5085
- samsung: "9.2",
5086
- },
5087
- import_meta_resolve: {
5088
- chrome: "107",
5089
- edge: "105",
5090
- firefox: "106",
5091
- node: "20.0.0",
5092
- },
5093
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#browser_compatibility
5094
- import_dynamic: {
5095
- android: "8",
5096
- chrome: "63",
5097
- edge: "79",
5098
- firefox: "67",
5099
- ios: "11.3",
5100
- opera: "50",
5101
- safari: "11.3",
5102
- samsung: "8.0",
5103
- node: "13.2",
4537
+ const JS_QUOTES = {
4538
+ pickBest: (string, { canUseTemplateString, defaultQuote = DOUBLE } = {}) => {
4539
+ // check default first, once tested do no re-test it
4540
+ if (!string.includes(defaultQuote)) {
4541
+ return defaultQuote;
4542
+ }
4543
+ if (defaultQuote !== DOUBLE && !string.includes(DOUBLE)) {
4544
+ return DOUBLE;
4545
+ }
4546
+ if (defaultQuote !== SINGLE && !string.includes(SINGLE)) {
4547
+ return SINGLE;
4548
+ }
4549
+ if (
4550
+ canUseTemplateString &&
4551
+ defaultQuote !== BACKTICK &&
4552
+ !string.includes(BACKTICK)
4553
+ ) {
4554
+ return BACKTICK;
4555
+ }
4556
+ return defaultQuote;
5104
4557
  },
5105
- top_level_await: {
5106
- edge: "89",
5107
- chrome: "89",
5108
- firefox: "89",
5109
- opera: "75",
5110
- safari: "15",
5111
- samsung: "15",
5112
- ios: "15",
5113
- node: "14.8",
5114
- },
5115
- // https://caniuse.com/import-maps
5116
- importmap: {
5117
- edge: "89",
5118
- chrome: "89",
5119
- opera: "76",
5120
- samsung: "15",
5121
- firefox: "108",
5122
- safari: "16.4",
5123
- },
5124
- import_type_json: {
5125
- chrome: "123",
5126
- safari: "17.2",
5127
- },
5128
- import_type_css: {
5129
- chrome: "123",
5130
- },
5131
- import_type_text: {},
5132
- // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet#browser_compatibility
5133
- new_stylesheet: {
5134
- chrome: "73",
5135
- edge: "79",
5136
- opera: "53",
5137
- android: "73",
5138
- },
5139
- // https://caniuse.com/?search=worker
5140
- worker: {
5141
- ie: "10",
5142
- edge: "12",
5143
- firefox: "3.5",
5144
- chrome: "4",
5145
- opera: "11.5",
5146
- safari: "4",
5147
- ios: "5",
5148
- android: "4.4",
5149
- },
5150
- // https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#browser_compatibility
5151
- worker_type_module: {
5152
- chrome: "80",
5153
- edge: "80",
5154
- opera: "67",
5155
- android: "80",
5156
- },
5157
- worker_importmap: {},
5158
- service_worker: {
5159
- edge: "17",
5160
- firefox: "44",
5161
- chrome: "40",
5162
- safari: "11.1",
5163
- opera: "27",
5164
- ios: "11.3",
5165
- android: "12.12",
5166
- },
5167
- service_worker_type_module: {
5168
- chrome: "80",
5169
- edge: "80",
5170
- opera: "67",
5171
- android: "80",
5172
- },
5173
- service_worker_importmap: {},
5174
- shared_worker: {
5175
- chrome: "4",
5176
- edge: "79",
5177
- firefox: "29",
5178
- opera: "10.6",
5179
- },
5180
- shared_worker_type_module: {
5181
- chrome: "80",
5182
- edge: "80",
5183
- opera: "67",
5184
- },
5185
- shared_worker_importmap: {},
5186
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis#browser_compatibility
5187
- global_this: {
5188
- edge: "79",
5189
- firefox: "65",
5190
- chrome: "71",
5191
- safari: "12.1",
5192
- opera: "58",
5193
- ios: "12.2",
5194
- android: "94",
5195
- node: "12",
5196
- },
5197
- async_generator_function: {
5198
- chrome: "63",
5199
- opera: "50",
5200
- edge: "79",
5201
- firefox: "57",
5202
- safari: "12",
5203
- node: "10",
5204
- ios: "12",
5205
- samsung: "8",
5206
- electron: "3",
5207
- },
5208
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#browser_compatibility
5209
- template_literals: {
5210
- chrome: "41",
5211
- edge: "12",
5212
- firefox: "34",
5213
- opera: "28",
5214
- safari: "9",
5215
- ios: "9",
5216
- android: "4",
5217
- node: "4",
5218
- },
5219
- arrow_function: {
5220
- chrome: "47",
5221
- opera: "34",
5222
- edge: "13",
5223
- firefox: "45",
5224
- safari: "10",
5225
- node: "6",
5226
- ios: "10",
5227
- samsung: "5",
5228
- electron: "0.36",
5229
- },
5230
- const_bindings: {
5231
- chrome: "41",
5232
- opera: "28",
5233
- edge: "12",
5234
- firefox: "46",
5235
- safari: "10",
5236
- node: "4",
5237
- ie: "11",
5238
- ios: "10",
5239
- samsung: "3.4",
5240
- electron: "0.22",
4558
+
4559
+ escapeSpecialChars: (
4560
+ string,
4561
+ {
4562
+ quote = "pickBest",
4563
+ canUseTemplateString,
4564
+ defaultQuote,
4565
+ allowEscapeForVersioning = false,
4566
+ },
4567
+ ) => {
4568
+ quote =
4569
+ quote === "pickBest"
4570
+ ? JS_QUOTES.pickBest(string, { canUseTemplateString, defaultQuote })
4571
+ : quote;
4572
+ const replacements = JS_QUOTE_REPLACEMENTS[quote];
4573
+ let result = "";
4574
+ let last = 0;
4575
+ let i = 0;
4576
+ while (i < string.length) {
4577
+ const char = string[i];
4578
+ i++;
4579
+ if (isEscaped(i - 1, string)) continue;
4580
+ const replacement = replacements[char];
4581
+ if (replacement) {
4582
+ if (
4583
+ allowEscapeForVersioning &&
4584
+ char === quote &&
4585
+ string.slice(i, i + 6) === "+__v__"
4586
+ ) {
4587
+ let isVersioningConcatenation = false;
4588
+ let j = i + 6; // start after the +
4589
+ while (j < string.length) {
4590
+ const lookAheadChar = string[j];
4591
+ j++;
4592
+ if (
4593
+ lookAheadChar === "+" &&
4594
+ string[j] === quote &&
4595
+ !isEscaped(j - 1, string)
4596
+ ) {
4597
+ isVersioningConcatenation = true;
4598
+ break;
4599
+ }
4600
+ }
4601
+ if (isVersioningConcatenation) {
4602
+ // it's a concatenation
4603
+ // skip until the end of concatenation (the second +)
4604
+ // and resume from there
4605
+ i = j + 1;
4606
+ continue;
4607
+ }
4608
+ }
4609
+ if (last === i - 1) {
4610
+ result += replacement;
4611
+ } else {
4612
+ result += `${string.slice(last, i - 1)}${replacement}`;
4613
+ }
4614
+ last = i;
4615
+ }
4616
+ }
4617
+ if (last !== string.length) {
4618
+ result += string.slice(last);
4619
+ }
4620
+ return `${quote}${result}${quote}`;
5241
4621
  },
5242
- object_properties_shorthand: {
5243
- chrome: "43",
5244
- opera: "30",
5245
- edge: "12",
5246
- firefox: "33",
5247
- safari: "9",
5248
- node: "4",
5249
- ios: "9",
5250
- samsung: "4",
5251
- electron: "0.28",
4622
+ };
4623
+
4624
+ const DOUBLE = `"`;
4625
+ const SINGLE = `'`;
4626
+ const BACKTICK = "`";
4627
+ const lineEndingEscapes = {
4628
+ "\n": "\\n",
4629
+ "\r": "\\r",
4630
+ "\u2028": "\\u2028",
4631
+ "\u2029": "\\u2029",
4632
+ };
4633
+ const JS_QUOTE_REPLACEMENTS = {
4634
+ [DOUBLE]: {
4635
+ '"': '\\"',
4636
+ ...lineEndingEscapes,
5252
4637
  },
5253
- reserved_words: {
5254
- chrome: "13",
5255
- opera: "10.50",
5256
- edge: "12",
5257
- firefox: "2",
5258
- safari: "3.1",
5259
- node: "0.10",
5260
- ie: "9",
5261
- android: "4.4",
5262
- ios: "6",
5263
- phantom: "2",
5264
- samsung: "1",
5265
- electron: "0.20",
4638
+ [SINGLE]: {
4639
+ "'": "\\'",
4640
+ ...lineEndingEscapes,
5266
4641
  },
5267
- symbols: {
5268
- chrome: "38",
5269
- opera: "25",
5270
- edge: "12",
5271
- firefox: "36",
5272
- safari: "9",
5273
- ios: "9",
5274
- samsung: "4",
5275
- node: "0.12",
4642
+ [BACKTICK]: {
4643
+ "`": "\\`",
4644
+ "$": "\\$",
5276
4645
  },
5277
4646
  };
5278
4647
 
5279
- const RUNTIME_COMPAT = {
5280
- featuresCompatMap,
5281
-
5282
- add: (originalRuntimeCompat, feature) => {
5283
- const featureCompat = getFeatureCompat(feature);
5284
- const runtimeCompat = {
5285
- ...originalRuntimeCompat,
5286
- };
5287
- Object.keys(originalRuntimeCompat).forEach((runtimeName) => {
5288
- const secondVersion = featureCompat[runtimeName]; // the version supported by the feature
5289
- if (secondVersion) {
5290
- const firstVersion = originalRuntimeCompat[runtimeName];
5291
- runtimeCompat[runtimeName] = findHighestVersion(
5292
- firstVersion,
5293
- secondVersion,
5294
- );
4648
+ // https://nodejs.org/api/packages.html#resolving-user-conditions
4649
+ const readCustomConditionsFromProcessArgs = () => {
4650
+ if (process.env.IGNORE_PACKAGE_CONDITIONS) {
4651
+ return [];
4652
+ }
4653
+ const packageConditions = [];
4654
+ for (const arg of process.execArgv) {
4655
+ if (arg.includes("-C=")) {
4656
+ const packageCondition = arg.slice(0, "-C=".length);
4657
+ packageConditions.push(packageCondition);
4658
+ }
4659
+ if (arg.includes("--conditions=")) {
4660
+ const packageCondition = arg.slice("--conditions=".length);
4661
+ packageConditions.push(packageCondition);
4662
+ }
4663
+ }
4664
+ return packageConditions;
4665
+ };
4666
+
4667
+ const asDirectoryUrl = (url) => {
4668
+ const { pathname } = new URL(url);
4669
+ if (pathname.endsWith("/")) {
4670
+ return url;
4671
+ }
4672
+ return new URL("./", url).href;
4673
+ };
4674
+
4675
+ const getParentUrl = (url) => {
4676
+ if (url.startsWith("file://")) {
4677
+ // With node.js new URL('../', 'file:///C:/').href
4678
+ // returns "file:///C:/" instead of "file:///"
4679
+ const resource = url.slice("file://".length);
4680
+ const slashLastIndex = resource.lastIndexOf("/");
4681
+ if (slashLastIndex === -1) {
4682
+ return url;
4683
+ }
4684
+ const lastCharIndex = resource.length - 1;
4685
+ if (slashLastIndex === lastCharIndex) {
4686
+ const slashBeforeLastIndex = resource.lastIndexOf(
4687
+ "/",
4688
+ slashLastIndex - 1,
4689
+ );
4690
+ if (slashBeforeLastIndex === -1) {
4691
+ return url;
5295
4692
  }
5296
- });
5297
- return runtimeCompat;
5298
- },
4693
+ return `file://${resource.slice(0, slashBeforeLastIndex + 1)}`;
4694
+ }
5299
4695
 
5300
- isSupported: (
5301
- runtimeCompat,
5302
- feature,
5303
- featureCompat = getFeatureCompat(feature),
5304
- ) => {
5305
- const runtimeNames = Object.keys(runtimeCompat);
5306
- const runtimeWithoutCompat = runtimeNames.find((runtimeName) => {
5307
- const runtimeVersion = runtimeCompat[runtimeName];
5308
- const runtimeVersionCompatible = featureCompat[runtimeName] || "Infinity";
5309
- const highestVersion = findHighestVersion(
5310
- runtimeVersion,
5311
- runtimeVersionCompatible,
4696
+ return `file://${resource.slice(0, slashLastIndex + 1)}`;
4697
+ }
4698
+ return new URL(url.endsWith("/") ? "../" : "./", url).href;
4699
+ };
4700
+
4701
+ const isValidUrl = (url) => {
4702
+ try {
4703
+ // eslint-disable-next-line no-new
4704
+ new URL(url);
4705
+ return true;
4706
+ } catch {
4707
+ return false;
4708
+ }
4709
+ };
4710
+
4711
+ const urlToFilename = (url) => {
4712
+ const { pathname } = new URL(url);
4713
+ const pathnameBeforeLastSlash = pathname.endsWith("/")
4714
+ ? pathname.slice(0, -1)
4715
+ : pathname;
4716
+ const slashLastIndex = pathnameBeforeLastSlash.lastIndexOf("/");
4717
+ const filename =
4718
+ slashLastIndex === -1
4719
+ ? pathnameBeforeLastSlash
4720
+ : pathnameBeforeLastSlash.slice(slashLastIndex + 1);
4721
+ return filename;
4722
+ };
4723
+
4724
+ const urlToExtension = (url) => {
4725
+ const filename = urlToFilename(url);
4726
+ const dotLastIndex = filename.lastIndexOf(".");
4727
+ if (dotLastIndex === -1) return "";
4728
+ // if (dotLastIndex === pathname.length - 1) return ""
4729
+ const extension = filename.slice(dotLastIndex);
4730
+ return extension;
4731
+ };
4732
+
4733
+ const defaultLookupPackageScope = (url) => {
4734
+ let scopeUrl = asDirectoryUrl(url);
4735
+ while (scopeUrl !== "file:///") {
4736
+ if (scopeUrl.endsWith("node_modules/")) {
4737
+ return null;
4738
+ }
4739
+ const packageJsonUrlObject = new URL("package.json", scopeUrl);
4740
+ if (existsSync(packageJsonUrlObject)) {
4741
+ return scopeUrl;
4742
+ }
4743
+ scopeUrl = getParentUrl(scopeUrl);
4744
+ }
4745
+ return null;
4746
+ };
4747
+
4748
+ const defaultReadPackageJson = (packageUrl) => {
4749
+ const packageJsonFileUrl = new URL("./package.json", packageUrl);
4750
+ let packageJsonFileContentBuffer;
4751
+ try {
4752
+ packageJsonFileContentBuffer = readFileSync(packageJsonFileUrl, "utf8");
4753
+ } catch (e) {
4754
+ if (e.code === "ENOENT") {
4755
+ return null;
4756
+ }
4757
+ throw e;
4758
+ }
4759
+ const packageJsonFileContentString = String(packageJsonFileContentBuffer);
4760
+ try {
4761
+ const packageJsonFileContentObject = JSON.parse(
4762
+ packageJsonFileContentString,
4763
+ );
4764
+ return packageJsonFileContentObject;
4765
+ } catch {
4766
+ throw new Error(`Invalid package configuration at ${packageJsonFileUrl}`);
4767
+ }
4768
+ };
4769
+
4770
+ // https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/tools/node_modules/eslint/node_modules/%40babel/core/lib/vendor/import-meta-resolve.js#L2473
4771
+
4772
+ const createInvalidModuleSpecifierError = (
4773
+ reason,
4774
+ specifier,
4775
+ { parentUrl },
4776
+ ) => {
4777
+ const error = new Error(
4778
+ `Invalid module "${specifier}" ${reason} imported from ${fileURLToPath(
4779
+ parentUrl,
4780
+ )}`,
4781
+ );
4782
+ error.code = "INVALID_MODULE_SPECIFIER";
4783
+ return error;
4784
+ };
4785
+
4786
+ const createInvalidPackageTargetError = (
4787
+ reason,
4788
+ target,
4789
+ { parentUrl, packageDirectoryUrl, key, isImport },
4790
+ ) => {
4791
+ let message;
4792
+ if (key === ".") {
4793
+ message = `Invalid "exports" main target defined in ${fileURLToPath(
4794
+ packageDirectoryUrl,
4795
+ )}package.json imported from ${fileURLToPath(parentUrl)}; ${reason}`;
4796
+ } else {
4797
+ message = `Invalid "${
4798
+ isImport ? "imports" : "exports"
4799
+ }" target ${JSON.stringify(target)} defined for "${key}" in ${fileURLToPath(
4800
+ packageDirectoryUrl,
4801
+ )}package.json imported from ${fileURLToPath(parentUrl)}; ${reason}`;
4802
+ }
4803
+ const error = new Error(message);
4804
+ error.code = "INVALID_PACKAGE_TARGET";
4805
+ return error;
4806
+ };
4807
+
4808
+ const createPackagePathNotExportedError = (
4809
+ subpath,
4810
+ { parentUrl, packageDirectoryUrl },
4811
+ ) => {
4812
+ let message;
4813
+ if (subpath === ".") {
4814
+ message = `No "exports" main defined in ${fileURLToPath(
4815
+ packageDirectoryUrl,
4816
+ )}package.json imported from ${fileURLToPath(parentUrl)}`;
4817
+ } else {
4818
+ message = `Package subpath "${subpath}" is not defined by "exports" in ${fileURLToPath(
4819
+ packageDirectoryUrl,
4820
+ )}package.json imported from ${fileURLToPath(parentUrl)}`;
4821
+ }
4822
+ const error = new Error(message);
4823
+ error.code = "PACKAGE_PATH_NOT_EXPORTED";
4824
+ return error;
4825
+ };
4826
+
4827
+ const createModuleNotFoundError = (specifier, { parentUrl }) => {
4828
+ const error = new Error(
4829
+ `Cannot find "${specifier}" imported from ${fileURLToPath(parentUrl)}`,
4830
+ );
4831
+ error.code = "MODULE_NOT_FOUND";
4832
+ return error;
4833
+ };
4834
+
4835
+ const createPackageImportNotDefinedError = (
4836
+ specifier,
4837
+ { parentUrl, packageDirectoryUrl },
4838
+ ) => {
4839
+ const error = new Error(
4840
+ `Package import specifier "${specifier}" is not defined in ${fileURLToPath(
4841
+ packageDirectoryUrl,
4842
+ )}package.json imported from ${fileURLToPath(parentUrl)}`,
4843
+ );
4844
+ error.code = "PACKAGE_IMPORT_NOT_DEFINED";
4845
+ return error;
4846
+ };
4847
+
4848
+ const isSpecifierForNodeBuiltin = (specifier) => {
4849
+ return (
4850
+ specifier.startsWith("node:") ||
4851
+ NODE_BUILTIN_MODULE_SPECIFIERS.includes(specifier)
4852
+ );
4853
+ };
4854
+
4855
+ const NODE_BUILTIN_MODULE_SPECIFIERS = [
4856
+ "assert",
4857
+ "assert/strict",
4858
+ "async_hooks",
4859
+ "buffer_ieee754",
4860
+ "buffer",
4861
+ "child_process",
4862
+ "cluster",
4863
+ "console",
4864
+ "constants",
4865
+ "crypto",
4866
+ "_debugger",
4867
+ "diagnostics_channel",
4868
+ "dgram",
4869
+ "dns",
4870
+ "domain",
4871
+ "events",
4872
+ "freelist",
4873
+ "fs",
4874
+ "fsevents",
4875
+ "fs/promises",
4876
+ "_http_agent",
4877
+ "_http_client",
4878
+ "_http_common",
4879
+ "_http_incoming",
4880
+ "_http_outgoing",
4881
+ "_http_server",
4882
+ "http",
4883
+ "http2",
4884
+ "https",
4885
+ "inspector",
4886
+ "_linklist",
4887
+ "module",
4888
+ "net",
4889
+ "node-inspect/lib/_inspect",
4890
+ "node-inspect/lib/internal/inspect_client",
4891
+ "node-inspect/lib/internal/inspect_repl",
4892
+ "os",
4893
+ "path",
4894
+ "perf_hooks",
4895
+ "process",
4896
+ "punycode",
4897
+ "querystring",
4898
+ "readline",
4899
+ "repl",
4900
+ "smalloc",
4901
+ "sqlite",
4902
+ "_stream_duplex",
4903
+ "_stream_transform",
4904
+ "_stream_wrap",
4905
+ "_stream_passthrough",
4906
+ "_stream_readable",
4907
+ "_stream_writable",
4908
+ "stream",
4909
+ "stream/promises",
4910
+ "string_decoder",
4911
+ "sys",
4912
+ "timers",
4913
+ "_tls_common",
4914
+ "_tls_legacy",
4915
+ "_tls_wrap",
4916
+ "tls",
4917
+ "trace_events",
4918
+ "tty",
4919
+ "url",
4920
+ "util",
4921
+ "v8/tools/arguments",
4922
+ "v8/tools/codemap",
4923
+ "v8/tools/consarray",
4924
+ "v8/tools/csvparser",
4925
+ "v8/tools/logreader",
4926
+ "v8/tools/profile_view",
4927
+ "v8/tools/splaytree",
4928
+ "v8",
4929
+ "vm",
4930
+ "worker_threads",
4931
+ "zlib",
4932
+ // global is special
4933
+ "global",
4934
+ ];
4935
+
4936
+ /*
4937
+ * https://nodejs.org/api/esm.html#resolver-algorithm-specification
4938
+ * https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/lib/internal/modules/esm/resolve.js#L1
4939
+ * deviations from the spec:
4940
+ * - take into account "browser", "module" and "jsnext"
4941
+ * - the check for isDirectory -> throw is delayed is descoped to the caller
4942
+ * - the call to real path ->
4943
+ * delayed to the caller so that we can decide to
4944
+ * maintain symlink as facade url when it's outside project directory
4945
+ * or use the real path when inside
4946
+ */
4947
+
4948
+ const applyNodeEsmResolution = ({
4949
+ specifier,
4950
+ parentUrl,
4951
+ conditions = [...readCustomConditionsFromProcessArgs(), "node", "import"],
4952
+ lookupPackageScope = defaultLookupPackageScope,
4953
+ readPackageJson = defaultReadPackageJson,
4954
+ preservesSymlink = false,
4955
+ }) => {
4956
+ const resolution = applyPackageSpecifierResolution(specifier, {
4957
+ parentUrl: String(parentUrl),
4958
+ conditions,
4959
+ lookupPackageScope,
4960
+ readPackageJson,
4961
+ preservesSymlink,
4962
+ });
4963
+ const { url } = resolution;
4964
+ if (url.startsWith("file:")) {
4965
+ if (url.includes("%2F") || url.includes("%5C")) {
4966
+ throw createInvalidModuleSpecifierError(
4967
+ `must not include encoded "/" or "\\" characters`,
4968
+ specifier,
4969
+ {
4970
+ parentUrl,
4971
+ },
5312
4972
  );
5313
- return highestVersion !== runtimeVersion;
5314
- });
5315
- return !runtimeWithoutCompat;
5316
- },
5317
- };
5318
-
5319
- const getFeatureCompat = (feature) => {
5320
- if (typeof feature === "string") {
5321
- const compat = featuresCompatMap[feature];
5322
- if (!compat) {
5323
- throw new Error(`"${feature}" feature is unknown`);
5324
4973
  }
5325
- return compat;
5326
- }
5327
- if (typeof feature !== "object") {
5328
- throw new TypeError(
5329
- `feature must be a string or an object, got ${feature}`,
5330
- );
4974
+ return resolution;
5331
4975
  }
5332
- return feature;
4976
+ return resolution;
5333
4977
  };
5334
4978
 
5335
- const isSupportedAlgorithm = (algo) => {
5336
- return SUPPORTED_ALGORITHMS.includes(algo);
4979
+ const createResolutionResult = (data) => {
4980
+ return data;
5337
4981
  };
5338
4982
 
5339
- // https://www.w3.org/TR/SRI/#priority
5340
- const getPrioritizedHashFunction = (firstAlgo, secondAlgo) => {
5341
- const firstIndex = SUPPORTED_ALGORITHMS.indexOf(firstAlgo);
5342
- const secondIndex = SUPPORTED_ALGORITHMS.indexOf(secondAlgo);
5343
- if (firstIndex === secondIndex) {
5344
- return "";
4983
+ const applyPackageSpecifierResolution = (specifier, resolutionContext) => {
4984
+ const { parentUrl } = resolutionContext;
4985
+ // relative specifier
4986
+ if (
4987
+ specifier[0] === "/" ||
4988
+ specifier.startsWith("./") ||
4989
+ specifier.startsWith("../")
4990
+ ) {
4991
+ if (specifier[0] !== "/") {
4992
+ const browserFieldResolution = applyBrowserFieldResolution(
4993
+ specifier,
4994
+ resolutionContext,
4995
+ );
4996
+ if (browserFieldResolution) {
4997
+ return browserFieldResolution;
4998
+ }
4999
+ }
5000
+ return createResolutionResult({
5001
+ type: "relative_specifier",
5002
+ url: new URL(specifier, parentUrl).href,
5003
+ });
5345
5004
  }
5346
- if (firstIndex < secondIndex) {
5347
- return secondAlgo;
5005
+ if (specifier[0] === "#") {
5006
+ return applyPackageImportsResolution(specifier, resolutionContext);
5348
5007
  }
5349
- return firstAlgo;
5350
- };
5351
-
5352
- const applyAlgoToRepresentationData = (algo, data) => {
5353
- const base64Value = crypto.createHash(algo).update(data).digest("base64");
5354
- return base64Value;
5355
- };
5356
-
5357
- // keep this ordered by collision resistance as it is also used by "getPrioritizedHashFunction"
5358
- const SUPPORTED_ALGORITHMS = ["sha256", "sha384", "sha512"];
5359
-
5360
- // see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
5361
- const parseIntegrity = (string) => {
5362
- const integrityMetadata = {};
5363
- string
5364
- .trim()
5365
- .split(/\s+/)
5366
- .forEach((token) => {
5367
- const { isValid, algo, base64Value, optionExpression } =
5368
- parseAsHashWithOptions(token);
5369
- if (!isValid) {
5370
- return;
5371
- }
5372
- if (!isSupportedAlgorithm(algo)) {
5373
- return;
5374
- }
5375
- const metadataList = integrityMetadata[algo];
5376
- const metadata = { base64Value, optionExpression };
5377
- integrityMetadata[algo] = metadataList
5378
- ? [...metadataList, metadata]
5379
- : [metadata];
5008
+ try {
5009
+ const urlObject = new URL(specifier);
5010
+ if (specifier.startsWith("node:")) {
5011
+ return createResolutionResult({
5012
+ type: "node_builtin_specifier",
5013
+ url: specifier,
5014
+ });
5015
+ }
5016
+ return createResolutionResult({
5017
+ type: "absolute_specifier",
5018
+ url: urlObject.href,
5380
5019
  });
5381
- return integrityMetadata;
5020
+ } catch {
5021
+ // bare specifier
5022
+ const browserFieldResolution = applyBrowserFieldResolution(
5023
+ specifier,
5024
+ resolutionContext,
5025
+ );
5026
+ if (browserFieldResolution) {
5027
+ return browserFieldResolution;
5028
+ }
5029
+ const packageResolution = applyPackageResolve(specifier, resolutionContext);
5030
+ const search = new URL(specifier, "file:///").search;
5031
+ if (search && !new URL(packageResolution.url).search) {
5032
+ packageResolution.url = `${packageResolution.url}${search}`;
5033
+ }
5034
+ return packageResolution;
5035
+ }
5382
5036
  };
5383
5037
 
5384
- // see https://w3c.github.io/webappsec-subresource-integrity/#the-integrity-attribute
5385
- const parseAsHashWithOptions = (token) => {
5386
- const dashIndex = token.indexOf("-");
5387
- if (dashIndex === -1) {
5388
- return { isValid: false };
5038
+ const applyBrowserFieldResolution = (specifier, resolutionContext) => {
5039
+ const { parentUrl, conditions, lookupPackageScope, readPackageJson } =
5040
+ resolutionContext;
5041
+ const browserCondition = conditions.includes("browser");
5042
+ if (!browserCondition) {
5043
+ return null;
5389
5044
  }
5390
- const beforeDash = token.slice(0, dashIndex);
5391
- const afterDash = token.slice(dashIndex + 1);
5392
- const questionIndex = afterDash.indexOf("?");
5393
- const algo = beforeDash;
5394
- if (questionIndex === -1) {
5395
- const base64Value = afterDash;
5396
- const isValid = BASE64_REGEX.test(afterDash);
5397
- return { isValid, algo, base64Value };
5045
+ const packageDirectoryUrl = lookupPackageScope(parentUrl);
5046
+ if (!packageDirectoryUrl) {
5047
+ return null;
5398
5048
  }
5399
- const base64Value = afterDash.slice(0, questionIndex);
5400
- const optionExpression = afterDash.slice(questionIndex + 1);
5401
- const isValid =
5402
- BASE64_REGEX.test(afterDash) && VCHAR_REGEX.test(optionExpression);
5403
- return { isValid, algo, base64Value, optionExpression };
5404
- };
5405
-
5406
- const BASE64_REGEX = /^[A-Za-z0-9+/=]+$/;
5407
- const VCHAR_REGEX = /^[\x21-\x7E]+$/;
5408
-
5409
- // https://www.w3.org/TR/SRI/#does-response-match-metadatalist
5410
- const validateResponseIntegrity = (
5411
- { url, type, dataRepresentation },
5412
- integrity,
5413
- ) => {
5414
- if (!isResponseEligibleForIntegrityValidation({ type })) {
5415
- return false;
5049
+ const packageJson = readPackageJson(packageDirectoryUrl);
5050
+ if (!packageJson) {
5051
+ return null;
5416
5052
  }
5417
- const integrityMetadata = parseIntegrity(integrity);
5418
- const algos = Object.keys(integrityMetadata);
5419
- if (algos.length === 0) {
5420
- return true;
5053
+ const { browser } = packageJson;
5054
+ if (!browser) {
5055
+ return null;
5421
5056
  }
5422
- let strongestAlgo = algos[0];
5423
- algos.slice(1).forEach((algoCandidate) => {
5424
- strongestAlgo =
5425
- getPrioritizedHashFunction(strongestAlgo, algoCandidate) || strongestAlgo;
5426
- });
5427
- const metadataList = integrityMetadata[strongestAlgo];
5428
- const actualBase64Value = applyAlgoToRepresentationData(
5429
- strongestAlgo,
5430
- dataRepresentation,
5431
- );
5432
- const acceptedBase64Values = metadataList.map(
5433
- (metadata) => metadata.base64Value,
5434
- );
5435
- const someIsMatching = acceptedBase64Values.includes(actualBase64Value);
5436
- if (someIsMatching) {
5437
- return true;
5057
+ if (typeof browser !== "object") {
5058
+ return null;
5438
5059
  }
5439
- const error = new Error(
5440
- `Integrity validation failed for resource "${url}". The integrity found for this resource is "${strongestAlgo}-${actualBase64Value}"`,
5441
- );
5442
- error.code = "EINTEGRITY";
5443
- error.algorithm = strongestAlgo;
5444
- error.found = actualBase64Value;
5445
- throw error;
5060
+ let url;
5061
+ if (specifier.startsWith(".")) {
5062
+ const specifierUrl = new URL(specifier, parentUrl).href;
5063
+ const specifierRelativeUrl = specifierUrl.slice(packageDirectoryUrl.length);
5064
+ const secifierRelativeNotation = `./${specifierRelativeUrl}`;
5065
+ const browserMapping = browser[secifierRelativeNotation];
5066
+ if (typeof browserMapping === "string") {
5067
+ url = new URL(browserMapping, packageDirectoryUrl).href;
5068
+ } else if (browserMapping === false) {
5069
+ url = `file:///@ignore/${specifierUrl.slice("file:///")}`;
5070
+ }
5071
+ } else {
5072
+ const browserMapping = browser[specifier];
5073
+ if (typeof browserMapping === "string") {
5074
+ url = new URL(browserMapping, packageDirectoryUrl).href;
5075
+ } else if (browserMapping === false) {
5076
+ url = `file:///@ignore/${specifier}`;
5077
+ }
5078
+ }
5079
+ if (url) {
5080
+ return createResolutionResult({
5081
+ type: "field:browser",
5082
+ isMain: true,
5083
+ packageDirectoryUrl,
5084
+ packageJson,
5085
+ url,
5086
+ });
5087
+ }
5088
+ return null;
5446
5089
  };
5447
5090
 
5448
- // https://www.w3.org/TR/SRI/#is-response-eligible-for-integrity-validation
5449
- const isResponseEligibleForIntegrityValidation = (response) => {
5450
- return ["basic", "cors", "default"].includes(response.type);
5091
+ const applyPackageImportsResolution = (
5092
+ internalSpecifier,
5093
+ resolutionContext,
5094
+ ) => {
5095
+ const { parentUrl, lookupPackageScope, readPackageJson } = resolutionContext;
5096
+ if (internalSpecifier === "#" || internalSpecifier.startsWith("#/")) {
5097
+ throw createInvalidModuleSpecifierError(
5098
+ "not a valid internal imports specifier name",
5099
+ internalSpecifier,
5100
+ resolutionContext,
5101
+ );
5102
+ }
5103
+ const packageDirectoryUrl = lookupPackageScope(parentUrl);
5104
+ if (packageDirectoryUrl !== null) {
5105
+ const packageJson = readPackageJson(packageDirectoryUrl);
5106
+ const { imports } = packageJson;
5107
+ if (imports !== null && typeof imports === "object") {
5108
+ const resolved = applyPackageImportsExportsResolution(internalSpecifier, {
5109
+ ...resolutionContext,
5110
+ packageDirectoryUrl,
5111
+ packageJson,
5112
+ isImport: true,
5113
+ });
5114
+ if (resolved) {
5115
+ return resolved;
5116
+ }
5117
+ }
5118
+ }
5119
+ throw createPackageImportNotDefinedError(internalSpecifier, {
5120
+ ...resolutionContext,
5121
+ packageDirectoryUrl,
5122
+ });
5451
5123
  };
5452
5124
 
5453
- const assertImportMap = (value) => {
5454
- if (value === null) {
5455
- throw new TypeError(`an importMap must be an object, got null`);
5125
+ const applyPackageResolve = (packageSpecifier, resolutionContext) => {
5126
+ const { parentUrl, conditions, readPackageJson, preservesSymlink, isImport } =
5127
+ resolutionContext;
5128
+ if (packageSpecifier === "") {
5129
+ throw new Error("invalid module specifier");
5456
5130
  }
5457
-
5458
- const type = typeof value;
5459
- if (type !== "object") {
5460
- throw new TypeError(`an importMap must be an object, received ${value}`);
5131
+ if (
5132
+ conditions.includes("node") &&
5133
+ isSpecifierForNodeBuiltin(packageSpecifier)
5134
+ ) {
5135
+ return createResolutionResult({
5136
+ type: "node_builtin_specifier",
5137
+ url: `node:${packageSpecifier}`,
5138
+ });
5461
5139
  }
5462
-
5463
- if (Array.isArray(value)) {
5464
- throw new TypeError(
5465
- `an importMap must be an object, received array ${value}`,
5140
+ let { packageName, packageSubpath } = parsePackageSpecifier(packageSpecifier);
5141
+ if (
5142
+ packageName[0] === "." ||
5143
+ packageName.includes("\\") ||
5144
+ packageName.includes("%")
5145
+ ) {
5146
+ throw createInvalidModuleSpecifierError(
5147
+ `is not a valid package name`,
5148
+ packageName,
5149
+ resolutionContext,
5466
5150
  );
5467
5151
  }
5468
- };
5469
-
5470
- // duplicated from @jsenv/log to avoid the dependency
5471
- const createDetailedMessage = (message, details = {}) => {
5472
- let string = `${message}`;
5473
-
5474
- Object.keys(details).forEach((key) => {
5475
- const value = details[key];
5476
- string += `
5477
- --- ${key} ---
5478
- ${
5479
- Array.isArray(value)
5480
- ? value.join(`
5481
- `)
5482
- : value
5483
- }`;
5152
+ if (isImport && packageSubpath.endsWith("/")) {
5153
+ throw new Error("invalid module specifier");
5154
+ }
5155
+ const questionCharIndex = packageName.indexOf("?");
5156
+ if (questionCharIndex > -1) {
5157
+ packageName = packageName.slice(0, questionCharIndex);
5158
+ }
5159
+ const selfResolution = applyPackageSelfResolution(packageSubpath, {
5160
+ ...resolutionContext,
5161
+ packageName,
5484
5162
  });
5485
-
5486
- return string;
5487
- };
5488
-
5489
- const hasScheme = (string) => {
5490
- return /^[a-zA-Z]{2,}:/.test(string);
5491
- };
5492
-
5493
- const pathnameToParentPathname = (pathname) => {
5494
- const slashLastIndex = pathname.lastIndexOf("/");
5495
- if (slashLastIndex === -1) {
5496
- return "/";
5163
+ if (selfResolution) {
5164
+ return selfResolution;
5497
5165
  }
5498
-
5499
- return pathname.slice(0, slashLastIndex + 1);
5500
- };
5501
-
5502
- const urlToScheme = (urlString) => {
5503
- const colonIndex = urlString.indexOf(":");
5504
- if (colonIndex === -1) return "";
5505
- return urlString.slice(0, colonIndex);
5166
+ let currentUrl = parentUrl;
5167
+ while (currentUrl !== "file:///") {
5168
+ const packageDirectoryFacadeUrl = new URL(
5169
+ `node_modules/${packageName}/`,
5170
+ currentUrl,
5171
+ ).href;
5172
+ if (!existsSync(new URL(packageDirectoryFacadeUrl))) {
5173
+ currentUrl = getParentUrl(currentUrl);
5174
+ continue;
5175
+ }
5176
+ const packageDirectoryUrl = preservesSymlink
5177
+ ? packageDirectoryFacadeUrl
5178
+ : resolvePackageSymlink(packageDirectoryFacadeUrl);
5179
+ const packageJson = readPackageJson(packageDirectoryUrl);
5180
+ if (packageJson !== null) {
5181
+ const { exports: exports$1 } = packageJson;
5182
+ if (exports$1 !== null && exports$1 !== undefined) {
5183
+ return applyPackageExportsResolution(packageSubpath, {
5184
+ ...resolutionContext,
5185
+ packageDirectoryUrl,
5186
+ packageJson,
5187
+ exports: exports$1,
5188
+ });
5189
+ }
5190
+ }
5191
+ return applyLegacySubpathResolution(packageSubpath, {
5192
+ ...resolutionContext,
5193
+ packageDirectoryUrl,
5194
+ packageJson,
5195
+ });
5196
+ }
5197
+ throw createModuleNotFoundError(packageName, resolutionContext);
5506
5198
  };
5507
5199
 
5508
- const urlToOrigin = (urlString) => {
5509
- const scheme = urlToScheme(urlString);
5510
-
5511
- if (scheme === "file") {
5512
- return "file://";
5200
+ const applyPackageSelfResolution = (packageSubpath, resolutionContext) => {
5201
+ const { parentUrl, packageName, lookupPackageScope, readPackageJson } =
5202
+ resolutionContext;
5203
+ const packageDirectoryUrl = lookupPackageScope(parentUrl);
5204
+ if (!packageDirectoryUrl) {
5205
+ return undefined;
5513
5206
  }
5514
-
5515
- if (scheme === "http" || scheme === "https") {
5516
- const secondProtocolSlashIndex = scheme.length + "://".length;
5517
- const pathnameSlashIndex = urlString.indexOf("/", secondProtocolSlashIndex);
5518
-
5519
- if (pathnameSlashIndex === -1) return urlString;
5520
- return urlString.slice(0, pathnameSlashIndex);
5207
+ const packageJson = readPackageJson(packageDirectoryUrl);
5208
+ if (!packageJson) {
5209
+ return undefined;
5521
5210
  }
5522
-
5523
- return urlString.slice(0, scheme.length + 1);
5524
- };
5525
-
5526
- const urlToPathname = (urlString) => {
5527
- return ressourceToPathname(urlToRessource(urlString));
5211
+ if (packageJson.name !== packageName) {
5212
+ return undefined;
5213
+ }
5214
+ const { exports: exports$1 } = packageJson;
5215
+ if (!exports$1) {
5216
+ const subpathResolution = applyLegacySubpathResolution(packageSubpath, {
5217
+ ...resolutionContext,
5218
+ packageDirectoryUrl,
5219
+ packageJson,
5220
+ });
5221
+ if (subpathResolution && subpathResolution.type !== "subpath") {
5222
+ return subpathResolution;
5223
+ }
5224
+ return undefined;
5225
+ }
5226
+ return applyPackageExportsResolution(packageSubpath, {
5227
+ ...resolutionContext,
5228
+ packageDirectoryUrl,
5229
+ packageJson,
5230
+ });
5528
5231
  };
5529
5232
 
5530
- const urlToRessource = (urlString) => {
5531
- const scheme = urlToScheme(urlString);
5532
-
5533
- if (scheme === "file") {
5534
- return urlString.slice("file://".length);
5233
+ // https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/lib/internal/modules/esm/resolve.js#L642
5234
+ const applyPackageExportsResolution = (packageSubpath, resolutionContext) => {
5235
+ if (packageSubpath === ".") {
5236
+ const mainExport = applyMainExportResolution(resolutionContext);
5237
+ if (!mainExport) {
5238
+ throw createPackagePathNotExportedError(
5239
+ packageSubpath,
5240
+ resolutionContext,
5241
+ );
5242
+ }
5243
+ const resolved = applyPackageTargetResolution(mainExport, {
5244
+ ...resolutionContext,
5245
+ key: ".",
5246
+ });
5247
+ if (resolved) {
5248
+ return resolved;
5249
+ }
5250
+ throw createPackagePathNotExportedError(packageSubpath, resolutionContext);
5535
5251
  }
5536
-
5537
- if (scheme === "https" || scheme === "http") {
5538
- // remove origin
5539
- const afterProtocol = urlString.slice(scheme.length + "://".length);
5540
- const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length);
5541
- return afterProtocol.slice(pathnameSlashIndex);
5252
+ const packageExportsInfo = readExports(resolutionContext);
5253
+ if (
5254
+ packageExportsInfo.type === "object" &&
5255
+ packageExportsInfo.allKeysAreRelative
5256
+ ) {
5257
+ const resolved = applyPackageImportsExportsResolution(packageSubpath, {
5258
+ ...resolutionContext,
5259
+ isImport: false,
5260
+ });
5261
+ if (resolved) {
5262
+ return resolved;
5263
+ }
5542
5264
  }
5543
-
5544
- return urlString.slice(scheme.length + 1);
5265
+ throw createPackagePathNotExportedError(packageSubpath, resolutionContext);
5545
5266
  };
5546
5267
 
5547
- const ressourceToPathname = (ressource) => {
5548
- const searchSeparatorIndex = ressource.indexOf("?");
5549
- return searchSeparatorIndex === -1
5550
- ? ressource
5551
- : ressource.slice(0, searchSeparatorIndex);
5552
- };
5268
+ const applyPackageImportsExportsResolution = (matchKey, resolutionContext) => {
5269
+ const { packageJson, isImport } = resolutionContext;
5270
+ const matchObject = isImport ? packageJson.imports : packageJson.exports;
5553
5271
 
5554
- // could be useful: https://url.spec.whatwg.org/#url-miscellaneous
5272
+ if (!matchKey.includes("*") && matchObject.hasOwnProperty(matchKey)) {
5273
+ const target = matchObject[matchKey];
5274
+ return applyPackageTargetResolution(target, {
5275
+ ...resolutionContext,
5276
+ key: matchKey,
5277
+ isImport,
5278
+ });
5279
+ }
5280
+ const expansionKeys = Object.keys(matchObject)
5281
+ .filter((key) => key.split("*").length === 2)
5282
+ .sort(comparePatternKeys);
5283
+ for (const expansionKey of expansionKeys) {
5284
+ const [patternBase, patternTrailer] = expansionKey.split("*");
5285
+ if (matchKey === patternBase) continue;
5286
+ if (!matchKey.startsWith(patternBase)) continue;
5287
+ if (patternTrailer.length > 0) {
5288
+ if (!matchKey.endsWith(patternTrailer)) continue;
5289
+ if (matchKey.length < expansionKey.length) continue;
5290
+ }
5291
+ const target = matchObject[expansionKey];
5292
+ const subpath = matchKey.slice(
5293
+ patternBase.length,
5294
+ matchKey.length - patternTrailer.length,
5295
+ );
5296
+ return applyPackageTargetResolution(target, {
5297
+ ...resolutionContext,
5298
+ key: matchKey,
5299
+ subpath,
5300
+ pattern: true,
5301
+ isImport,
5302
+ });
5303
+ }
5304
+ return null;
5305
+ };
5555
5306
 
5307
+ const applyPackageTargetResolution = (target, resolutionContext) => {
5308
+ const {
5309
+ conditions,
5310
+ packageDirectoryUrl,
5311
+ packageJson,
5312
+ key,
5313
+ subpath = "",
5314
+ pattern = false,
5315
+ isImport = false,
5316
+ } = resolutionContext;
5556
5317
 
5557
- const resolveUrl = (specifier, baseUrl) => {
5558
- if (baseUrl) {
5559
- if (typeof baseUrl !== "string") {
5560
- throw new TypeError(writeBaseUrlMustBeAString({ baseUrl, specifier }));
5318
+ if (typeof target === "string") {
5319
+ if (pattern === false && subpath !== "" && !target.endsWith("/")) {
5320
+ throw new Error("invalid module specifier");
5561
5321
  }
5562
- if (!hasScheme(baseUrl)) {
5563
- throw new Error(writeBaseUrlMustBeAbsolute({ baseUrl, specifier }));
5322
+ if (target.startsWith("./")) {
5323
+ const targetUrl = new URL(target, packageDirectoryUrl).href;
5324
+ if (!targetUrl.startsWith(packageDirectoryUrl)) {
5325
+ throw createInvalidPackageTargetError(
5326
+ `target must be inside package`,
5327
+ target,
5328
+ resolutionContext,
5329
+ );
5330
+ }
5331
+ return createResolutionResult({
5332
+ type: isImport ? "field:imports" : "field:exports",
5333
+ isMain: subpath === "" || subpath === ".",
5334
+ packageDirectoryUrl,
5335
+ packageJson,
5336
+ url: pattern
5337
+ ? targetUrl.replaceAll("*", subpath)
5338
+ : new URL(subpath, targetUrl).href,
5339
+ });
5564
5340
  }
5341
+ if (!isImport || target.startsWith("../") || isValidUrl(target)) {
5342
+ throw createInvalidPackageTargetError(
5343
+ `target must starst with "./"`,
5344
+ target,
5345
+ resolutionContext,
5346
+ );
5347
+ }
5348
+ return applyPackageResolve(
5349
+ pattern ? target.replaceAll("*", subpath) : `${target}${subpath}`,
5350
+ {
5351
+ ...resolutionContext,
5352
+ parentUrl: packageDirectoryUrl,
5353
+ },
5354
+ );
5565
5355
  }
5566
-
5567
- if (hasScheme(specifier)) {
5568
- return specifier;
5569
- }
5570
-
5571
- if (!baseUrl) {
5572
- throw new Error(writeBaseUrlRequired({ baseUrl, specifier }));
5356
+ if (Array.isArray(target)) {
5357
+ if (target.length === 0) {
5358
+ return null;
5359
+ }
5360
+ let lastResult;
5361
+ let i = 0;
5362
+ while (i < target.length) {
5363
+ const targetValue = target[i];
5364
+ i++;
5365
+ try {
5366
+ const resolved = applyPackageTargetResolution(targetValue, {
5367
+ ...resolutionContext,
5368
+ key: `${key}[${i}]`,
5369
+ subpath,
5370
+ pattern,
5371
+ isImport,
5372
+ });
5373
+ if (resolved) {
5374
+ return resolved;
5375
+ }
5376
+ lastResult = resolved;
5377
+ } catch (e) {
5378
+ if (e.code === "INVALID_PACKAGE_TARGET") {
5379
+ continue;
5380
+ }
5381
+ lastResult = e;
5382
+ }
5383
+ }
5384
+ if (lastResult) {
5385
+ throw lastResult;
5386
+ }
5387
+ return null;
5573
5388
  }
5574
-
5575
- // scheme relative
5576
- if (specifier.slice(0, 2) === "//") {
5577
- return `${urlToScheme(baseUrl)}:${specifier}`;
5389
+ if (target === null) {
5390
+ return null;
5578
5391
  }
5579
-
5580
- // origin relative
5581
- if (specifier[0] === "/") {
5582
- return `${urlToOrigin(baseUrl)}${specifier}`;
5392
+ if (typeof target === "object") {
5393
+ const keys = Object.keys(target);
5394
+ for (const key of keys) {
5395
+ if (Number.isInteger(key)) {
5396
+ throw new Error("Invalid package configuration");
5397
+ }
5398
+ let matched;
5399
+ if (key === "default") {
5400
+ matched = true;
5401
+ } else {
5402
+ for (const conditionCandidate of conditions) {
5403
+ if (conditionCandidate === key) {
5404
+ matched = true;
5405
+ break;
5406
+ }
5407
+ if (conditionCandidate.includes("*")) {
5408
+ const conditionCandidateRegex = new RegExp(
5409
+ `^${conditionCandidate.replace(/\*/g, "(.*)")}$`,
5410
+ );
5411
+ if (conditionCandidateRegex.test(key)) {
5412
+ matched = true;
5413
+ break;
5414
+ }
5415
+ }
5416
+ }
5417
+ }
5418
+ if (matched) {
5419
+ const targetValue = target[key];
5420
+ const resolved = applyPackageTargetResolution(targetValue, {
5421
+ ...resolutionContext,
5422
+ key,
5423
+ subpath,
5424
+ pattern,
5425
+ isImport,
5426
+ });
5427
+ if (resolved) {
5428
+ return resolved;
5429
+ }
5430
+ }
5431
+ }
5432
+ return null;
5583
5433
  }
5434
+ throw createInvalidPackageTargetError(
5435
+ `target must be a string, array, object or null`,
5436
+ target,
5437
+ resolutionContext,
5438
+ );
5439
+ };
5584
5440
 
5585
- const baseOrigin = urlToOrigin(baseUrl);
5586
- const basePathname = urlToPathname(baseUrl);
5587
-
5588
- if (specifier === ".") {
5589
- const baseDirectoryPathname = pathnameToParentPathname(basePathname);
5590
- return `${baseOrigin}${baseDirectoryPathname}`;
5441
+ const readExports = ({ packageDirectoryUrl, packageJson }) => {
5442
+ const packageExports = packageJson.exports;
5443
+ if (Array.isArray(packageExports)) {
5444
+ return {
5445
+ type: "array",
5446
+ };
5591
5447
  }
5592
-
5593
- // pathname relative inside
5594
- if (specifier.slice(0, 2) === "./") {
5595
- const baseDirectoryPathname = pathnameToParentPathname(basePathname);
5596
- return `${baseOrigin}${baseDirectoryPathname}${specifier.slice(2)}`;
5448
+ if (packageExports === null) {
5449
+ return {};
5597
5450
  }
5598
-
5599
- // pathname relative outside
5600
- if (specifier.slice(0, 3) === "../") {
5601
- let unresolvedPathname = specifier;
5602
- const importerFolders = basePathname.split("/");
5603
- importerFolders.pop();
5604
-
5605
- while (unresolvedPathname.slice(0, 3) === "../") {
5606
- unresolvedPathname = unresolvedPathname.slice(3);
5607
- // when there is no folder left to resolved
5608
- // we just ignore '../'
5609
- if (importerFolders.length) {
5610
- importerFolders.pop();
5451
+ if (typeof packageExports === "object") {
5452
+ const keys = Object.keys(packageExports);
5453
+ const relativeKeys = [];
5454
+ const conditionalKeys = [];
5455
+ keys.forEach((availableKey) => {
5456
+ if (availableKey.startsWith(".")) {
5457
+ relativeKeys.push(availableKey);
5458
+ } else {
5459
+ conditionalKeys.push(availableKey);
5611
5460
  }
5461
+ });
5462
+ const hasRelativeKey = relativeKeys.length > 0;
5463
+ if (hasRelativeKey && conditionalKeys.length > 0) {
5464
+ throw new Error(
5465
+ `Invalid package configuration: cannot mix relative and conditional keys in package.exports
5466
+ --- unexpected keys ---
5467
+ ${conditionalKeys.map((key) => `"${key}"`).join("\n")}
5468
+ --- package directory url ---
5469
+ ${packageDirectoryUrl}`,
5470
+ );
5612
5471
  }
5613
-
5614
- const resolvedPathname = `${importerFolders.join(
5615
- "/",
5616
- )}/${unresolvedPathname}`;
5617
- return `${baseOrigin}${resolvedPathname}`;
5618
- }
5619
-
5620
- // bare
5621
- if (basePathname === "") {
5622
- return `${baseOrigin}/${specifier}`;
5472
+ return {
5473
+ type: "object",
5474
+ hasRelativeKey,
5475
+ allKeysAreRelative: relativeKeys.length === keys.length,
5476
+ };
5623
5477
  }
5624
- if (basePathname[basePathname.length] === "/") {
5625
- return `${baseOrigin}${basePathname}${specifier}`;
5478
+ if (typeof packageExports === "string") {
5479
+ return { type: "string" };
5626
5480
  }
5627
- return `${baseOrigin}${pathnameToParentPathname(basePathname)}${specifier}`;
5481
+ return {};
5628
5482
  };
5629
5483
 
5630
- const writeBaseUrlMustBeAString = ({
5631
- baseUrl,
5632
- specifier,
5633
- }) => `baseUrl must be a string.
5634
- --- base url ---
5635
- ${baseUrl}
5636
- --- specifier ---
5637
- ${specifier}`;
5638
-
5639
- const writeBaseUrlMustBeAbsolute = ({
5640
- baseUrl,
5641
- specifier,
5642
- }) => `baseUrl must be absolute.
5643
- --- base url ---
5644
- ${baseUrl}
5645
- --- specifier ---
5646
- ${specifier}`;
5647
-
5648
- const writeBaseUrlRequired = ({
5649
- baseUrl,
5650
- specifier,
5651
- }) => `baseUrl required to resolve relative specifier.
5652
- --- base url ---
5653
- ${baseUrl}
5654
- --- specifier ---
5655
- ${specifier}`;
5656
-
5657
- const tryUrlResolution = (string, url) => {
5658
- const result = resolveUrl(string, url);
5659
- return hasScheme(result) ? result : null;
5484
+ const parsePackageSpecifier = (packageSpecifier) => {
5485
+ if (packageSpecifier[0] === "@") {
5486
+ const firstSlashIndex = packageSpecifier.indexOf("/");
5487
+ if (firstSlashIndex === -1) {
5488
+ throw new Error("invalid module specifier");
5489
+ }
5490
+ const secondSlashIndex = packageSpecifier.indexOf("/", firstSlashIndex + 1);
5491
+ if (secondSlashIndex === -1) {
5492
+ return {
5493
+ packageName: packageSpecifier,
5494
+ packageSubpath: ".",
5495
+ isScoped: true,
5496
+ };
5497
+ }
5498
+ const packageName = packageSpecifier.slice(0, secondSlashIndex);
5499
+ const afterSecondSlash = packageSpecifier.slice(secondSlashIndex + 1);
5500
+ const packageSubpath = `./${afterSecondSlash}`;
5501
+ return {
5502
+ packageName,
5503
+ packageSubpath,
5504
+ isScoped: true,
5505
+ };
5506
+ }
5507
+ const firstSlashIndex = packageSpecifier.indexOf("/");
5508
+ if (firstSlashIndex === -1) {
5509
+ return {
5510
+ packageName: packageSpecifier,
5511
+ packageSubpath: ".",
5512
+ };
5513
+ }
5514
+ const packageName = packageSpecifier.slice(0, firstSlashIndex);
5515
+ const afterFirstSlash = packageSpecifier.slice(firstSlashIndex + 1);
5516
+ if (afterFirstSlash === "") {
5517
+ return {
5518
+ packageName,
5519
+ packageSubpath: "/",
5520
+ };
5521
+ }
5522
+ const packageSubpath = `./${afterFirstSlash}`;
5523
+ return {
5524
+ packageName,
5525
+ packageSubpath,
5526
+ };
5660
5527
  };
5661
5528
 
5662
- const resolveSpecifier = (specifier, importer) => {
5529
+ const applyMainExportResolution = (resolutionContext) => {
5530
+ const { packageJson } = resolutionContext;
5531
+ const packageExportsInfo = readExports(resolutionContext);
5663
5532
  if (
5664
- specifier === "." ||
5665
- specifier[0] === "/" ||
5666
- specifier.startsWith("./") ||
5667
- specifier.startsWith("../")
5533
+ packageExportsInfo.type === "array" ||
5534
+ packageExportsInfo.type === "string"
5668
5535
  ) {
5669
- return resolveUrl(specifier, importer);
5536
+ return packageJson.exports;
5670
5537
  }
5671
-
5672
- if (hasScheme(specifier)) {
5673
- return specifier;
5538
+ if (packageExportsInfo.type === "object") {
5539
+ if (packageExportsInfo.hasRelativeKey) {
5540
+ return packageJson.exports["."];
5541
+ }
5542
+ return packageJson.exports;
5674
5543
  }
5675
-
5676
- return null;
5544
+ return undefined;
5677
5545
  };
5678
5546
 
5679
- const applyImportMap = ({
5680
- importMap,
5681
- specifier,
5682
- importer,
5683
- createBareSpecifierError = ({ specifier, importer }) => {
5684
- return new Error(
5685
- createDetailedMessage(`Unmapped bare specifier.`, {
5686
- specifier,
5687
- importer,
5688
- }),
5689
- );
5690
- },
5691
- onImportMapping = () => {},
5692
- }) => {
5693
- assertImportMap(importMap);
5694
- if (typeof specifier !== "string") {
5695
- throw new TypeError(
5696
- createDetailedMessage("specifier must be a string.", {
5697
- specifier,
5698
- importer,
5699
- }),
5700
- );
5547
+ const applyLegacySubpathResolution = (packageSubpath, resolutionContext) => {
5548
+ const { packageDirectoryUrl, packageJson } = resolutionContext;
5549
+
5550
+ if (packageSubpath === ".") {
5551
+ return applyLegacyMainResolution(packageSubpath, resolutionContext);
5701
5552
  }
5702
- if (importer) {
5703
- if (typeof importer !== "string") {
5704
- throw new TypeError(
5705
- createDetailedMessage("importer must be a string.", {
5706
- importer,
5707
- specifier,
5708
- }),
5709
- );
5553
+ const browserFieldResolution = applyBrowserFieldResolution(
5554
+ packageSubpath,
5555
+ resolutionContext,
5556
+ );
5557
+ if (browserFieldResolution) {
5558
+ return browserFieldResolution;
5559
+ }
5560
+ return createResolutionResult({
5561
+ type: "subpath",
5562
+ isMain: packageSubpath === ".",
5563
+ packageDirectoryUrl,
5564
+ packageJson,
5565
+ url: new URL(packageSubpath, packageDirectoryUrl).href,
5566
+ });
5567
+ };
5568
+
5569
+ const applyLegacyMainResolution = (packageSubpath, resolutionContext) => {
5570
+ const { conditions, packageDirectoryUrl, packageJson } = resolutionContext;
5571
+ for (const condition of conditions) {
5572
+ const conditionResolver = mainLegacyResolvers[condition];
5573
+ if (!conditionResolver) {
5574
+ continue;
5710
5575
  }
5711
- if (!hasScheme(importer)) {
5712
- throw new Error(
5713
- createDetailedMessage(`importer must be an absolute url.`, {
5714
- importer,
5715
- specifier,
5716
- }),
5717
- );
5576
+ const resolved = conditionResolver(resolutionContext);
5577
+ if (resolved) {
5578
+ return createResolutionResult({
5579
+ type: resolved.type,
5580
+ isMain: resolved.isMain,
5581
+ packageDirectoryUrl,
5582
+ packageJson,
5583
+ url: new URL(resolved.path, packageDirectoryUrl).href,
5584
+ });
5718
5585
  }
5719
5586
  }
5587
+ return createResolutionResult({
5588
+ type: "field:main", // the absence of "main" field
5589
+ isMain: true,
5590
+ packageDirectoryUrl,
5591
+ packageJson,
5592
+ url: new URL("index.js", packageDirectoryUrl).href,
5593
+ });
5594
+ };
5595
+ const mainLegacyResolvers = {
5596
+ import: ({ packageJson }) => {
5597
+ if (typeof packageJson.module === "string") {
5598
+ return { type: "field:module", isMain: true, path: packageJson.module };
5599
+ }
5600
+ if (typeof packageJson.jsnext === "string") {
5601
+ return { type: "field:jsnext", isMain: true, path: packageJson.jsnext };
5602
+ }
5603
+ if (typeof packageJson.main === "string") {
5604
+ return { type: "field:main", isMain: true, path: packageJson.main };
5605
+ }
5606
+ return null;
5607
+ },
5608
+ browser: ({ packageDirectoryUrl, packageJson }) => {
5609
+ const browserMain = (() => {
5610
+ if (typeof packageJson.browser === "string") {
5611
+ return packageJson.browser;
5612
+ }
5613
+ if (
5614
+ typeof packageJson.browser === "object" &&
5615
+ packageJson.browser !== null
5616
+ ) {
5617
+ return packageJson.browser["."];
5618
+ }
5619
+ return "";
5620
+ })();
5720
5621
 
5721
- const specifierUrl = resolveSpecifier(specifier, importer);
5722
- const specifierNormalized = specifierUrl || specifier;
5723
-
5724
- const { scopes } = importMap;
5725
- if (scopes && importer) {
5726
- const scopeSpecifierMatching = Object.keys(scopes).find(
5727
- (scopeSpecifier) => {
5728
- return (
5729
- scopeSpecifier === importer ||
5730
- specifierIsPrefixOf(scopeSpecifier, importer)
5731
- );
5732
- },
5733
- );
5734
- if (scopeSpecifierMatching) {
5735
- const scopeMappings = scopes[scopeSpecifierMatching];
5736
- const mappingFromScopes = applyMappings(
5737
- scopeMappings,
5738
- specifierNormalized,
5739
- scopeSpecifierMatching,
5740
- onImportMapping,
5741
- );
5742
- if (mappingFromScopes !== null) {
5743
- return mappingFromScopes;
5622
+ if (!browserMain) {
5623
+ if (typeof packageJson.module === "string") {
5624
+ return {
5625
+ type: "field:module",
5626
+ isMain: true,
5627
+ path: packageJson.module,
5628
+ };
5629
+ }
5630
+ return null;
5631
+ }
5632
+ if (
5633
+ typeof packageJson.module !== "string" ||
5634
+ packageJson.module === browserMain
5635
+ ) {
5636
+ return {
5637
+ type: "field:browser",
5638
+ isMain: true,
5639
+ path: browserMain,
5640
+ };
5641
+ }
5642
+ const browserMainUrlObject = new URL(browserMain, packageDirectoryUrl);
5643
+ const content = readFileSync(browserMainUrlObject, "utf-8");
5644
+ if (
5645
+ (/typeof exports\s*==/.test(content) &&
5646
+ /typeof module\s*==/.test(content)) ||
5647
+ /module\.exports\s*=/.test(content)
5648
+ ) {
5649
+ return {
5650
+ type: "field:module",
5651
+ isMain: true,
5652
+ path: packageJson.module,
5653
+ };
5654
+ }
5655
+ return {
5656
+ type: "field:browser",
5657
+ isMain: true,
5658
+ path: browserMain,
5659
+ };
5660
+ },
5661
+ node: ({ packageJson, conditions }) => {
5662
+ if (conditions.includes("import") && !conditions.includes("require")) {
5663
+ if (typeof packageJson.module === "string") {
5664
+ return { type: "field:module", isMain: true, path: packageJson.module };
5665
+ }
5666
+ if (typeof packageJson.jsnext === "string") {
5667
+ return { type: "field:jsnext", isMain: true, path: packageJson.jsnext };
5744
5668
  }
5745
5669
  }
5670
+ if (typeof packageJson.main === "string") {
5671
+ return { type: "field:main", isMain: true, path: packageJson.main };
5672
+ }
5673
+ return null;
5674
+ },
5675
+ };
5676
+ mainLegacyResolvers.require = mainLegacyResolvers.node;
5677
+
5678
+ const comparePatternKeys = (keyA, keyB) => {
5679
+ if (!keyA.endsWith("/") && !keyA.includes("*")) {
5680
+ throw new Error("Invalid package configuration");
5681
+ }
5682
+ if (!keyB.endsWith("/") && !keyB.includes("*")) {
5683
+ throw new Error("Invalid package configuration");
5684
+ }
5685
+ const aStarIndex = keyA.indexOf("*");
5686
+ const baseLengthA = aStarIndex > -1 ? aStarIndex + 1 : keyA.length;
5687
+ const bStarIndex = keyB.indexOf("*");
5688
+ const baseLengthB = bStarIndex > -1 ? bStarIndex + 1 : keyB.length;
5689
+ if (baseLengthA > baseLengthB) {
5690
+ return -1;
5746
5691
  }
5747
-
5748
- const { imports } = importMap;
5749
- if (imports) {
5750
- const mappingFromImports = applyMappings(
5751
- imports,
5752
- specifierNormalized,
5753
- undefined,
5754
- onImportMapping,
5755
- );
5756
- if (mappingFromImports !== null) {
5757
- return mappingFromImports;
5758
- }
5692
+ if (baseLengthB > baseLengthA) {
5693
+ return 1;
5759
5694
  }
5760
-
5761
- if (specifierUrl) {
5762
- return specifierUrl;
5695
+ if (aStarIndex === -1) {
5696
+ return 1;
5697
+ }
5698
+ if (bStarIndex === -1) {
5699
+ return -1;
5700
+ }
5701
+ if (keyA.length > keyB.length) {
5702
+ return -1;
5703
+ }
5704
+ if (keyB.length > keyA.length) {
5705
+ return 1;
5763
5706
  }
5707
+ return 0;
5708
+ };
5764
5709
 
5765
- throw createBareSpecifierError({ specifier, importer });
5710
+ const resolvePackageSymlink = (packageDirectoryUrl) => {
5711
+ const packageDirectoryPath = realpathSync(new URL(packageDirectoryUrl));
5712
+ const packageDirectoryResolvedUrl = pathToFileURL(packageDirectoryPath).href;
5713
+ return `${packageDirectoryResolvedUrl}/`;
5766
5714
  };
5767
5715
 
5768
- const applyMappings = (
5769
- mappings,
5770
- specifierNormalized,
5771
- scope,
5772
- onImportMapping,
5716
+ const applyFileSystemMagicResolution = (
5717
+ fileUrl,
5718
+ { fileStat, magicDirectoryIndex, magicExtensions },
5773
5719
  ) => {
5774
- const specifierCandidates = Object.keys(mappings);
5720
+ const result = {
5721
+ stat: null,
5722
+ url: fileUrl,
5723
+ magicExtension: "",
5724
+ magicDirectoryIndex: false,
5725
+ lastENOENTError: null,
5726
+ };
5775
5727
 
5776
- let i = 0;
5777
- while (i < specifierCandidates.length) {
5778
- const specifierCandidate = specifierCandidates[i];
5779
- i++;
5780
- if (specifierCandidate === specifierNormalized) {
5781
- const address = mappings[specifierCandidate];
5782
- onImportMapping({
5783
- scope,
5784
- from: specifierCandidate,
5785
- to: address,
5786
- before: specifierNormalized,
5787
- after: address,
5788
- });
5789
- return address;
5728
+ if (fileStat === undefined) {
5729
+ try {
5730
+ fileStat = readEntryStatSync(new URL(fileUrl));
5731
+ } catch (e) {
5732
+ if (e.code === "ENOENT") {
5733
+ result.lastENOENTError = e;
5734
+ fileStat = null;
5735
+ } else {
5736
+ throw e;
5737
+ }
5790
5738
  }
5791
- if (specifierIsPrefixOf(specifierCandidate, specifierNormalized)) {
5792
- const address = mappings[specifierCandidate];
5793
- const afterSpecifier = specifierNormalized.slice(
5794
- specifierCandidate.length,
5795
- );
5796
- const addressFinal = tryUrlResolution(afterSpecifier, address);
5797
- onImportMapping({
5798
- scope,
5799
- from: specifierCandidate,
5800
- to: address,
5801
- before: specifierNormalized,
5802
- after: addressFinal,
5739
+ }
5740
+
5741
+ if (fileStat && fileStat.isFile()) {
5742
+ result.stat = fileStat;
5743
+ result.url = fileUrl;
5744
+ return result;
5745
+ }
5746
+ if (fileStat && fileStat.isDirectory()) {
5747
+ if (magicDirectoryIndex) {
5748
+ const indexFileSuffix = fileUrl.endsWith("/") ? "index" : "/index";
5749
+ const indexFileUrl = `${fileUrl}${indexFileSuffix}`;
5750
+ const subResult = applyFileSystemMagicResolution(indexFileUrl, {
5751
+ magicDirectoryIndex: false,
5752
+ magicExtensions,
5803
5753
  });
5804
- return addressFinal;
5754
+ return {
5755
+ ...result,
5756
+ ...subResult,
5757
+ magicDirectoryIndex: true,
5758
+ };
5805
5759
  }
5760
+ result.stat = fileStat;
5761
+ result.url = fileUrl;
5762
+ return result;
5806
5763
  }
5807
5764
 
5808
- return null;
5765
+ if (magicExtensions && magicExtensions.length) {
5766
+ const parentUrl = new URL("./", fileUrl).href;
5767
+ const urlFilename = urlToFilename(fileUrl);
5768
+ for (const extensionToTry of magicExtensions) {
5769
+ const urlCandidate = `${parentUrl}${urlFilename}${extensionToTry}`;
5770
+ let stat;
5771
+ try {
5772
+ stat = readEntryStatSync(new URL(urlCandidate));
5773
+ } catch (e) {
5774
+ if (e.code === "ENOENT") {
5775
+ stat = null;
5776
+ } else {
5777
+ throw e;
5778
+ }
5779
+ }
5780
+ if (stat) {
5781
+ result.stat = stat;
5782
+ result.url = `${fileUrl}${extensionToTry}`;
5783
+ result.magicExtension = extensionToTry;
5784
+ return result;
5785
+ }
5786
+ }
5787
+ }
5788
+ // magic extension not found
5789
+ return result;
5809
5790
  };
5810
5791
 
5811
- const specifierIsPrefixOf = (specifierHref, href) => {
5812
- return (
5813
- specifierHref[specifierHref.length - 1] === "/" &&
5814
- href.startsWith(specifierHref)
5815
- );
5792
+ const getExtensionsToTry = (magicExtensions, importer) => {
5793
+ if (!magicExtensions) {
5794
+ return [];
5795
+ }
5796
+ const extensionsSet = new Set();
5797
+ magicExtensions.forEach((magicExtension) => {
5798
+ if (magicExtension === "inherit") {
5799
+ const importerExtension = urlToExtension(importer);
5800
+ extensionsSet.add(importerExtension);
5801
+ } else {
5802
+ extensionsSet.add(magicExtension);
5803
+ }
5804
+ });
5805
+ return Array.from(extensionsSet.values());
5816
5806
  };
5817
5807
 
5818
- // https://github.com/systemjs/systemjs/blob/89391f92dfeac33919b0223bbf834a1f4eea5750/src/common.js#L136
5819
-
5820
- const composeTwoImportMaps = (leftImportMap, rightImportMap) => {
5821
- assertImportMap(leftImportMap);
5822
- assertImportMap(rightImportMap);
5823
-
5824
- const importMap = {};
5825
-
5826
- const leftImports = leftImportMap.imports;
5827
- const rightImports = rightImportMap.imports;
5828
- const leftHasImports = Boolean(leftImports);
5829
- const rightHasImports = Boolean(rightImports);
5830
- if (leftHasImports && rightHasImports) {
5831
- importMap.imports = composeTwoMappings(leftImports, rightImports);
5832
- } else if (leftHasImports) {
5833
- importMap.imports = { ...leftImports };
5834
- } else if (rightHasImports) {
5835
- importMap.imports = { ...rightImports };
5808
+ const versionFromValue = (value) => {
5809
+ if (typeof value === "number") {
5810
+ return numberToVersion(value);
5836
5811
  }
5837
-
5838
- const leftScopes = leftImportMap.scopes;
5839
- const rightScopes = rightImportMap.scopes;
5840
- const leftHasScopes = Boolean(leftScopes);
5841
- const rightHasScopes = Boolean(rightScopes);
5842
- if (leftHasScopes && rightHasScopes) {
5843
- importMap.scopes = composeTwoScopes(
5844
- leftScopes,
5845
- rightScopes,
5846
- importMap.imports || {},
5847
- );
5848
- } else if (leftHasScopes) {
5849
- importMap.scopes = { ...leftScopes };
5850
- } else if (rightHasScopes) {
5851
- importMap.scopes = { ...rightScopes };
5812
+ if (typeof value === "string") {
5813
+ return stringToVersion(value);
5852
5814
  }
5815
+ throw new TypeError(`version must be a number or a string, got ${value}`);
5816
+ };
5853
5817
 
5854
- return importMap;
5818
+ const numberToVersion = (number) => {
5819
+ return {
5820
+ major: number,
5821
+ minor: 0,
5822
+ patch: 0,
5823
+ };
5855
5824
  };
5856
5825
 
5857
- const composeTwoMappings = (leftMappings, rightMappings) => {
5858
- const mappings = {};
5826
+ const stringToVersion = (string) => {
5827
+ if (string.indexOf(".") > -1) {
5828
+ const parts = string.split(".");
5829
+ return {
5830
+ major: Number(parts[0]),
5831
+ minor: parts[1] ? Number(parts[1]) : 0,
5832
+ patch: parts[2] ? Number(parts[2]) : 0,
5833
+ };
5834
+ }
5859
5835
 
5860
- Object.keys(leftMappings).forEach((leftSpecifier) => {
5861
- if (objectHasKey(rightMappings, leftSpecifier)) {
5862
- // will be overidden
5863
- return;
5864
- }
5865
- const leftAddress = leftMappings[leftSpecifier];
5866
- const rightSpecifier = Object.keys(rightMappings).find((rightSpecifier) => {
5867
- return compareAddressAndSpecifier(leftAddress, rightSpecifier);
5868
- });
5869
- mappings[leftSpecifier] = rightSpecifier
5870
- ? rightMappings[rightSpecifier]
5871
- : leftAddress;
5872
- });
5836
+ if (isNaN(string)) {
5837
+ return {
5838
+ major: 0,
5839
+ minor: 0,
5840
+ patch: 0,
5841
+ };
5842
+ }
5873
5843
 
5874
- Object.keys(rightMappings).forEach((rightSpecifier) => {
5875
- mappings[rightSpecifier] = rightMappings[rightSpecifier];
5876
- });
5844
+ return {
5845
+ major: Number(string),
5846
+ minor: 0,
5847
+ patch: 0,
5848
+ };
5849
+ };
5877
5850
 
5878
- return mappings;
5851
+ const compareTwoVersions = (versionA, versionB) => {
5852
+ const semanticVersionA = versionFromValue(versionA);
5853
+ const semanticVersionB = versionFromValue(versionB);
5854
+ const majorDiff = semanticVersionA.major - semanticVersionB.major;
5855
+ if (majorDiff > 0) {
5856
+ return majorDiff;
5857
+ }
5858
+ if (majorDiff < 0) {
5859
+ return majorDiff;
5860
+ }
5861
+ const minorDiff = semanticVersionA.minor - semanticVersionB.minor;
5862
+ if (minorDiff > 0) {
5863
+ return minorDiff;
5864
+ }
5865
+ if (minorDiff < 0) {
5866
+ return minorDiff;
5867
+ }
5868
+ const patchDiff = semanticVersionA.patch - semanticVersionB.patch;
5869
+ if (patchDiff > 0) {
5870
+ return patchDiff;
5871
+ }
5872
+ if (patchDiff < 0) {
5873
+ return patchDiff;
5874
+ }
5875
+ return 0;
5879
5876
  };
5880
5877
 
5881
- const objectHasKey = (object, key) =>
5882
- Object.prototype.hasOwnProperty.call(object, key);
5883
-
5884
- const compareAddressAndSpecifier = (address, specifier) => {
5885
- const addressUrl = resolveUrl(address, "file:///");
5886
- const specifierUrl = resolveUrl(specifier, "file:///");
5887
- return addressUrl === specifierUrl;
5878
+ const versionIsBelow = (versionSupposedBelow, versionSupposedAbove) => {
5879
+ return compareTwoVersions(versionSupposedBelow, versionSupposedAbove) < 0;
5888
5880
  };
5889
5881
 
5890
- const composeTwoScopes = (leftScopes, rightScopes, imports) => {
5891
- const scopes = {};
5892
-
5893
- Object.keys(leftScopes).forEach((leftScopeKey) => {
5894
- if (objectHasKey(rightScopes, leftScopeKey)) {
5895
- // will be merged
5896
- scopes[leftScopeKey] = leftScopes[leftScopeKey];
5897
- return;
5898
- }
5899
- const topLevelSpecifier = Object.keys(imports).find(
5900
- (topLevelSpecifierCandidate) => {
5901
- return compareAddressAndSpecifier(
5902
- leftScopeKey,
5903
- topLevelSpecifierCandidate,
5904
- );
5905
- },
5906
- );
5907
- if (topLevelSpecifier) {
5908
- scopes[imports[topLevelSpecifier]] = leftScopes[leftScopeKey];
5909
- } else {
5910
- scopes[leftScopeKey] = leftScopes[leftScopeKey];
5911
- }
5912
- });
5913
-
5914
- Object.keys(rightScopes).forEach((rightScopeKey) => {
5915
- if (objectHasKey(scopes, rightScopeKey)) {
5916
- scopes[rightScopeKey] = composeTwoMappings(
5917
- scopes[rightScopeKey],
5918
- rightScopes[rightScopeKey],
5919
- );
5920
- } else {
5921
- scopes[rightScopeKey] = {
5922
- ...rightScopes[rightScopeKey],
5923
- };
5882
+ const findHighestVersion = (...values) => {
5883
+ if (values.length === 0) throw new Error(`missing argument`);
5884
+ return values.reduce((highestVersion, value) => {
5885
+ if (versionIsBelow(highestVersion, value)) {
5886
+ return value;
5924
5887
  }
5888
+ return highestVersion;
5925
5889
  });
5926
-
5927
- return scopes;
5928
5890
  };
5929
5891
 
5930
- const sortImports = (imports) => {
5931
- const mappingsSorted = {};
5932
-
5933
- Object.keys(imports)
5934
- .sort(compareLengthOrLocaleCompare)
5935
- .forEach((name) => {
5936
- mappingsSorted[name] = imports[name];
5937
- });
5938
-
5939
- return mappingsSorted;
5892
+ const featuresCompatMap = {
5893
+ script_type_module: {
5894
+ edge: "16",
5895
+ firefox: "60",
5896
+ chrome: "61",
5897
+ safari: "10.1",
5898
+ opera: "48",
5899
+ ios: "10.3",
5900
+ android: "61",
5901
+ samsung: "8.2",
5902
+ },
5903
+ document_current_script: {
5904
+ edge: "12",
5905
+ firefox: "4",
5906
+ chrome: "29",
5907
+ safari: "8",
5908
+ opera: "16",
5909
+ android: "4.4",
5910
+ samsung: "4",
5911
+ },
5912
+ // https://caniuse.com/?search=import.meta
5913
+ import_meta: {
5914
+ android: "9",
5915
+ chrome: "64",
5916
+ edge: "79",
5917
+ firefox: "62",
5918
+ ios: "12",
5919
+ opera: "51",
5920
+ safari: "11.1",
5921
+ samsung: "9.2",
5922
+ },
5923
+ import_meta_resolve: {
5924
+ chrome: "107",
5925
+ edge: "105",
5926
+ firefox: "106",
5927
+ node: "20.0.0",
5928
+ },
5929
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#browser_compatibility
5930
+ import_dynamic: {
5931
+ android: "8",
5932
+ chrome: "63",
5933
+ edge: "79",
5934
+ firefox: "67",
5935
+ ios: "11.3",
5936
+ opera: "50",
5937
+ safari: "11.3",
5938
+ samsung: "8.0",
5939
+ node: "13.2",
5940
+ },
5941
+ top_level_await: {
5942
+ edge: "89",
5943
+ chrome: "89",
5944
+ firefox: "89",
5945
+ opera: "75",
5946
+ safari: "15",
5947
+ samsung: "15",
5948
+ ios: "15",
5949
+ node: "14.8",
5950
+ },
5951
+ // https://caniuse.com/import-maps
5952
+ importmap: {
5953
+ edge: "89",
5954
+ chrome: "89",
5955
+ opera: "76",
5956
+ samsung: "15",
5957
+ firefox: "108",
5958
+ safari: "16.4",
5959
+ },
5960
+ import_type_json: {
5961
+ chrome: "123",
5962
+ safari: "17.2",
5963
+ },
5964
+ import_type_css: {
5965
+ chrome: "123",
5966
+ },
5967
+ import_type_text: {},
5968
+ // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet#browser_compatibility
5969
+ new_stylesheet: {
5970
+ chrome: "73",
5971
+ edge: "79",
5972
+ opera: "53",
5973
+ android: "73",
5974
+ },
5975
+ // https://caniuse.com/?search=worker
5976
+ worker: {
5977
+ ie: "10",
5978
+ edge: "12",
5979
+ firefox: "3.5",
5980
+ chrome: "4",
5981
+ opera: "11.5",
5982
+ safari: "4",
5983
+ ios: "5",
5984
+ android: "4.4",
5985
+ },
5986
+ // https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#browser_compatibility
5987
+ worker_type_module: {
5988
+ chrome: "80",
5989
+ edge: "80",
5990
+ opera: "67",
5991
+ android: "80",
5992
+ },
5993
+ worker_importmap: {},
5994
+ service_worker: {
5995
+ edge: "17",
5996
+ firefox: "44",
5997
+ chrome: "40",
5998
+ safari: "11.1",
5999
+ opera: "27",
6000
+ ios: "11.3",
6001
+ android: "12.12",
6002
+ },
6003
+ service_worker_type_module: {
6004
+ chrome: "80",
6005
+ edge: "80",
6006
+ opera: "67",
6007
+ android: "80",
6008
+ },
6009
+ service_worker_importmap: {},
6010
+ shared_worker: {
6011
+ chrome: "4",
6012
+ edge: "79",
6013
+ firefox: "29",
6014
+ opera: "10.6",
6015
+ },
6016
+ shared_worker_type_module: {
6017
+ chrome: "80",
6018
+ edge: "80",
6019
+ opera: "67",
6020
+ },
6021
+ shared_worker_importmap: {},
6022
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis#browser_compatibility
6023
+ global_this: {
6024
+ edge: "79",
6025
+ firefox: "65",
6026
+ chrome: "71",
6027
+ safari: "12.1",
6028
+ opera: "58",
6029
+ ios: "12.2",
6030
+ android: "94",
6031
+ node: "12",
6032
+ },
6033
+ async_generator_function: {
6034
+ chrome: "63",
6035
+ opera: "50",
6036
+ edge: "79",
6037
+ firefox: "57",
6038
+ safari: "12",
6039
+ node: "10",
6040
+ ios: "12",
6041
+ samsung: "8",
6042
+ electron: "3",
6043
+ },
6044
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#browser_compatibility
6045
+ template_literals: {
6046
+ chrome: "41",
6047
+ edge: "12",
6048
+ firefox: "34",
6049
+ opera: "28",
6050
+ safari: "9",
6051
+ ios: "9",
6052
+ android: "4",
6053
+ node: "4",
6054
+ },
6055
+ arrow_function: {
6056
+ chrome: "47",
6057
+ opera: "34",
6058
+ edge: "13",
6059
+ firefox: "45",
6060
+ safari: "10",
6061
+ node: "6",
6062
+ ios: "10",
6063
+ samsung: "5",
6064
+ electron: "0.36",
6065
+ },
6066
+ const_bindings: {
6067
+ chrome: "41",
6068
+ opera: "28",
6069
+ edge: "12",
6070
+ firefox: "46",
6071
+ safari: "10",
6072
+ node: "4",
6073
+ ie: "11",
6074
+ ios: "10",
6075
+ samsung: "3.4",
6076
+ electron: "0.22",
6077
+ },
6078
+ object_properties_shorthand: {
6079
+ chrome: "43",
6080
+ opera: "30",
6081
+ edge: "12",
6082
+ firefox: "33",
6083
+ safari: "9",
6084
+ node: "4",
6085
+ ios: "9",
6086
+ samsung: "4",
6087
+ electron: "0.28",
6088
+ },
6089
+ reserved_words: {
6090
+ chrome: "13",
6091
+ opera: "10.50",
6092
+ edge: "12",
6093
+ firefox: "2",
6094
+ safari: "3.1",
6095
+ node: "0.10",
6096
+ ie: "9",
6097
+ android: "4.4",
6098
+ ios: "6",
6099
+ phantom: "2",
6100
+ samsung: "1",
6101
+ electron: "0.20",
6102
+ },
6103
+ symbols: {
6104
+ chrome: "38",
6105
+ opera: "25",
6106
+ edge: "12",
6107
+ firefox: "36",
6108
+ safari: "9",
6109
+ ios: "9",
6110
+ samsung: "4",
6111
+ node: "0.12",
6112
+ },
5940
6113
  };
5941
6114
 
5942
- const sortScopes = (scopes) => {
5943
- const scopesSorted = {};
6115
+ const RUNTIME_COMPAT = {
6116
+ featuresCompatMap,
5944
6117
 
5945
- Object.keys(scopes)
5946
- .sort(compareLengthOrLocaleCompare)
5947
- .forEach((scopeSpecifier) => {
5948
- scopesSorted[scopeSpecifier] = sortImports(scopes[scopeSpecifier]);
6118
+ add: (originalRuntimeCompat, feature) => {
6119
+ const featureCompat = getFeatureCompat(feature);
6120
+ const runtimeCompat = {
6121
+ ...originalRuntimeCompat,
6122
+ };
6123
+ Object.keys(originalRuntimeCompat).forEach((runtimeName) => {
6124
+ const secondVersion = featureCompat[runtimeName]; // the version supported by the feature
6125
+ if (secondVersion) {
6126
+ const firstVersion = originalRuntimeCompat[runtimeName];
6127
+ runtimeCompat[runtimeName] = findHighestVersion(
6128
+ firstVersion,
6129
+ secondVersion,
6130
+ );
6131
+ }
5949
6132
  });
6133
+ return runtimeCompat;
6134
+ },
5950
6135
 
5951
- return scopesSorted;
5952
- };
5953
-
5954
- const compareLengthOrLocaleCompare = (a, b) => {
5955
- return b.length - a.length || a.localeCompare(b);
5956
- };
5957
-
5958
- const normalizeImportMap = (importMap, baseUrl) => {
5959
- assertImportMap(importMap);
5960
-
5961
- if (!isStringOrUrl(baseUrl)) {
5962
- throw new TypeError(formulateBaseUrlMustBeStringOrUrl({ baseUrl }));
5963
- }
5964
-
5965
- const { imports, scopes } = importMap;
5966
-
5967
- return {
5968
- imports: imports ? normalizeMappings(imports, baseUrl) : undefined,
5969
- scopes: scopes ? normalizeScopes(scopes, baseUrl) : undefined,
5970
- };
5971
- };
5972
-
5973
- const isStringOrUrl = (value) => {
5974
- if (typeof value === "string") {
5975
- return true;
5976
- }
5977
-
5978
- if (typeof URL === "function" && value instanceof URL) {
5979
- return true;
5980
- }
5981
-
5982
- return false;
5983
- };
5984
-
5985
- const normalizeMappings = (mappings, baseUrl) => {
5986
- const mappingsNormalized = {};
5987
-
5988
- Object.keys(mappings).forEach((specifier) => {
5989
- const address = mappings[specifier];
5990
-
5991
- if (typeof address !== "string") {
5992
- console.warn(
5993
- formulateAddressMustBeAString({
5994
- address,
5995
- specifier,
5996
- }),
5997
- );
5998
- return;
5999
- }
6000
-
6001
- const specifierResolved = resolveSpecifier(specifier, baseUrl) || specifier;
6002
-
6003
- const addressUrl = tryUrlResolution(address, baseUrl);
6004
- if (addressUrl === null) {
6005
- console.warn(
6006
- formulateAdressResolutionFailed({
6007
- address,
6008
- baseUrl,
6009
- specifier,
6010
- }),
6011
- );
6012
- return;
6013
- }
6014
-
6015
- if (specifier.endsWith("/") && !addressUrl.endsWith("/")) {
6016
- console.warn(
6017
- formulateAddressUrlRequiresTrailingSlash({
6018
- address,
6019
- specifier,
6020
- }),
6021
- );
6022
- return;
6023
- }
6024
- mappingsNormalized[specifierResolved] = addressUrl;
6025
- });
6026
-
6027
- return sortImports(mappingsNormalized);
6028
- };
6029
-
6030
- const normalizeScopes = (scopes, baseUrl) => {
6031
- const scopesNormalized = {};
6032
-
6033
- Object.keys(scopes).forEach((scopeSpecifier) => {
6034
- const scopeMappings = scopes[scopeSpecifier];
6035
- const scopeUrl = tryUrlResolution(scopeSpecifier, baseUrl);
6036
- if (scopeUrl === null) {
6037
- console.warn(
6038
- formulateScopeResolutionFailed({
6039
- scope: scopeSpecifier,
6040
- baseUrl,
6041
- }),
6136
+ isSupported: (
6137
+ runtimeCompat,
6138
+ feature,
6139
+ featureCompat = getFeatureCompat(feature),
6140
+ ) => {
6141
+ const runtimeNames = Object.keys(runtimeCompat);
6142
+ const runtimeWithoutCompat = runtimeNames.find((runtimeName) => {
6143
+ const runtimeVersion = runtimeCompat[runtimeName];
6144
+ const runtimeVersionCompatible = featureCompat[runtimeName] || "Infinity";
6145
+ const highestVersion = findHighestVersion(
6146
+ runtimeVersion,
6147
+ runtimeVersionCompatible,
6042
6148
  );
6043
- return;
6044
- }
6045
- const scopeValueNormalized = normalizeMappings(scopeMappings, baseUrl);
6046
- scopesNormalized[scopeUrl] = scopeValueNormalized;
6047
- });
6048
-
6049
- return sortScopes(scopesNormalized);
6050
- };
6051
-
6052
- const formulateBaseUrlMustBeStringOrUrl = ({
6053
- baseUrl,
6054
- }) => `baseUrl must be a string or an url.
6055
- --- base url ---
6056
- ${baseUrl}`;
6057
-
6058
- const formulateAddressMustBeAString = ({
6059
- specifier,
6060
- address,
6061
- }) => `Address must be a string.
6062
- --- address ---
6063
- ${address}
6064
- --- specifier ---
6065
- ${specifier}`;
6066
-
6067
- const formulateAdressResolutionFailed = ({
6068
- address,
6069
- baseUrl,
6070
- specifier,
6071
- }) => `Address url resolution failed.
6072
- --- address ---
6073
- ${address}
6074
- --- base url ---
6075
- ${baseUrl}
6076
- --- specifier ---
6077
- ${specifier}`;
6078
-
6079
- const formulateAddressUrlRequiresTrailingSlash = ({
6080
- addressURL,
6081
- address,
6082
- specifier,
6083
- }) => `Address must end with /.
6084
- --- address url ---
6085
- ${addressURL}
6086
- --- address ---
6087
- ${address}
6088
- --- specifier ---
6089
- ${specifier}`;
6090
-
6091
- const formulateScopeResolutionFailed = ({
6092
- scope,
6093
- baseUrl,
6094
- }) => `Scope url resolution failed.
6095
- --- scope ---
6096
- ${scope}
6097
- --- base url ---
6098
- ${baseUrl}`;
6099
-
6100
- const pathnameToExtension = (pathname) => {
6101
- const slashLastIndex = pathname.lastIndexOf("/");
6102
- if (slashLastIndex !== -1) {
6103
- pathname = pathname.slice(slashLastIndex + 1);
6104
- }
6105
-
6106
- const dotLastIndex = pathname.lastIndexOf(".");
6107
- if (dotLastIndex === -1) return "";
6108
- // if (dotLastIndex === pathname.length - 1) return ""
6109
- return pathname.slice(dotLastIndex);
6110
- };
6111
-
6112
- const resolveImport = ({
6113
- specifier,
6114
- importer,
6115
- importMap,
6116
- defaultExtension = false,
6117
- createBareSpecifierError,
6118
- onImportMapping = () => {},
6119
- }) => {
6120
- let url;
6121
- if (importMap) {
6122
- url = applyImportMap({
6123
- importMap,
6124
- specifier,
6125
- importer,
6126
- createBareSpecifierError,
6127
- onImportMapping,
6149
+ return highestVersion !== runtimeVersion;
6128
6150
  });
6129
- } else {
6130
- url = resolveUrl(specifier, importer);
6131
- }
6151
+ return !runtimeWithoutCompat;
6152
+ },
6153
+ };
6132
6154
 
6133
- if (defaultExtension) {
6134
- url = applyDefaultExtension({ url, importer, defaultExtension });
6155
+ const getFeatureCompat = (feature) => {
6156
+ if (typeof feature === "string") {
6157
+ const compat = featuresCompatMap[feature];
6158
+ if (!compat) {
6159
+ throw new Error(`"${feature}" feature is unknown`);
6160
+ }
6161
+ return compat;
6162
+ }
6163
+ if (typeof feature !== "object") {
6164
+ throw new TypeError(
6165
+ `feature must be a string or an object, got ${feature}`,
6166
+ );
6135
6167
  }
6168
+ return feature;
6169
+ };
6136
6170
 
6137
- return url;
6171
+ const isSupportedAlgorithm = (algo) => {
6172
+ return SUPPORTED_ALGORITHMS.includes(algo);
6138
6173
  };
6139
6174
 
6140
- const applyDefaultExtension = ({ url, importer, defaultExtension }) => {
6141
- if (urlToPathname(url).endsWith("/")) {
6142
- return url;
6175
+ // https://www.w3.org/TR/SRI/#priority
6176
+ const getPrioritizedHashFunction = (firstAlgo, secondAlgo) => {
6177
+ const firstIndex = SUPPORTED_ALGORITHMS.indexOf(firstAlgo);
6178
+ const secondIndex = SUPPORTED_ALGORITHMS.indexOf(secondAlgo);
6179
+ if (firstIndex === secondIndex) {
6180
+ return "";
6143
6181
  }
6144
-
6145
- if (typeof defaultExtension === "string") {
6146
- const extension = pathnameToExtension(url);
6147
- if (extension === "") {
6148
- return `${url}${defaultExtension}`;
6149
- }
6150
- return url;
6182
+ if (firstIndex < secondIndex) {
6183
+ return secondAlgo;
6151
6184
  }
6185
+ return firstAlgo;
6186
+ };
6152
6187
 
6153
- if (defaultExtension === true) {
6154
- const extension = pathnameToExtension(url);
6155
- if (extension === "" && importer) {
6156
- const importerPathname = urlToPathname(importer);
6157
- const importerExtension = pathnameToExtension(importerPathname);
6158
- return `${url}${importerExtension}`;
6159
- }
6160
- }
6188
+ const applyAlgoToRepresentationData = (algo, data) => {
6189
+ const base64Value = crypto.createHash(algo).update(data).digest("base64");
6190
+ return base64Value;
6191
+ };
6161
6192
 
6162
- return url;
6193
+ // keep this ordered by collision resistance as it is also used by "getPrioritizedHashFunction"
6194
+ const SUPPORTED_ALGORITHMS = ["sha256", "sha384", "sha512"];
6195
+
6196
+ // see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
6197
+ const parseIntegrity = (string) => {
6198
+ const integrityMetadata = {};
6199
+ string
6200
+ .trim()
6201
+ .split(/\s+/)
6202
+ .forEach((token) => {
6203
+ const { isValid, algo, base64Value, optionExpression } =
6204
+ parseAsHashWithOptions(token);
6205
+ if (!isValid) {
6206
+ return;
6207
+ }
6208
+ if (!isSupportedAlgorithm(algo)) {
6209
+ return;
6210
+ }
6211
+ const metadataList = integrityMetadata[algo];
6212
+ const metadata = { base64Value, optionExpression };
6213
+ integrityMetadata[algo] = metadataList
6214
+ ? [...metadataList, metadata]
6215
+ : [metadata];
6216
+ });
6217
+ return integrityMetadata;
6163
6218
  };
6164
6219
 
6165
- const isEscaped = (i, string) => {
6166
- let backslashBeforeCount = 0;
6167
- while (i--) {
6168
- const previousChar = string[i];
6169
- if (previousChar === "\\") {
6170
- backslashBeforeCount++;
6171
- }
6172
- break;
6220
+ // see https://w3c.github.io/webappsec-subresource-integrity/#the-integrity-attribute
6221
+ const parseAsHashWithOptions = (token) => {
6222
+ const dashIndex = token.indexOf("-");
6223
+ if (dashIndex === -1) {
6224
+ return { isValid: false };
6173
6225
  }
6174
- const isEven = backslashBeforeCount % 2 === 0;
6175
- return !isEven;
6226
+ const beforeDash = token.slice(0, dashIndex);
6227
+ const afterDash = token.slice(dashIndex + 1);
6228
+ const questionIndex = afterDash.indexOf("?");
6229
+ const algo = beforeDash;
6230
+ if (questionIndex === -1) {
6231
+ const base64Value = afterDash;
6232
+ const isValid = BASE64_REGEX.test(afterDash);
6233
+ return { isValid, algo, base64Value };
6234
+ }
6235
+ const base64Value = afterDash.slice(0, questionIndex);
6236
+ const optionExpression = afterDash.slice(questionIndex + 1);
6237
+ const isValid =
6238
+ BASE64_REGEX.test(afterDash) && VCHAR_REGEX.test(optionExpression);
6239
+ return { isValid, algo, base64Value, optionExpression };
6176
6240
  };
6177
6241
 
6178
- const JS_QUOTES = {
6179
- pickBest: (string, { canUseTemplateString, defaultQuote = DOUBLE } = {}) => {
6180
- // check default first, once tested do no re-test it
6181
- if (!string.includes(defaultQuote)) {
6182
- return defaultQuote;
6183
- }
6184
- if (defaultQuote !== DOUBLE && !string.includes(DOUBLE)) {
6185
- return DOUBLE;
6186
- }
6187
- if (defaultQuote !== SINGLE && !string.includes(SINGLE)) {
6188
- return SINGLE;
6189
- }
6190
- if (
6191
- canUseTemplateString &&
6192
- defaultQuote !== BACKTICK &&
6193
- !string.includes(BACKTICK)
6194
- ) {
6195
- return BACKTICK;
6196
- }
6197
- return defaultQuote;
6198
- },
6242
+ const BASE64_REGEX = /^[A-Za-z0-9+/=]+$/;
6243
+ const VCHAR_REGEX = /^[\x21-\x7E]+$/;
6199
6244
 
6200
- escapeSpecialChars: (
6201
- string,
6202
- {
6203
- quote = "pickBest",
6204
- canUseTemplateString,
6205
- defaultQuote,
6206
- allowEscapeForVersioning = false,
6207
- },
6208
- ) => {
6209
- quote =
6210
- quote === "pickBest"
6211
- ? JS_QUOTES.pickBest(string, { canUseTemplateString, defaultQuote })
6212
- : quote;
6213
- const replacements = JS_QUOTE_REPLACEMENTS[quote];
6214
- let result = "";
6215
- let last = 0;
6216
- let i = 0;
6217
- while (i < string.length) {
6218
- const char = string[i];
6219
- i++;
6220
- if (isEscaped(i - 1, string)) continue;
6221
- const replacement = replacements[char];
6222
- if (replacement) {
6223
- if (
6224
- allowEscapeForVersioning &&
6225
- char === quote &&
6226
- string.slice(i, i + 6) === "+__v__"
6227
- ) {
6228
- let isVersioningConcatenation = false;
6229
- let j = i + 6; // start after the +
6230
- while (j < string.length) {
6231
- const lookAheadChar = string[j];
6232
- j++;
6233
- if (
6234
- lookAheadChar === "+" &&
6235
- string[j] === quote &&
6236
- !isEscaped(j - 1, string)
6237
- ) {
6238
- isVersioningConcatenation = true;
6239
- break;
6240
- }
6241
- }
6242
- if (isVersioningConcatenation) {
6243
- // it's a concatenation
6244
- // skip until the end of concatenation (the second +)
6245
- // and resume from there
6246
- i = j + 1;
6247
- continue;
6248
- }
6249
- }
6250
- if (last === i - 1) {
6251
- result += replacement;
6252
- } else {
6253
- result += `${string.slice(last, i - 1)}${replacement}`;
6254
- }
6255
- last = i;
6256
- }
6257
- }
6258
- if (last !== string.length) {
6259
- result += string.slice(last);
6260
- }
6261
- return `${quote}${result}${quote}`;
6262
- },
6245
+ // https://www.w3.org/TR/SRI/#does-response-match-metadatalist
6246
+ const validateResponseIntegrity = (
6247
+ { url, type, dataRepresentation },
6248
+ integrity,
6249
+ ) => {
6250
+ if (!isResponseEligibleForIntegrityValidation({ type })) {
6251
+ return false;
6252
+ }
6253
+ const integrityMetadata = parseIntegrity(integrity);
6254
+ const algos = Object.keys(integrityMetadata);
6255
+ if (algos.length === 0) {
6256
+ return true;
6257
+ }
6258
+ let strongestAlgo = algos[0];
6259
+ algos.slice(1).forEach((algoCandidate) => {
6260
+ strongestAlgo =
6261
+ getPrioritizedHashFunction(strongestAlgo, algoCandidate) || strongestAlgo;
6262
+ });
6263
+ const metadataList = integrityMetadata[strongestAlgo];
6264
+ const actualBase64Value = applyAlgoToRepresentationData(
6265
+ strongestAlgo,
6266
+ dataRepresentation,
6267
+ );
6268
+ const acceptedBase64Values = metadataList.map(
6269
+ (metadata) => metadata.base64Value,
6270
+ );
6271
+ const someIsMatching = acceptedBase64Values.includes(actualBase64Value);
6272
+ if (someIsMatching) {
6273
+ return true;
6274
+ }
6275
+ const error = new Error(
6276
+ `Integrity validation failed for resource "${url}". The integrity found for this resource is "${strongestAlgo}-${actualBase64Value}"`,
6277
+ );
6278
+ error.code = "EINTEGRITY";
6279
+ error.algorithm = strongestAlgo;
6280
+ error.found = actualBase64Value;
6281
+ throw error;
6263
6282
  };
6264
6283
 
6265
- const DOUBLE = `"`;
6266
- const SINGLE = `'`;
6267
- const BACKTICK = "`";
6268
- const lineEndingEscapes = {
6269
- "\n": "\\n",
6270
- "\r": "\\r",
6271
- "\u2028": "\\u2028",
6272
- "\u2029": "\\u2029",
6273
- };
6274
- const JS_QUOTE_REPLACEMENTS = {
6275
- [DOUBLE]: {
6276
- '"': '\\"',
6277
- ...lineEndingEscapes,
6278
- },
6279
- [SINGLE]: {
6280
- "'": "\\'",
6281
- ...lineEndingEscapes,
6282
- },
6283
- [BACKTICK]: {
6284
- "`": "\\`",
6285
- "$": "\\$",
6286
- },
6284
+ // https://www.w3.org/TR/SRI/#is-response-eligible-for-integrity-validation
6285
+ const isResponseEligibleForIntegrityValidation = (response) => {
6286
+ return ["basic", "cors", "default"].includes(response.type);
6287
6287
  };
6288
6288
 
6289
6289
  const memoizeByFirstArgument = (compute) => {