@jsenv/core 39.7.7 → 39.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ import os, { networkInterfaces } from "node:os";
3
3
  import tty from "node:tty";
4
4
  import stringWidth from "string-width";
5
5
  import { pathToFileURL, fileURLToPath } from "node:url";
6
- import { readdir, chmod, stat, lstat, chmodSync, statSync, lstatSync, promises, readFileSync, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, unlinkSync, readdirSync, rmdirSync, watch, createReadStream, readFile, existsSync, realpathSync } from "node:fs";
6
+ import { readdir, chmod, stat, lstat, chmodSync, statSync, lstatSync, promises, unlinkSync, openSync, closeSync, readdirSync, rmdirSync, mkdirSync, readFileSync, writeFileSync as writeFileSync$1, unlink, rmdir, watch, createReadStream, readFile, existsSync, realpathSync } from "node:fs";
7
7
  import { extname } from "node:path";
8
8
  import crypto, { createHash } from "node:crypto";
9
9
  import cluster from "node:cluster";
@@ -2058,6 +2058,42 @@ const extractDriveLetter = (resource) => {
2058
2058
  return null;
2059
2059
  };
2060
2060
 
2061
+ const getParentDirectoryUrl = (url) => {
2062
+ if (url.startsWith("file://")) {
2063
+ // With node.js new URL('../', 'file:///C:/').href
2064
+ // returns "file:///C:/" instead of "file:///"
2065
+ const resource = url.slice("file://".length);
2066
+ const slashLastIndex = resource.lastIndexOf("/");
2067
+ if (slashLastIndex === -1) {
2068
+ return url;
2069
+ }
2070
+ const lastCharIndex = resource.length - 1;
2071
+ if (slashLastIndex === lastCharIndex) {
2072
+ const slashBeforeLastIndex = resource.lastIndexOf(
2073
+ "/",
2074
+ slashLastIndex - 1,
2075
+ );
2076
+ if (slashBeforeLastIndex === -1) {
2077
+ return url;
2078
+ }
2079
+ return `file://${resource.slice(0, slashBeforeLastIndex + 1)}`;
2080
+ }
2081
+ return `file://${resource.slice(0, slashLastIndex + 1)}`;
2082
+ }
2083
+ return new URL(url.endsWith("/") ? "../" : "./", url).href;
2084
+ };
2085
+
2086
+ const findAncestorDirectoryUrl = (url, callback) => {
2087
+ url = String(url);
2088
+ while (url !== "file:///") {
2089
+ if (callback(url)) {
2090
+ return url;
2091
+ }
2092
+ url = getParentDirectoryUrl(url);
2093
+ }
2094
+ return null;
2095
+ };
2096
+
2061
2097
  const createCallbackListNotifiedOnce = () => {
2062
2098
  let callbacks = [];
2063
2099
  let status = "waiting";
@@ -3610,30 +3646,9 @@ const normalizeMediaType = (value) => {
3610
3646
  return value;
3611
3647
  };
3612
3648
 
3613
- const writeFileSync = (destination, content = "") => {
3614
- const destinationUrl = assertAndNormalizeFileUrl(destination);
3615
- const destinationUrlObject = new URL(destinationUrl);
3616
- if (content && content instanceof URL) {
3617
- content = readFileSync(content);
3618
- }
3619
- try {
3620
- writeFileSync$1(destinationUrlObject, content);
3621
- } catch (error) {
3622
- if (error.code === "ENOENT") {
3623
- mkdirSync(new URL("./", destinationUrlObject), {
3624
- recursive: true,
3625
- });
3626
- writeFileSync$1(destinationUrlObject, content);
3627
- return;
3628
- }
3629
- throw error;
3630
- }
3631
- };
3632
-
3633
- const removeEntry = async (
3649
+ const removeEntrySync = (
3634
3650
  source,
3635
3651
  {
3636
- signal = new AbortController().signal,
3637
3652
  allowUseless = false,
3638
3653
  recursive = false,
3639
3654
  maxRetries = 3,
@@ -3642,110 +3657,74 @@ const removeEntry = async (
3642
3657
  } = {},
3643
3658
  ) => {
3644
3659
  const sourceUrl = assertAndNormalizeFileUrl(source);
3645
-
3646
- const removeOperation = Abort.startOperation();
3647
- removeOperation.addAbortSignal(signal);
3648
-
3649
- try {
3650
- removeOperation.throwIfAborted();
3651
- const sourceStats = await readEntryStat(sourceUrl, {
3652
- nullIfNotFound: true,
3653
- followLink: false,
3654
- });
3655
- if (!sourceStats) {
3656
- if (allowUseless) {
3657
- return;
3658
- }
3659
- throw new Error(`nothing to remove at ${urlToFileSystemPath(sourceUrl)}`);
3660
+ const sourceStats = readEntryStatSync(sourceUrl, {
3661
+ nullIfNotFound: true,
3662
+ followLink: false,
3663
+ });
3664
+ if (!sourceStats) {
3665
+ if (allowUseless) {
3666
+ return;
3660
3667
  }
3668
+ throw new Error(`nothing to remove at ${urlToFileSystemPath(sourceUrl)}`);
3669
+ }
3661
3670
 
3662
- // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_class_fs_stats
3663
- // FIFO and socket are ignored, not sure what they are exactly and what to do with them
3664
- // other libraries ignore them, let's do the same.
3665
- if (
3666
- sourceStats.isFile() ||
3667
- sourceStats.isSymbolicLink() ||
3668
- sourceStats.isCharacterDevice() ||
3669
- sourceStats.isBlockDevice()
3670
- ) {
3671
- await removeNonDirectory$1(
3672
- sourceUrl.endsWith("/") ? sourceUrl.slice(0, -1) : sourceUrl,
3673
- {
3674
- maxRetries,
3675
- retryDelay,
3676
- },
3677
- );
3678
- } else if (sourceStats.isDirectory()) {
3679
- await removeDirectory(ensurePathnameTrailingSlash(sourceUrl), {
3680
- signal: removeOperation.signal,
3681
- recursive,
3682
- maxRetries,
3683
- retryDelay,
3684
- onlyContent,
3685
- });
3686
- }
3687
- } finally {
3688
- await removeOperation.end();
3671
+ // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_class_fs_stats
3672
+ // FIFO and socket are ignored, not sure what they are exactly and what to do with them
3673
+ // other libraries ignore them, let's do the same.
3674
+ if (
3675
+ sourceStats.isFile() ||
3676
+ sourceStats.isSymbolicLink() ||
3677
+ sourceStats.isCharacterDevice() ||
3678
+ sourceStats.isBlockDevice()
3679
+ ) {
3680
+ removeNonDirectory$1(
3681
+ sourceUrl.endsWith("/") ? sourceUrl.slice(0, -1) : sourceUrl);
3682
+ } else if (sourceStats.isDirectory()) {
3683
+ const directoryUrl = ensurePathnameTrailingSlash(sourceUrl);
3684
+ removeDirectorySync$1(directoryUrl, {
3685
+ recursive,
3686
+ maxRetries,
3687
+ retryDelay,
3688
+ onlyContent,
3689
+ });
3689
3690
  }
3690
3691
  };
3691
3692
 
3692
- const removeNonDirectory$1 = (sourceUrl, { maxRetries, retryDelay }) => {
3693
+ const removeNonDirectory$1 = (sourceUrl) => {
3693
3694
  const sourcePath = urlToFileSystemPath(sourceUrl);
3694
-
3695
- let retryCount = 0;
3696
3695
  const attempt = () => {
3697
- return unlinkNaive(sourcePath, {
3698
- ...(retryCount >= maxRetries
3699
- ? {}
3700
- : {
3701
- handleTemporaryError: async () => {
3702
- retryCount++;
3703
- return new Promise((resolve) => {
3704
- setTimeout(() => {
3705
- resolve(attempt());
3706
- }, retryCount * retryDelay);
3707
- });
3708
- },
3709
- }),
3710
- });
3696
+ unlinkSyncNaive(sourcePath);
3711
3697
  };
3712
- return attempt();
3698
+ attempt();
3713
3699
  };
3714
3700
 
3715
- const unlinkNaive = (sourcePath, { handleTemporaryError = null } = {}) => {
3716
- return new Promise((resolve, reject) => {
3717
- unlink(sourcePath, (error) => {
3718
- if (error) {
3719
- if (error.code === "ENOENT") {
3720
- resolve();
3721
- } else if (
3722
- handleTemporaryError &&
3723
- (error.code === "EBUSY" ||
3724
- error.code === "EMFILE" ||
3725
- error.code === "ENFILE" ||
3726
- error.code === "ENOENT")
3727
- ) {
3728
- resolve(handleTemporaryError(error));
3729
- } else {
3730
- reject(error);
3731
- }
3732
- } else {
3733
- resolve();
3734
- }
3735
- });
3736
- });
3701
+ const unlinkSyncNaive = (sourcePath, { handleTemporaryError = null } = {}) => {
3702
+ try {
3703
+ unlinkSync(sourcePath);
3704
+ } catch (error) {
3705
+ if (error.code === "ENOENT") {
3706
+ return;
3707
+ }
3708
+ if (
3709
+ handleTemporaryError &&
3710
+ (error.code === "EBUSY" ||
3711
+ error.code === "EMFILE" ||
3712
+ error.code === "ENFILE" ||
3713
+ error.code === "ENOENT")
3714
+ ) {
3715
+ handleTemporaryError(error);
3716
+ return;
3717
+ }
3718
+ throw error;
3719
+ }
3737
3720
  };
3738
3721
 
3739
- const removeDirectory = async (
3722
+ const removeDirectorySync$1 = (
3740
3723
  rootDirectoryUrl,
3741
- { signal, maxRetries, retryDelay, recursive, onlyContent },
3724
+ { maxRetries, retryDelay, recursive, onlyContent },
3742
3725
  ) => {
3743
- const removeDirectoryOperation = Abort.startOperation();
3744
- removeDirectoryOperation.addAbortSignal(signal);
3745
-
3746
- const visit = async (sourceUrl) => {
3747
- removeDirectoryOperation.throwIfAborted();
3748
- const sourceStats = await readEntryStat(sourceUrl, {
3726
+ const visit = (sourceUrl) => {
3727
+ const sourceStats = readEntryStatSync(sourceUrl, {
3749
3728
  nullIfNotFound: true,
3750
3729
  followLink: false,
3751
3730
  });
@@ -3760,31 +3739,30 @@ const removeDirectory = async (
3760
3739
  sourceStats.isCharacterDevice() ||
3761
3740
  sourceStats.isBlockDevice()
3762
3741
  ) {
3763
- await visitFile(sourceUrl);
3742
+ visitFile(sourceUrl);
3764
3743
  } else if (sourceStats.isSymbolicLink()) {
3765
- await visitSymbolicLink(sourceUrl);
3744
+ visitSymbolicLink(sourceUrl);
3766
3745
  } else if (sourceStats.isDirectory()) {
3767
- await visitDirectory(`${sourceUrl}/`);
3746
+ visitDirectory(`${sourceUrl}/`);
3768
3747
  }
3769
3748
  };
3770
3749
 
3771
- const visitDirectory = async (directoryUrl) => {
3750
+ const visitDirectory = (directoryUrl) => {
3772
3751
  const directoryPath = urlToFileSystemPath(directoryUrl);
3773
3752
  const optionsFromRecursive = recursive
3774
3753
  ? {
3775
- handleNotEmptyError: async () => {
3776
- await removeDirectoryContent(directoryUrl);
3777
- await visitDirectory(directoryUrl);
3754
+ handleNotEmptyError: () => {
3755
+ removeDirectoryContent(directoryUrl);
3756
+ visitDirectory(directoryUrl);
3778
3757
  },
3779
3758
  }
3780
3759
  : {};
3781
- removeDirectoryOperation.throwIfAborted();
3782
- await removeDirectoryNaive(directoryPath, {
3760
+ removeDirectorySyncNaive(directoryPath, {
3783
3761
  ...optionsFromRecursive,
3784
3762
  // Workaround for https://github.com/joyent/node/issues/4337
3785
3763
  ...(process.platform === "win32"
3786
3764
  ? {
3787
- handlePermissionError: async (error) => {
3765
+ handlePermissionError: (error) => {
3788
3766
  console.error(
3789
3767
  `trying to fix windows EPERM after readir on ${directoryPath}`,
3790
3768
  );
@@ -3806,8 +3784,7 @@ const removeDirectory = async (
3806
3784
  );
3807
3785
  throw error;
3808
3786
  }
3809
-
3810
- await removeDirectoryNaive(directoryPath, {
3787
+ removeDirectorySyncNaive(directoryPath, {
3811
3788
  ...optionsFromRecursive,
3812
3789
  });
3813
3790
  },
@@ -3816,70 +3793,170 @@ const removeDirectory = async (
3816
3793
  });
3817
3794
  };
3818
3795
 
3819
- const removeDirectoryContent = async (directoryUrl) => {
3820
- removeDirectoryOperation.throwIfAborted();
3821
- const names = await readDirectory(directoryUrl);
3822
- await Promise.all(
3823
- names.map(async (name) => {
3824
- const url = resolveUrl$1(name, directoryUrl);
3825
- await visit(url);
3826
- }),
3827
- );
3796
+ const removeDirectoryContent = (directoryUrl) => {
3797
+ const entryNames = readdirSync(new URL(directoryUrl));
3798
+ for (const entryName of entryNames) {
3799
+ const url = resolveUrl$1(entryName, directoryUrl);
3800
+ visit(url);
3801
+ }
3828
3802
  };
3829
3803
 
3830
- const visitFile = async (fileUrl) => {
3831
- await removeNonDirectory$1(fileUrl, { maxRetries, retryDelay });
3804
+ const visitFile = (fileUrl) => {
3805
+ removeNonDirectory$1(fileUrl);
3832
3806
  };
3833
3807
 
3834
- const visitSymbolicLink = async (symbolicLinkUrl) => {
3835
- await removeNonDirectory$1(symbolicLinkUrl, { maxRetries, retryDelay });
3808
+ const visitSymbolicLink = (symbolicLinkUrl) => {
3809
+ removeNonDirectory$1(symbolicLinkUrl);
3836
3810
  };
3837
3811
 
3838
- try {
3839
- if (onlyContent) {
3840
- await removeDirectoryContent(rootDirectoryUrl);
3841
- } else {
3842
- await visitDirectory(rootDirectoryUrl);
3843
- }
3844
- } finally {
3845
- await removeDirectoryOperation.end();
3812
+ if (onlyContent) {
3813
+ removeDirectoryContent(rootDirectoryUrl);
3814
+ } else {
3815
+ visitDirectory(rootDirectoryUrl);
3846
3816
  }
3847
3817
  };
3848
3818
 
3849
- const removeDirectoryNaive = (
3819
+ const removeDirectorySyncNaive = (
3850
3820
  directoryPath,
3851
3821
  { handleNotEmptyError = null, handlePermissionError = null } = {},
3852
3822
  ) => {
3853
- return new Promise((resolve, reject) => {
3854
- rmdir(directoryPath, (error, lstatObject) => {
3855
- if (error) {
3856
- if (handlePermissionError && error.code === "EPERM") {
3857
- resolve(handlePermissionError(error));
3858
- } else if (error.code === "ENOENT") {
3859
- resolve();
3860
- } else if (
3861
- handleNotEmptyError &&
3862
- // linux os
3863
- (error.code === "ENOTEMPTY" ||
3864
- // SunOS
3865
- error.code === "EEXIST")
3866
- ) {
3867
- resolve(handleNotEmptyError(error));
3868
- } else {
3869
- reject(error);
3823
+ try {
3824
+ rmdirSync(directoryPath);
3825
+ } catch (error) {
3826
+ if (handlePermissionError && error.code === "EPERM") {
3827
+ handlePermissionError(error);
3828
+ return;
3829
+ }
3830
+ if (error.code === "ENOENT") {
3831
+ return;
3832
+ }
3833
+ if (
3834
+ handleNotEmptyError &&
3835
+ // linux os
3836
+ (error.code === "ENOTEMPTY" ||
3837
+ // SunOS
3838
+ error.code === "EEXIST")
3839
+ ) {
3840
+ handleNotEmptyError(error);
3841
+ return;
3842
+ }
3843
+ throw error;
3844
+ }
3845
+ };
3846
+
3847
+ const removeDirectorySync = (url, options = {}) => {
3848
+ return removeEntrySync(url, {
3849
+ ...options,
3850
+ recursive: true,
3851
+ });
3852
+ };
3853
+
3854
+ const writeDirectorySync = (
3855
+ destination,
3856
+ { recursive = true, allowUseless = false, force } = {},
3857
+ ) => {
3858
+ const destinationUrl = assertAndNormalizeDirectoryUrl(destination);
3859
+ const destinationPath = urlToFileSystemPath(destinationUrl);
3860
+
3861
+ let destinationStats;
3862
+ try {
3863
+ destinationStats = readEntryStatSync(destinationUrl, {
3864
+ nullIfNotFound: true,
3865
+ followLink: false,
3866
+ });
3867
+ } catch (e) {
3868
+ if (e.code === "ENOTDIR") {
3869
+ let previousNonDirUrl = destinationUrl;
3870
+ // we must try all parent directories as long as it fails with ENOTDIR
3871
+ findAncestorDirectoryUrl(destinationUrl, (ancestorUrl) => {
3872
+ try {
3873
+ statSync(new URL(ancestorUrl));
3874
+ return true;
3875
+ } catch (e) {
3876
+ if (e.code === "ENOTDIR") {
3877
+ previousNonDirUrl = ancestorUrl;
3878
+ return false;
3879
+ }
3880
+ throw e;
3870
3881
  }
3882
+ });
3883
+ if (force) {
3884
+ unlinkSync(
3885
+ new URL(
3886
+ previousNonDirUrl
3887
+ // remove trailing slash
3888
+ .slice(0, -1),
3889
+ ),
3890
+ );
3871
3891
  } else {
3872
- resolve(lstatObject);
3892
+ throw new Error(
3893
+ `cannot write directory at ${destinationPath} because there is a file at ${urlToFileSystemPath(
3894
+ previousNonDirUrl,
3895
+ )}`,
3896
+ );
3873
3897
  }
3874
- });
3875
- });
3898
+ } else {
3899
+ throw e;
3900
+ }
3901
+ }
3902
+
3903
+ if (destinationStats) {
3904
+ if (destinationStats.isDirectory()) {
3905
+ if (allowUseless) {
3906
+ return;
3907
+ }
3908
+ throw new Error(`directory already exists at ${destinationPath}`);
3909
+ }
3910
+ const destinationType = statsToType(destinationStats);
3911
+ throw new Error(
3912
+ `cannot write directory at ${destinationPath} because there is a ${destinationType}`,
3913
+ );
3914
+ }
3915
+
3916
+ try {
3917
+ mkdirSync(destinationPath, { recursive });
3918
+ } catch (error) {
3919
+ if (allowUseless && error.code === "EEXIST") {
3920
+ return;
3921
+ }
3922
+ throw error;
3923
+ }
3876
3924
  };
3877
3925
 
3878
- process.platform === "win32";
3926
+ const writeFileSync = (destination, content = "", { force } = {}) => {
3927
+ const destinationUrl = assertAndNormalizeFileUrl(destination);
3928
+ const destinationUrlObject = new URL(destinationUrl);
3929
+ if (content && content instanceof URL) {
3930
+ content = readFileSync(content);
3931
+ }
3932
+ try {
3933
+ writeFileSync$1(destinationUrlObject, content);
3934
+ } catch (error) {
3935
+ if (error.code === "EISDIR") {
3936
+ // happens when directory existed but got deleted and now it's a file
3937
+ if (force) {
3938
+ removeDirectorySync(destinationUrlObject);
3939
+ writeFileSync$1(destinationUrlObject, content);
3940
+ } else {
3941
+ throw error;
3942
+ }
3943
+ }
3944
+ if (error.code === "ENOENT" || error.code === "ENOTDIR") {
3945
+ writeDirectorySync(new URL("./", destinationUrlObject), {
3946
+ force,
3947
+ recursive: true,
3948
+ });
3949
+ writeFileSync$1(destinationUrlObject, content);
3950
+ return;
3951
+ }
3952
+ throw error;
3953
+ }
3954
+ };
3879
3955
 
3880
- const removeEntrySync = (
3956
+ const removeEntry = async (
3881
3957
  source,
3882
3958
  {
3959
+ signal = new AbortController().signal,
3883
3960
  allowUseless = false,
3884
3961
  recursive = false,
3885
3962
  maxRetries = 3,
@@ -3888,74 +3965,110 @@ const removeEntrySync = (
3888
3965
  } = {},
3889
3966
  ) => {
3890
3967
  const sourceUrl = assertAndNormalizeFileUrl(source);
3891
- const sourceStats = readEntryStatSync(sourceUrl, {
3892
- nullIfNotFound: true,
3893
- followLink: false,
3894
- });
3895
- if (!sourceStats) {
3896
- if (allowUseless) {
3897
- return;
3968
+
3969
+ const removeOperation = Abort.startOperation();
3970
+ removeOperation.addAbortSignal(signal);
3971
+
3972
+ try {
3973
+ removeOperation.throwIfAborted();
3974
+ const sourceStats = await readEntryStat(sourceUrl, {
3975
+ nullIfNotFound: true,
3976
+ followLink: false,
3977
+ });
3978
+ if (!sourceStats) {
3979
+ if (allowUseless) {
3980
+ return;
3981
+ }
3982
+ throw new Error(`nothing to remove at ${urlToFileSystemPath(sourceUrl)}`);
3898
3983
  }
3899
- throw new Error(`nothing to remove at ${urlToFileSystemPath(sourceUrl)}`);
3984
+
3985
+ // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_class_fs_stats
3986
+ // FIFO and socket are ignored, not sure what they are exactly and what to do with them
3987
+ // other libraries ignore them, let's do the same.
3988
+ if (
3989
+ sourceStats.isFile() ||
3990
+ sourceStats.isSymbolicLink() ||
3991
+ sourceStats.isCharacterDevice() ||
3992
+ sourceStats.isBlockDevice()
3993
+ ) {
3994
+ await removeNonDirectory(
3995
+ sourceUrl.endsWith("/") ? sourceUrl.slice(0, -1) : sourceUrl,
3996
+ {
3997
+ maxRetries,
3998
+ retryDelay,
3999
+ },
4000
+ );
4001
+ } else if (sourceStats.isDirectory()) {
4002
+ await removeDirectory(ensurePathnameTrailingSlash(sourceUrl), {
4003
+ signal: removeOperation.signal,
4004
+ recursive,
4005
+ maxRetries,
4006
+ retryDelay,
4007
+ onlyContent,
4008
+ });
4009
+ }
4010
+ } finally {
4011
+ await removeOperation.end();
3900
4012
  }
4013
+ };
3901
4014
 
3902
- // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_class_fs_stats
3903
- // FIFO and socket are ignored, not sure what they are exactly and what to do with them
3904
- // other libraries ignore them, let's do the same.
3905
- if (
3906
- sourceStats.isFile() ||
3907
- sourceStats.isSymbolicLink() ||
3908
- sourceStats.isCharacterDevice() ||
3909
- sourceStats.isBlockDevice()
3910
- ) {
3911
- removeNonDirectory(
3912
- sourceUrl.endsWith("/") ? sourceUrl.slice(0, -1) : sourceUrl);
3913
- } else if (sourceStats.isDirectory()) {
3914
- const directoryUrl = ensurePathnameTrailingSlash(sourceUrl);
3915
- removeDirectorySync$1(directoryUrl, {
3916
- recursive,
3917
- maxRetries,
3918
- retryDelay,
3919
- onlyContent,
4015
+ const removeNonDirectory = (sourceUrl, { maxRetries, retryDelay }) => {
4016
+ const sourcePath = urlToFileSystemPath(sourceUrl);
4017
+
4018
+ let retryCount = 0;
4019
+ const attempt = () => {
4020
+ return unlinkNaive(sourcePath, {
4021
+ ...(retryCount >= maxRetries
4022
+ ? {}
4023
+ : {
4024
+ handleTemporaryError: async () => {
4025
+ retryCount++;
4026
+ return new Promise((resolve) => {
4027
+ setTimeout(() => {
4028
+ resolve(attempt());
4029
+ }, retryCount * retryDelay);
4030
+ });
4031
+ },
4032
+ }),
3920
4033
  });
3921
- }
3922
- };
3923
-
3924
- const removeNonDirectory = (sourceUrl) => {
3925
- const sourcePath = urlToFileSystemPath(sourceUrl);
3926
- const attempt = () => {
3927
- unlinkSyncNaive(sourcePath);
3928
4034
  };
3929
- attempt();
4035
+ return attempt();
3930
4036
  };
3931
4037
 
3932
- const unlinkSyncNaive = (sourcePath, { handleTemporaryError = null } = {}) => {
3933
- try {
3934
- unlinkSync(sourcePath);
3935
- } catch (error) {
3936
- if (error.code === "ENOENT") {
3937
- return;
3938
- }
3939
- if (
3940
- handleTemporaryError &&
3941
- (error.code === "EBUSY" ||
3942
- error.code === "EMFILE" ||
3943
- error.code === "ENFILE" ||
3944
- error.code === "ENOENT")
3945
- ) {
3946
- handleTemporaryError(error);
3947
- return;
3948
- }
3949
- throw error;
3950
- }
4038
+ const unlinkNaive = (sourcePath, { handleTemporaryError = null } = {}) => {
4039
+ return new Promise((resolve, reject) => {
4040
+ unlink(sourcePath, (error) => {
4041
+ if (error) {
4042
+ if (error.code === "ENOENT") {
4043
+ resolve();
4044
+ } else if (
4045
+ handleTemporaryError &&
4046
+ (error.code === "EBUSY" ||
4047
+ error.code === "EMFILE" ||
4048
+ error.code === "ENFILE" ||
4049
+ error.code === "ENOENT")
4050
+ ) {
4051
+ resolve(handleTemporaryError(error));
4052
+ } else {
4053
+ reject(error);
4054
+ }
4055
+ } else {
4056
+ resolve();
4057
+ }
4058
+ });
4059
+ });
3951
4060
  };
3952
4061
 
3953
- const removeDirectorySync$1 = (
4062
+ const removeDirectory = async (
3954
4063
  rootDirectoryUrl,
3955
- { maxRetries, retryDelay, recursive, onlyContent },
4064
+ { signal, maxRetries, retryDelay, recursive, onlyContent },
3956
4065
  ) => {
3957
- const visit = (sourceUrl) => {
3958
- const sourceStats = readEntryStatSync(sourceUrl, {
4066
+ const removeDirectoryOperation = Abort.startOperation();
4067
+ removeDirectoryOperation.addAbortSignal(signal);
4068
+
4069
+ const visit = async (sourceUrl) => {
4070
+ removeDirectoryOperation.throwIfAborted();
4071
+ const sourceStats = await readEntryStat(sourceUrl, {
3959
4072
  nullIfNotFound: true,
3960
4073
  followLink: false,
3961
4074
  });
@@ -3970,30 +4083,31 @@ const removeDirectorySync$1 = (
3970
4083
  sourceStats.isCharacterDevice() ||
3971
4084
  sourceStats.isBlockDevice()
3972
4085
  ) {
3973
- visitFile(sourceUrl);
4086
+ await visitFile(sourceUrl);
3974
4087
  } else if (sourceStats.isSymbolicLink()) {
3975
- visitSymbolicLink(sourceUrl);
4088
+ await visitSymbolicLink(sourceUrl);
3976
4089
  } else if (sourceStats.isDirectory()) {
3977
- visitDirectory(`${sourceUrl}/`);
4090
+ await visitDirectory(`${sourceUrl}/`);
3978
4091
  }
3979
4092
  };
3980
4093
 
3981
- const visitDirectory = (directoryUrl) => {
4094
+ const visitDirectory = async (directoryUrl) => {
3982
4095
  const directoryPath = urlToFileSystemPath(directoryUrl);
3983
4096
  const optionsFromRecursive = recursive
3984
4097
  ? {
3985
- handleNotEmptyError: () => {
3986
- removeDirectoryContent(directoryUrl);
3987
- visitDirectory(directoryUrl);
4098
+ handleNotEmptyError: async () => {
4099
+ await removeDirectoryContent(directoryUrl);
4100
+ await visitDirectory(directoryUrl);
3988
4101
  },
3989
4102
  }
3990
4103
  : {};
3991
- removeDirectorySyncNaive(directoryPath, {
4104
+ removeDirectoryOperation.throwIfAborted();
4105
+ await removeDirectoryNaive(directoryPath, {
3992
4106
  ...optionsFromRecursive,
3993
4107
  // Workaround for https://github.com/joyent/node/issues/4337
3994
4108
  ...(process.platform === "win32"
3995
4109
  ? {
3996
- handlePermissionError: (error) => {
4110
+ handlePermissionError: async (error) => {
3997
4111
  console.error(
3998
4112
  `trying to fix windows EPERM after readir on ${directoryPath}`,
3999
4113
  );
@@ -4015,7 +4129,8 @@ const removeDirectorySync$1 = (
4015
4129
  );
4016
4130
  throw error;
4017
4131
  }
4018
- removeDirectorySyncNaive(directoryPath, {
4132
+
4133
+ await removeDirectoryNaive(directoryPath, {
4019
4134
  ...optionsFromRecursive,
4020
4135
  });
4021
4136
  },
@@ -4024,59 +4139,69 @@ const removeDirectorySync$1 = (
4024
4139
  });
4025
4140
  };
4026
4141
 
4027
- const removeDirectoryContent = (directoryUrl) => {
4028
- const entryNames = readdirSync(new URL(directoryUrl));
4029
- for (const entryName of entryNames) {
4030
- const url = resolveUrl$1(entryName, directoryUrl);
4031
- visit(url);
4032
- }
4142
+ const removeDirectoryContent = async (directoryUrl) => {
4143
+ removeDirectoryOperation.throwIfAborted();
4144
+ const names = await readDirectory(directoryUrl);
4145
+ await Promise.all(
4146
+ names.map(async (name) => {
4147
+ const url = resolveUrl$1(name, directoryUrl);
4148
+ await visit(url);
4149
+ }),
4150
+ );
4033
4151
  };
4034
4152
 
4035
- const visitFile = (fileUrl) => {
4036
- removeNonDirectory(fileUrl);
4153
+ const visitFile = async (fileUrl) => {
4154
+ await removeNonDirectory(fileUrl, { maxRetries, retryDelay });
4037
4155
  };
4038
4156
 
4039
- const visitSymbolicLink = (symbolicLinkUrl) => {
4040
- removeNonDirectory(symbolicLinkUrl);
4157
+ const visitSymbolicLink = async (symbolicLinkUrl) => {
4158
+ await removeNonDirectory(symbolicLinkUrl, { maxRetries, retryDelay });
4041
4159
  };
4042
4160
 
4043
- if (onlyContent) {
4044
- removeDirectoryContent(rootDirectoryUrl);
4045
- } else {
4046
- visitDirectory(rootDirectoryUrl);
4161
+ try {
4162
+ if (onlyContent) {
4163
+ await removeDirectoryContent(rootDirectoryUrl);
4164
+ } else {
4165
+ await visitDirectory(rootDirectoryUrl);
4166
+ }
4167
+ } finally {
4168
+ await removeDirectoryOperation.end();
4047
4169
  }
4048
4170
  };
4049
4171
 
4050
- const removeDirectorySyncNaive = (
4172
+ const removeDirectoryNaive = (
4051
4173
  directoryPath,
4052
4174
  { handleNotEmptyError = null, handlePermissionError = null } = {},
4053
4175
  ) => {
4054
- try {
4055
- rmdirSync(directoryPath);
4056
- } catch (error) {
4057
- if (handlePermissionError && error.code === "EPERM") {
4058
- handlePermissionError(error);
4059
- return;
4060
- }
4061
- if (error.code === "ENOENT") {
4062
- return;
4063
- }
4064
- if (
4065
- handleNotEmptyError &&
4066
- // linux os
4067
- (error.code === "ENOTEMPTY" ||
4068
- // SunOS
4069
- error.code === "EEXIST")
4070
- ) {
4071
- handleNotEmptyError(error);
4072
- return;
4073
- }
4074
- throw error;
4075
- }
4176
+ return new Promise((resolve, reject) => {
4177
+ rmdir(directoryPath, (error, lstatObject) => {
4178
+ if (error) {
4179
+ if (handlePermissionError && error.code === "EPERM") {
4180
+ resolve(handlePermissionError(error));
4181
+ } else if (error.code === "ENOENT") {
4182
+ resolve();
4183
+ } else if (
4184
+ handleNotEmptyError &&
4185
+ // linux os
4186
+ (error.code === "ENOTEMPTY" ||
4187
+ // SunOS
4188
+ error.code === "EEXIST")
4189
+ ) {
4190
+ resolve(handleNotEmptyError(error));
4191
+ } else {
4192
+ reject(error);
4193
+ }
4194
+ } else {
4195
+ resolve(lstatObject);
4196
+ }
4197
+ });
4198
+ });
4076
4199
  };
4077
4200
 
4078
4201
  process.platform === "win32";
4079
4202
 
4203
+ process.platform === "win32";
4204
+
4080
4205
  const ensureEmptyDirectory = async (source) => {
4081
4206
  const stats = await readEntryStat(source, {
4082
4207
  nullIfNotFound: true,
@@ -4104,13 +4229,6 @@ const ensureEmptyDirectory = async (source) => {
4104
4229
  );
4105
4230
  };
4106
4231
 
4107
- const removeDirectorySync = (url, options = {}) => {
4108
- return removeEntrySync(url, {
4109
- ...options,
4110
- recursive: true,
4111
- });
4112
- };
4113
-
4114
4232
  const callOnceIdlePerFile = (callback, idleMs) => {
4115
4233
  const timeoutIdMap = new Map();
4116
4234
  return (fileEvent) => {
@@ -11364,39 +11482,10 @@ const jsenvPluginTranspilation = ({
11364
11482
  };
11365
11483
 
11366
11484
  const lookupPackageDirectory = (currentUrl) => {
11367
- if (currentUrl === "file:///") {
11368
- return null;
11369
- }
11370
- const packageJsonFileUrl = `${currentUrl}package.json`;
11371
- if (existsSync(new URL(packageJsonFileUrl))) {
11372
- return currentUrl;
11373
- }
11374
- return lookupPackageDirectory(getParentUrl$1(currentUrl));
11375
- };
11376
-
11377
- const getParentUrl$1 = (url) => {
11378
- if (url.startsWith("file://")) {
11379
- // With node.js new URL('../', 'file:///C:/').href
11380
- // returns "file:///C:/" instead of "file:///"
11381
- const resource = url.slice("file://".length);
11382
- const slashLastIndex = resource.lastIndexOf("/");
11383
- if (slashLastIndex === -1) {
11384
- return url;
11385
- }
11386
- const lastCharIndex = resource.length - 1;
11387
- if (slashLastIndex === lastCharIndex) {
11388
- const slashBeforeLastIndex = resource.lastIndexOf(
11389
- "/",
11390
- slashLastIndex - 1,
11391
- );
11392
- if (slashBeforeLastIndex === -1) {
11393
- return url;
11394
- }
11395
- return `file://${resource.slice(0, slashBeforeLastIndex + 1)}`;
11396
- }
11397
- return `file://${resource.slice(0, slashLastIndex + 1)}`;
11398
- }
11399
- return new URL(url.endsWith("/") ? "../" : "./", url).href;
11485
+ return findAncestorDirectoryUrl(currentUrl, (ancestorDirectoryUrl) => {
11486
+ const potentialPackageJsonFileUrl = `${ancestorDirectoryUrl}package.json`;
11487
+ return existsSync(new URL(potentialPackageJsonFileUrl));
11488
+ });
11400
11489
  };
11401
11490
 
11402
11491
  const watchSourceFiles = (
@@ -14368,18 +14457,7 @@ const createUrlInfoTransformer = ({
14368
14457
  contentIsInlined = false;
14369
14458
  }
14370
14459
  if (!contentIsInlined) {
14371
- try {
14372
- writeFileSync(new URL(generatedUrl), urlInfo.content);
14373
- } catch (e) {
14374
- if (e.code === "EISDIR") {
14375
- // happens when directory existed but got delete
14376
- // we can safely remove that directory and write the new file
14377
- removeDirectorySync(new URL(generatedUrl));
14378
- writeFileSync(new URL(generatedUrl), urlInfo.content);
14379
- } else {
14380
- throw e;
14381
- }
14382
- }
14460
+ writeFileSync(new URL(generatedUrl), urlInfo.content, { force: true });
14383
14461
  }
14384
14462
  const { sourcemapGeneratedUrl, sourcemapReference } = urlInfo;
14385
14463
  if (sourcemapGeneratedUrl && sourcemapReference) {
@@ -19175,6 +19253,7 @@ const jsenvPluginProtocolFile = ({
19175
19253
  if (!urlInfo.url.startsWith("file:")) {
19176
19254
  return null;
19177
19255
  }
19256
+ const { rootDirectoryUrl } = urlInfo.context;
19178
19257
  const generateContent = () => {
19179
19258
  const urlObject = new URL(urlInfo.url);
19180
19259
  const { firstReference } = urlInfo;
@@ -19193,11 +19272,12 @@ const jsenvPluginProtocolFile = ({
19193
19272
  : false;
19194
19273
  if (acceptsHtml) {
19195
19274
  firstReference.expectedType = "html";
19196
- const html = generateHtmlForDirectory(
19197
- urlObject.href,
19198
- directoryContentArray,
19199
- urlInfo.context.rootDirectoryUrl,
19275
+ const directoryUrl = urlObject.href;
19276
+ const directoryContentItems = generateDirectoryContentItems(
19277
+ directoryUrl,
19278
+ rootDirectoryUrl,
19200
19279
  );
19280
+ const html = generateHtmlForDirectory(directoryContentItems);
19201
19281
  return {
19202
19282
  type: "html",
19203
19283
  contentType: "text/html",
@@ -19230,32 +19310,13 @@ const jsenvPluginProtocolFile = ({
19230
19310
  if (e.code !== "ENOENT") {
19231
19311
  throw e;
19232
19312
  }
19233
- const rootDirectoryUrl = urlInfo.context.rootDirectoryUrl;
19234
- let firstExistingAncestorDirectoryUrl = new URL("./", urlInfo.url);
19235
- while (!existsSync(firstExistingAncestorDirectoryUrl)) {
19236
- firstExistingAncestorDirectoryUrl = new URL(
19237
- "../",
19238
- firstExistingAncestorDirectoryUrl,
19239
- );
19240
- if (
19241
- !urlIsInsideOf(
19242
- firstExistingAncestorDirectoryUrl,
19243
- rootDirectoryUrl,
19244
- )
19245
- ) {
19246
- firstExistingAncestorDirectoryUrl = rootDirectoryUrl;
19247
- break;
19248
- }
19249
- }
19250
-
19251
- const firstExistingAncestorDirectoryContent = readdirSync(
19252
- new URL(firstExistingAncestorDirectoryUrl),
19313
+ const directoryContentItems = generateDirectoryContentItems(
19314
+ urlInfo.url,
19315
+ rootDirectoryUrl,
19253
19316
  );
19254
19317
  const html = generateHtmlForENOENT(
19255
19318
  urlInfo.url,
19256
- firstExistingAncestorDirectoryContent,
19257
- firstExistingAncestorDirectoryUrl,
19258
- urlInfo.context.rootDirectoryUrl,
19319
+ directoryContentItems,
19259
19320
  directoryListingUrlMocks,
19260
19321
  );
19261
19322
  return {
@@ -19274,11 +19335,9 @@ const jsenvPluginProtocolFile = ({
19274
19335
  ];
19275
19336
  };
19276
19337
 
19277
- const generateHtmlForDirectory = (
19278
- directoryUrl,
19279
- directoryContentArray,
19280
- rootDirectoryUrl,
19281
- ) => {
19338
+ const generateHtmlForDirectory = (directoryContentItems) => {
19339
+ let directoryUrl = directoryContentItems.firstExistingDirectoryUrl;
19340
+ const rootDirectoryUrl = directoryContentItems.rootDirectoryUrl;
19282
19341
  directoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl);
19283
19342
 
19284
19343
  const htmlForDirectory = String(readFileSync(htmlFileUrlForDirectory));
@@ -19287,23 +19346,19 @@ const generateHtmlForDirectory = (
19287
19346
  directoryUrl,
19288
19347
  directoryNav: () =>
19289
19348
  generateDirectoryNav(directoryRelativeUrl, rootDirectoryUrl),
19290
- directoryContent: () =>
19291
- generateDirectoryContent(
19292
- directoryContentArray,
19293
- directoryUrl,
19294
- rootDirectoryUrl,
19295
- ),
19349
+ directoryContent: () => generateDirectoryContent(directoryContentItems),
19296
19350
  };
19297
19351
  const html = replacePlaceholders$1(htmlForDirectory, replacers);
19298
19352
  return html;
19299
19353
  };
19300
19354
  const generateHtmlForENOENT = (
19301
19355
  url,
19302
- ancestorDirectoryContentArray,
19303
- ancestorDirectoryUrl,
19304
- rootDirectoryUrl,
19356
+ directoryContentItems,
19305
19357
  directoryListingUrlMocks,
19306
19358
  ) => {
19359
+ const ancestorDirectoryUrl = directoryContentItems.firstExistingDirectoryUrl;
19360
+ const rootDirectoryUrl = directoryContentItems.rootDirectoryUrl;
19361
+
19307
19362
  const htmlFor404AndAncestorDir = String(
19308
19363
  readFileSync(html404AndAncestorDirFileUrl),
19309
19364
  );
@@ -19322,11 +19377,7 @@ const generateHtmlForENOENT = (
19322
19377
  ancestorDirectoryNav: () =>
19323
19378
  generateDirectoryNav(ancestorDirectoryRelativeUrl, rootDirectoryUrl),
19324
19379
  ancestorDirectoryContent: () =>
19325
- generateDirectoryContent(
19326
- ancestorDirectoryContentArray,
19327
- ancestorDirectoryUrl,
19328
- rootDirectoryUrl,
19329
- ),
19380
+ generateDirectoryContent(directoryContentItems),
19330
19381
  };
19331
19382
  const html = replacePlaceholders$1(htmlFor404AndAncestorDir, replacers);
19332
19383
  return html;
@@ -19368,31 +19419,95 @@ const generateDirectoryNav = (relativeUrl, rootDirectoryUrl) => {
19368
19419
  }
19369
19420
  return dirPartsHtml;
19370
19421
  };
19371
- const generateDirectoryContent = (
19372
- directoryContentArray,
19373
- directoryUrl,
19374
- rootDirectoryUrl,
19375
- ) => {
19376
- if (directoryContentArray.length === 0) {
19377
- return `<p>Directory is empty</p>`;
19422
+ const generateDirectoryContentItems = (directoryUrl, rootDirectoryUrl) => {
19423
+ let firstExistingDirectoryUrl = new URL("./", directoryUrl);
19424
+ while (!existsSync(firstExistingDirectoryUrl)) {
19425
+ firstExistingDirectoryUrl = new URL("../", firstExistingDirectoryUrl);
19426
+ if (!urlIsInsideOf(firstExistingDirectoryUrl, rootDirectoryUrl)) {
19427
+ firstExistingDirectoryUrl = new URL(rootDirectoryUrl);
19428
+ break;
19429
+ }
19378
19430
  }
19379
- const sortedNames = [];
19431
+ const directoryContentArray = readdirSync(firstExistingDirectoryUrl);
19432
+ const fileUrls = [];
19380
19433
  for (const filename of directoryContentArray) {
19381
- const fileUrlObject = new URL(filename, directoryUrl);
19382
- if (lstatSync(fileUrlObject).isDirectory()) {
19383
- sortedNames.push(`${filename}/`);
19434
+ const fileUrlObject = new URL(filename, firstExistingDirectoryUrl);
19435
+ fileUrls.push(fileUrlObject);
19436
+ }
19437
+ package_workspaces: {
19438
+ if (String(firstExistingDirectoryUrl) !== String(rootDirectoryUrl)) {
19439
+ break package_workspaces;
19440
+ }
19441
+ const packageDirectoryUrl = lookupPackageDirectory(rootDirectoryUrl);
19442
+ if (!packageDirectoryUrl) {
19443
+ break package_workspaces;
19444
+ }
19445
+ if (String(packageDirectoryUrl) === String(rootDirectoryUrl)) {
19446
+ break package_workspaces;
19447
+ }
19448
+ let packageContent;
19449
+ try {
19450
+ packageContent = JSON.parse(
19451
+ readFileSync(new URL("package.json", packageDirectoryUrl), "utf8"),
19452
+ );
19453
+ } catch {
19454
+ break package_workspaces;
19455
+ }
19456
+ const { workspaces } = packageContent;
19457
+ if (Array.isArray(workspaces)) {
19458
+ for (const workspace of workspaces) {
19459
+ const workspaceUrlObject = new URL(workspace, packageDirectoryUrl);
19460
+ const workspaceUrl = workspaceUrlObject.href;
19461
+ if (workspaceUrl.endsWith("*")) {
19462
+ const directoryUrl = ensurePathnameTrailingSlash(
19463
+ workspaceUrl.slice(0, -1),
19464
+ );
19465
+ fileUrls.push(new URL(directoryUrl));
19466
+ } else {
19467
+ fileUrls.push(ensurePathnameTrailingSlash(workspaceUrlObject));
19468
+ }
19469
+ }
19470
+ }
19471
+ }
19472
+
19473
+ const sortedUrls = [];
19474
+ for (let fileUrl of fileUrls) {
19475
+ if (lstatSync(fileUrl).isDirectory()) {
19476
+ sortedUrls.push(ensurePathnameTrailingSlash(fileUrl));
19384
19477
  } else {
19385
- sortedNames.push(filename);
19478
+ sortedUrls.push(fileUrl);
19386
19479
  }
19387
19480
  }
19388
- sortedNames.sort(comparePathnames);
19389
- let html = `<ul class="directory_content">`;
19390
- for (const filename of sortedNames) {
19391
- const fileUrlObject = new URL(filename, directoryUrl);
19392
- const fileUrl = String(fileUrlObject);
19393
- const fileUrlRelativeToParent = urlToRelativeUrl(fileUrl, directoryUrl);
19394
- const fileUrlRelativeToRoot = urlToRelativeUrl(fileUrl, rootDirectoryUrl);
19481
+ sortedUrls.sort((a, b) => {
19482
+ return comparePathnames(a.pathname, b.pathname);
19483
+ });
19484
+
19485
+ const items = [];
19486
+ for (const sortedUrl of sortedUrls) {
19487
+ const fileUrlRelativeToParent = urlToRelativeUrl(
19488
+ sortedUrl,
19489
+ firstExistingDirectoryUrl,
19490
+ );
19491
+ const fileUrlRelativeToRoot = urlToRelativeUrl(sortedUrl, rootDirectoryUrl);
19395
19492
  const type = fileUrlRelativeToParent.endsWith("/") ? "dir" : "file";
19493
+ items.push({
19494
+ type,
19495
+ fileUrlRelativeToParent,
19496
+ fileUrlRelativeToRoot,
19497
+ });
19498
+ }
19499
+ items.rootDirectoryUrl = rootDirectoryUrl;
19500
+ items.firstExistingDirectoryUrl = firstExistingDirectoryUrl;
19501
+ return items;
19502
+ };
19503
+ const generateDirectoryContent = (directoryContentItems) => {
19504
+ if (directoryContentItems.length === 0) {
19505
+ return `<p>Directory is empty</p>`;
19506
+ }
19507
+ let html = `<ul class="directory_content">`;
19508
+ for (const directoryContentItem of directoryContentItems) {
19509
+ const { type, fileUrlRelativeToParent, fileUrlRelativeToRoot } =
19510
+ directoryContentItem;
19396
19511
  html += `
19397
19512
  <li class="directory_child" data-type="${type}">
19398
19513
  <a href="/${fileUrlRelativeToRoot}">${fileUrlRelativeToParent}</a>