deel-local-cli 1.20.9 → 1.20.11

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.
@@ -122,18 +122,54 @@ export class History {
122
122
  #maybePrune() {
123
123
  this.#writes = (this.#writes ?? 0) + 1;
124
124
  if (this.#writes % 20 !== 0) return;
125
+ // 앞서 못 줄였으면 한참 쉬었다가 다시 본다 (prune 의 주석).
126
+ if (this.#writes < this.#다음줄이기) return;
125
127
  try {
126
128
  if (statSync(this.file).size < MAX_BYTES) return;
127
129
  } catch { return; }
128
- this.prune();
130
+ /*
131
+ * 줄이다 터져도 **고치던 파일까지 말려들면 안 된다.**
132
+ *
133
+ * 이 함수는 snapshot() 안에서 돈다 — 즉 Write·Edit 이 파일을 쓰기
134
+ * 직전이다. prune() 이 이력을 읽다 던지면(EACCES·EBUSY) 그 예외가
135
+ * 도구 밖으로 새어 나가, 사람은 고치려던 파일과 아무 상관 없는
136
+ * 「EACCES: …/edits.jsonl」 을 보게 된다. 이력을 못 줄인 것은
137
+ * 위쪽(줄이기못함)에 남기고, 고치는 일은 그대로 이어 간다.
138
+ */
139
+ try { this.prune(); }
140
+ catch (err) {
141
+ this.줄이기못함 = err?.message ?? String(err);
142
+ this.#다음줄이기 = this.#writes + 500;
143
+ }
129
144
  }
130
145
 
146
+ #다음줄이기 = 0;
147
+ /** 이력을 못 줄였으면 그 까닭. 못 줄이면 안전망이 제 무게로 무너진다. */
148
+ 줄이기못함 = null;
149
+
131
150
  #writes = 0;
132
151
 
133
152
  /** 최근 keep 개 턴만 남기고 자른다. 버린 줄 수를 돌려준다. */
134
153
  prune({ keep = KEEP_TURNS } = {}) {
135
- const recs = this.all();
136
- const turns = this.turns();
154
+ /*
155
+ * ── **읽다 터지는 것**도 못 줄인 것이다 ────────────────────────────
156
+ *
157
+ * 아래 쓰기만 감싸 두고 여기 읽기는 맨몸이었다. 그런데 이력을 못 읽는
158
+ * 판(권한·잠김·자리가 폴더로 바뀜)은 못 쓰는 판만큼이나 흔하고, 그때
159
+ * 예외가 여기를 뚫고 나가면 부르는 쪽 — 파일을 쓰던 Write·Edit — 이
160
+ * 고치려던 파일과 아무 상관 없는 오류를 뒤집어쓴다.
161
+ *
162
+ * 못 줄인 것은 못 줄였다고 위쪽(줄이기못함)에 남기고 0을 돌려준다.
163
+ * 그게 이 함수가 실패를 말하는 유일한 방식이라 여기서도 같아야 한다.
164
+ */
165
+ let recs;
166
+ let turns;
167
+ try { recs = this.all(); turns = this.turns(); }
168
+ catch (err) {
169
+ this.줄이기못함 = err?.message ?? String(err);
170
+ this.#다음줄이기 = (this.#writes ?? 0) + 500;
171
+ return 0;
172
+ }
137
173
  if (turns.length <= keep) return 0;
138
174
  const 남길턴 = new Set(turns.slice(-keep));
139
175
  const 남길것 = recs.filter((r) => 남길턴.has(r.turn));
@@ -143,7 +179,22 @@ export class History {
143
179
  writeFileSync(this.file, 남길것.map((r) => JSON.stringify(r)).join('\n') + (남길것.length ? '\n' : ''), 'utf8');
144
180
  // 통째로 다시 쓰면 새 파일일 수 있다 — 빗장을 다시 건다 (agent/store.js 도 같다).
145
181
  this.#잠갔나 = false; this.#잠그기();
146
- } catch { return 0; }
182
+ this.줄이기못함 = null;
183
+ } catch (err) {
184
+ /*
185
+ * 못 줄인 것을 **0개 버렸다**와 같은 값으로 돌려주고 있었다.
186
+ *
187
+ * 부르는 쪽은 둘을 구별할 길이 없다. 그리고 실패는 대개 이어진다 —
188
+ * 읽기 전용이거나 디스크가 찬 것이라 다음 번에도 같다. 그러면 이력은
189
+ * 32MB 를 넘긴 채 끝없이 자라는데 화면에는 영영 안 뜬다.
190
+ *
191
+ * 곧바로 또 시도하지도 않는다. 32MB 짜리 파일을 스무 번 쓸 때마다
192
+ * 통째로 다시 읽는 셈이라, 실패가 이어지면 그 자체가 느려지는 원인이 된다.
193
+ */
194
+ this.줄이기못함 = err?.message ?? String(err);
195
+ this.#다음줄이기 = (this.#writes ?? 0) + 500;
196
+ return 0;
197
+ }
147
198
  return 버린수;
148
199
  }
149
200
 
@@ -152,12 +203,30 @@ export class History {
152
203
  try { return statSync(this.file).size; } catch { return 0; }
153
204
  }
154
205
 
206
+ /*
207
+ * ── 깨진 줄은 **되돌릴 수 없는 파일**이다 ────────────────────────────
208
+ *
209
+ * 여태 `catch { return null }` 로 조용히 버렸다. 그런데 이 파일의 한 줄은
210
+ * 곧 한 파일의 원문이다 — 줄이 깨졌다는 것은 그 파일을 되돌릴 길이
211
+ * 사라졌다는 뜻이다. 그걸 안 세면 `/undo` 는 「파일 3개를 되돌렸습니다」
212
+ * 라고만 하고, 넷째 파일이 왜 안 돌아왔는지는 아무 데도 안 남는다.
213
+ *
214
+ * 줄은 실제로 깨진다. 쓰다 죽으면 마지막 줄이 반만 적히고, 같은 폴더에서
215
+ * 창을 둘 띄워 놓으면 큰 줄 둘이 서로 끼어든다(appendFileSync 는 긴 줄을
216
+ * 통째로 보장하지 않는다).
217
+ */
218
+ /** 마지막으로 읽을 때 깨져 있던 줄 수. 0 이 아니면 그만큼 못 되돌린다. */
219
+ 깨진줄 = 0;
220
+
155
221
  all() {
156
222
  if (!existsSync(this.file)) return [];
157
- return readFileSync(this.file, 'utf8')
223
+ let 깨진 = 0;
224
+ const 것들 = readFileSync(this.file, 'utf8')
158
225
  .split('\n').filter(Boolean)
159
- .map((l) => { try { return JSON.parse(l); } catch { return null; } })
226
+ .map((l) => { try { return JSON.parse(l); } catch { 깨진 += 1; return null; } })
160
227
  .filter(Boolean);
228
+ this.깨진줄 = 깨진;
229
+ return 것들;
161
230
  }
162
231
 
163
232
  turns() {
@@ -232,10 +301,31 @@ export class History {
232
301
  */
233
302
  const 못한것 = new Set(restored.filter((x) => x.ok === false).map((x) => x.path));
234
303
  const keep = recs.filter((r) => !turns.includes(r.turn) || 못한것.has(r.path));
235
- writeFileSync(this.file, keep.map((r) => JSON.stringify(r)).join('\n') + (keep.length ? '\n' : ''), 'utf8');
236
- this.#잠갔나 = false; this.#잠그기();
304
+ /*
305
+ * ── 여기가 터지면 **파일은 이미 되돌아가 있다** ────────────────────
306
+ *
307
+ * 감싸지 않은 쓰기였다. 던지면 위에서 받아 「명령을 처리하다 막혔습니다」
308
+ * 한 줄만 남는다 — 그런데 디스크의 파일들은 방금 되돌아갔다. 화면이
309
+ * 실패라고 말하는데 실제로는 성공한, 이 저장소가 계속 잡아 온 어긋남이
310
+ * 방향만 뒤집힌 꼴이다.
311
+ *
312
+ * 게다가 이력을 못 줄였으니 그 턴이 그대로 남는다. 사람이 「실패했으니
313
+ * 다시」 하고 /undo 를 또 치면 **같은 턴**을 또 되돌린다 — 두 턴을
314
+ * 되돌린 줄 알지만 한 턴이다.
315
+ */
316
+ let 이력줄임 = { ok: true };
317
+ try {
318
+ writeFileSync(this.file, keep.map((r) => JSON.stringify(r)).join('\n') + (keep.length ? '\n' : ''), 'utf8');
319
+ this.#잠갔나 = false; this.#잠그기();
320
+ } catch (err) {
321
+ 이력줄임 = { ok: false, 왜: err?.message ?? String(err) };
322
+ }
237
323
  return {
238
324
  restored,
325
+ // 이력에서 그 턴을 지웠나. 못 지웠으면 같은 턴이 그대로 남아 있다.
326
+ 이력줄임,
327
+ // 이력에서 못 읽은 줄 수. 그만큼은 되돌릴 길이 애초에 없었다.
328
+ 깨진줄: this.깨진줄,
239
329
  // 화면·감사기록이 쓰는 수. **진짜로 되돌아간 것만** 센다.
240
330
  되돌린수: restored.filter((x) => x.ok === true).length,
241
331
  못한것: restored.filter((x) => x.ok === false),
@@ -297,6 +297,8 @@ const 한숨 = () => new Promise((풀기) => setImmediate(풀기));
297
297
  export async function walk(root, { limit = 훑기상한(), skipDirs = SKIP_DIRS, ignore = true, signal = null } = {}) {
298
298
  const out = [];
299
299
  const 건너뜀 = { 폴더: 0, 파일: 0 };
300
+ // .gitignore 로 건너뛴 것과 다르다 — 이쪽은 우리가 늘 안 보는 살림 폴더다.
301
+ const 건너뛴살림 = [];
300
302
  let 끊김 = false;
301
303
  let 본것 = 0;
302
304
  const stack = [{ dir: root, rel: '', 규칙: ignore ? 뿌리규칙읽기(root) : [] }];
@@ -315,7 +317,10 @@ export async function walk(root, { limit = 훑기상한(), skipDirs = SKIP_DIRS,
315
317
  const full = join(dir, e.name);
316
318
  const erel = rel ? `${rel}/${e.name}` : e.name;
317
319
  if (e.isDirectory()) {
318
- if (skipDirs.has(e.name)) continue;
320
+ // 살림 폴더(node_modules·.git·dist…) 안 훑는다. 다만 **몇 개를
321
+ // 안 봤는지는 센다** — Move 가 폴더를 통째로 옮길 때 이 수를 모르면
322
+ // 「폴더 12개 파일」 이라고 말해 놓고 3만 개를 옮기게 된다.
323
+ if (skipDirs.has(e.name)) { 건너뛴살림.push(erel); continue; }
319
324
  if (여기규칙.length && 걸리나(erel, true, 여기규칙)) { 건너뜀.폴더 += 1; continue; }
320
325
  stack.push({ dir: full, rel: erel, 규칙: 여기규칙 });
321
326
  } else if (e.isFile()) {
@@ -335,6 +340,7 @@ export async function walk(root, { limit = 훑기상한(), skipDirs = SKIP_DIRS,
335
340
  if (끊김) break;
336
341
  }
337
342
  Object.defineProperty(out, '건너뜀', { value: 건너뜀, enumerable: false });
343
+ Object.defineProperty(out, '건너뛴살림', { value: 건너뛴살림, enumerable: false });
338
344
  // 상한까지 찼으면 "여기서 멈췄다" 고 표시한다. 딱 맞아떨어져 끝난 경우까지
339
345
  // 잘렸다고 하게 되지만, 그쪽으로 틀리는 편이 낫다 — 덜 봤다고 말하는 것은
340
346
  // 사람을 한 번 더 보게 만들 뿐이고, 다 봤다고 말하는 것은 못 보게 만든다.