@opencraw/studio 0.0.13 → 0.0.15

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/dist/index.esm.js CHANGED
@@ -8,7 +8,7 @@ import { randomBytes, createHash } from 'node:crypto';
8
8
  import { execFile } from 'node:child_process';
9
9
  import { createRequire } from 'node:module';
10
10
  import { load } from 'cheerio';
11
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
11
+ import { readFile, writeFile, rm, rename, mkdir } from 'node:fs/promises';
12
12
  import { describeJson } from '@opencraw/probe';
13
13
  import { tmpdir } from 'node:os';
14
14
  import { existsSync, createReadStream } from 'node:fs';
@@ -2877,11 +2877,40 @@ function parseOne(file, content, kind, parse) {
2877
2877
  * round-trip for a hand-formatted file is future work; see this file's test
2878
2878
  * for what is guaranteed today.
2879
2879
  *
2880
+ * The text goes to a sibling temporary file that is then renamed over the
2881
+ * recipe, so a reader (or a crash) never sees a half-written file. Windows
2882
+ * refuses a rename over a file another process has open (`EPERM`, `EBUSY`),
2883
+ * so the rename is retried briefly and then gives way to a plain write.
2884
+ *
2880
2885
  * @param path - The recipe file to write.
2881
2886
  * @param recipe - The whole recipe object.
2882
2887
  */
2883
2888
  async function saveRecipe(path, recipe) {
2884
- await writeFile(path, `${JSON.stringify(recipe, null, 2)}\n`, 'utf8');
2889
+ const temporary = `${path}.${String(process.pid)}.tmp`;
2890
+ const text = `${JSON.stringify(recipe, null, 2)}\n`;
2891
+ await writeFile(temporary, text, 'utf8');
2892
+ try {
2893
+ await renameWithRetry(temporary, path);
2894
+ } catch {
2895
+ await writeFile(path, text, 'utf8');
2896
+ } finally {
2897
+ await rm(temporary, {
2898
+ force: true
2899
+ });
2900
+ }
2901
+ }
2902
+ const RENAME_ATTEMPTS = 5;
2903
+ const RENAME_PAUSE_MS = 20;
2904
+ async function renameWithRetry(from, to) {
2905
+ for (let attempt = 1;; attempt += 1) {
2906
+ try {
2907
+ await rename(from, to);
2908
+ return;
2909
+ } catch (error) {
2910
+ if (attempt >= RENAME_ATTEMPTS) throw error;
2911
+ await new Promise(resolve => setTimeout(resolve, RENAME_PAUSE_MS));
2912
+ }
2913
+ }
2885
2914
  }
2886
2915
 
2887
2916
  /** How many `name-2.ext`, `name-3.ext`… are tried before giving up on a folder full of same-named copies. */
@@ -9,6 +9,11 @@
9
9
  * round-trip for a hand-formatted file is future work; see this file's test
10
10
  * for what is guaranteed today.
11
11
  *
12
+ * The text goes to a sibling temporary file that is then renamed over the
13
+ * recipe, so a reader (or a crash) never sees a half-written file. Windows
14
+ * refuses a rename over a file another process has open (`EPERM`, `EBUSY`),
15
+ * so the rename is retried briefly and then gives way to a plain write.
16
+ *
12
17
  * @param path - The recipe file to write.
13
18
  * @param recipe - The whole recipe object.
14
19
  */