safe-rm 3.2.0 → 3.3.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.
@@ -23,5 +23,6 @@ jobs:
23
23
  npm install
24
24
  npm run build --if-present
25
25
  npm run test:dev
26
+ npm run test:mock-linux
26
27
  env:
27
28
  CI: true
package/README.md CHANGED
@@ -173,8 +173,7 @@ Targets outside the configured scope will be skipped and reported as unsafe.
173
173
  For example:
174
174
 
175
175
  ```sh
176
- export SAFE_RM_SCOPE="$HOME"
177
- safe-rm -rf /
176
+ SAFE_RM_SCOPE="$HOME" safe-rm -rf /
178
177
  # rm: target '/' skipped, unsafe directory scope
179
178
  ```
180
179
 
@@ -186,6 +185,27 @@ SAFE_RM_SCOPE=. safe-rm -rf ../
186
185
  # rm: target '../' skipped, unsafe directory scope
187
186
  ```
188
187
 
188
+ ### Honor Options After Operands (`rm dir -rf`)
189
+
190
+ GNU `rm` (most Linux distros) accepts options anywhere on the command line, so `rm dir -rf` works like `rm -rf dir`. BSD `rm` (MacOS) does not — it stops parsing options at the first operand and would treat `-rf` as a filename.
191
+
192
+ By default, `safe-rm` mirrors the host's real `rm`:
193
+
194
+ - On Linux: options after operands are parsed as flags (GNU style).
195
+ - On MacOS: options after operands are treated as filenames (BSD style).
196
+
197
+ To override the auto-detection (e.g. on Alpine/BusyBox where `rm` does not permute):
198
+
199
+ ```sh
200
+ # Force GNU-style permutation
201
+ export SAFE_RM_OPTIONS_ANYWHERE=yes
202
+
203
+ # Force BSD-style strict ordering
204
+ export SAFE_RM_OPTIONS_ANYWHERE=no
205
+ ```
206
+
207
+ `--` always terminates option parsing in either mode.
208
+
189
209
  ### Protect Files And Directories From Deleting
190
210
 
191
211
  If you want to protect some certain files or directories from deleting by mistake, you could create a `.gitignore` file under the `"~/.safe-rm/"` directory, you could write [.gitignore rules](https://git-scm.com/docs/gitignore) inside the file.
package/bin/rm.sh CHANGED
@@ -90,6 +90,25 @@ else
90
90
  fi
91
91
 
92
92
 
93
+ # Whether to honor options after the first operand (`rm dir -rf`).
94
+ # Defaults to auto: enabled on Linux, disabled on MacOS. yes|no to override.
95
+ case ${SAFE_RM_OPTIONS_ANYWHERE:0:1} in
96
+ [yY])
97
+ OPTIONS_ANYWHERE=1
98
+ ;;
99
+ [nN])
100
+ OPTIONS_ANYWHERE=
101
+ ;;
102
+ *)
103
+ if [[ "$OS_TYPE" == "Linux" ]]; then
104
+ OPTIONS_ANYWHERE=1
105
+ else
106
+ OPTIONS_ANYWHERE=
107
+ fi
108
+ ;;
109
+ esac
110
+
111
+
93
112
  # The target trash directory to dispose files and directories,
94
113
  # defaults to the system trash directory
95
114
  SAFE_RM_TRASH=${SAFE_RM_TRASH:="$DEFAULT_TRASH"}
@@ -270,7 +289,7 @@ while [[ -n $1 ]]; do
270
289
  # -> args: [], files: ['-']
271
290
  *)
272
291
  push_file "$1"; debug "$LINENO: file $1"
273
- ARG_END=1
292
+ [[ -z "$OPTIONS_ANYWHERE" ]] && ARG_END=1
274
293
  ;;
275
294
  esac
276
295
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safe-rm",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "A much safer replacement of bash rm with nearly full functionalities and options of the rm command!",
5
5
  "bin": {
6
6
  "safe-rm": "./bin/rm.sh"
package/test/cases.js CHANGED
@@ -975,4 +975,78 @@ fi
975
975
  t.is(result.code, 0, 'exit code should be 0')
976
976
  t.false(await pathExists(filepath), 'file should be removed')
977
977
  })
978
+
979
+ // Flag-after-operand parsing: align with the host's real `rm`.
980
+ // GNU rm (Linux) permutes options; BSD rm (MacOS) does not.
981
+ !is_rm(type) && !is_as(type) && !IS_MACOS && test(`Linux: flags after operand parse as options`, async t => {
982
+ const {
983
+ createDir,
984
+ createFile,
985
+ runRm,
986
+ pathExists
987
+ } = t.context
988
+
989
+ const dirpath = await createDir()
990
+ await createFile({under: dirpath})
991
+
992
+ const result = await runRm([dirpath, '-rf'])
993
+
994
+ assertEmptySuccess(t, result)
995
+ t.false(await pathExists(dirpath), 'directory should be removed')
996
+ })
997
+
998
+ !is_rm(type) && !is_as(type) && IS_MACOS && test(`MacOS: flags after operand are treated as filenames`, async t => {
999
+ const {
1000
+ createDir,
1001
+ createFile,
1002
+ runRm,
1003
+ pathExists
1004
+ } = t.context
1005
+
1006
+ const dirpath = await createDir()
1007
+ await createFile({under: dirpath})
1008
+
1009
+ const result = await runRm([dirpath, '-rf'])
1010
+
1011
+ t.is(result.code, 1, 'exit code should be 1')
1012
+ t.true(await pathExists(dirpath), 'directory should remain')
1013
+ })
1014
+
1015
+ !is_rm(type) && !is_as(type) && IS_MACOS && test(`SAFE_RM_OPTIONS_ANYWHERE=yes enables permutation on MacOS`, async t => {
1016
+ const {
1017
+ createDir,
1018
+ createFile,
1019
+ runRm,
1020
+ pathExists
1021
+ } = t.context
1022
+
1023
+ const dirpath = await createDir()
1024
+ await createFile({under: dirpath})
1025
+
1026
+ const result = await runRm([dirpath, '-rf'], {
1027
+ env: {SAFE_RM_OPTIONS_ANYWHERE: 'yes'}
1028
+ })
1029
+
1030
+ assertEmptySuccess(t, result)
1031
+ t.false(await pathExists(dirpath), 'directory should be removed')
1032
+ })
1033
+
1034
+ !is_rm(type) && !is_as(type) && !IS_MACOS && test(`SAFE_RM_OPTIONS_ANYWHERE=no disables permutation on Linux`, async t => {
1035
+ const {
1036
+ createDir,
1037
+ createFile,
1038
+ runRm,
1039
+ pathExists
1040
+ } = t.context
1041
+
1042
+ const dirpath = await createDir()
1043
+ await createFile({under: dirpath})
1044
+
1045
+ const result = await runRm([dirpath, '-rf'], {
1046
+ env: {SAFE_RM_OPTIONS_ANYWHERE: 'no'}
1047
+ })
1048
+
1049
+ t.is(result.code, 1, 'exit code should be 1')
1050
+ t.true(await pathExists(dirpath), 'directory should remain')
1051
+ })
978
1052
  }