@sanurb/ringi 0.2.0 → 0.2.1

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.
@@ -1,3526 +0,0 @@
1
- import * as HttpApiSchema from '@effect/platform/HttpApiSchema';
2
- import * as Schema3 from 'effect/Schema';
3
- import { randomUUID } from 'node:crypto';
4
- import * as Effect4 from 'effect/Effect';
5
- import * as Option from 'effect/Option';
6
- import { mkdirSync, stat as stat$1, unwatchFile, watchFile, watch as watch$1 } from 'node:fs';
7
- import * as sp2 from 'node:path';
8
- import { dirname, join, relative, resolve, sep } from 'node:path';
9
- import { DatabaseSync } from 'node:sqlite';
10
- import * as Config from 'effect/Config';
11
- import * as Exit from 'effect/Exit';
12
- import { spawn, execFile } from 'node:child_process';
13
- import { readFile, stat, realpath, lstat, readdir, open } from 'node:fs/promises';
14
- import * as Layer from 'effect/Layer';
15
- import 'effect/ManagedRuntime';
16
- import { type, platform } from 'node:os';
17
- import { EventEmitter } from 'node:events';
18
- import { Readable } from 'node:stream';
19
- import * as Queue from 'effect/Queue';
20
- import * as Runtime from 'effect/Runtime';
21
- import * as Stream from 'effect/Stream';
22
-
23
- // ../../packages/core/src/schemas/review.ts
24
- var ReviewId = Schema3.String.pipe(Schema3.brand("ReviewId"));
25
- var ReviewStatus = Schema3.Literal(
26
- "in_progress",
27
- "approved",
28
- "changes_requested"
29
- );
30
- var ReviewSourceType = Schema3.Literal("staged", "branch", "commits");
31
- Schema3.Struct({
32
- baseRef: Schema3.NullOr(Schema3.String),
33
- createdAt: Schema3.String,
34
- id: ReviewId,
35
- repositoryPath: Schema3.String,
36
- snapshotData: Schema3.String,
37
- sourceRef: Schema3.NullOr(Schema3.String),
38
- sourceType: ReviewSourceType,
39
- status: ReviewStatus,
40
- updatedAt: Schema3.String
41
- });
42
- Schema3.Struct({
43
- sourceRef: Schema3.optionalWith(Schema3.NullOr(Schema3.String), {
44
- default: () => null
45
- }),
46
- sourceType: Schema3.optionalWith(ReviewSourceType, {
47
- default: () => "staged"
48
- })
49
- });
50
- Schema3.Struct({
51
- status: Schema3.optionalWith(ReviewStatus, { as: "Option" })
52
- });
53
- var ReviewNotFound = class extends Schema3.TaggedError()(
54
- "ReviewNotFound",
55
- { id: ReviewId },
56
- HttpApiSchema.annotations({ status: 404 })
57
- ) {
58
- };
59
- var TodoId = Schema3.String.pipe(Schema3.brand("TodoId"));
60
- Schema3.Struct({
61
- completed: Schema3.Boolean,
62
- content: Schema3.String,
63
- createdAt: Schema3.String,
64
- id: TodoId,
65
- position: Schema3.Number,
66
- reviewId: Schema3.NullOr(ReviewId),
67
- updatedAt: Schema3.String
68
- });
69
- Schema3.Struct({
70
- content: Schema3.String.pipe(Schema3.minLength(1)),
71
- reviewId: Schema3.optionalWith(Schema3.NullOr(ReviewId), {
72
- default: () => null
73
- })
74
- });
75
- Schema3.Struct({
76
- completed: Schema3.optionalWith(Schema3.Boolean, { as: "Option" }),
77
- content: Schema3.optionalWith(Schema3.String.pipe(Schema3.minLength(1)), {
78
- as: "Option"
79
- })
80
- });
81
- var TodoNotFound = class extends Schema3.TaggedError()(
82
- "TodoNotFound",
83
- { id: TodoId },
84
- HttpApiSchema.annotations({ status: 404 })
85
- ) {
86
- };
87
-
88
- // ../../packages/core/src/db/migrations.ts
89
- var migrations = [
90
- // v1: reviews table
91
- `CREATE TABLE IF NOT EXISTS reviews (
92
- id TEXT PRIMARY KEY,
93
- repository_path TEXT NOT NULL,
94
- base_ref TEXT,
95
- snapshot_data TEXT NOT NULL,
96
- status TEXT DEFAULT 'in_progress',
97
- created_at TEXT DEFAULT (datetime('now')),
98
- updated_at TEXT DEFAULT (datetime('now'))
99
- ) STRICT`,
100
- // v2: comments table
101
- `CREATE TABLE IF NOT EXISTS comments (
102
- id TEXT PRIMARY KEY,
103
- review_id TEXT NOT NULL REFERENCES reviews(id) ON DELETE CASCADE,
104
- file_path TEXT NOT NULL,
105
- line_number INTEGER,
106
- line_type TEXT,
107
- content TEXT NOT NULL,
108
- suggestion TEXT,
109
- resolved INTEGER DEFAULT 0,
110
- created_at TEXT DEFAULT (datetime('now')),
111
- updated_at TEXT DEFAULT (datetime('now'))
112
- ) STRICT`,
113
- // v3: add source_type and source_ref to reviews
114
- `ALTER TABLE reviews ADD COLUMN source_type TEXT DEFAULT 'staged';
115
- ALTER TABLE reviews ADD COLUMN source_ref TEXT`,
116
- // v4: todos table
117
- `CREATE TABLE IF NOT EXISTS todos (
118
- id TEXT PRIMARY KEY,
119
- content TEXT NOT NULL,
120
- completed INTEGER DEFAULT 0,
121
- review_id TEXT REFERENCES reviews(id) ON DELETE CASCADE,
122
- created_at TEXT DEFAULT (datetime('now')),
123
- updated_at TEXT DEFAULT (datetime('now'))
124
- ) STRICT`,
125
- // v5: add position to todos
126
- `ALTER TABLE todos ADD COLUMN position INTEGER DEFAULT 0`,
127
- // v6: review_files table
128
- `CREATE TABLE IF NOT EXISTS review_files (
129
- id TEXT PRIMARY KEY,
130
- review_id TEXT NOT NULL REFERENCES reviews(id) ON DELETE CASCADE,
131
- file_path TEXT NOT NULL,
132
- old_path TEXT,
133
- status TEXT NOT NULL,
134
- additions INTEGER NOT NULL DEFAULT 0,
135
- deletions INTEGER NOT NULL DEFAULT 0,
136
- hunks_data TEXT,
137
- created_at TEXT DEFAULT (datetime('now'))
138
- ) STRICT`
139
- ];
140
- var runMigrations = (db) => {
141
- const currentVersion = db.prepare("PRAGMA user_version").get().user_version;
142
- for (let i = currentVersion; i < migrations.length; i++) {
143
- const statements = migrations[i].split(";").map((s) => s.trim()).filter(Boolean);
144
- for (const sql of statements) {
145
- db.exec(sql);
146
- }
147
- db.exec(`PRAGMA user_version = ${i + 1}`);
148
- }
149
- };
150
-
151
- // ../../packages/core/src/db/database.ts
152
- var withTransaction = (db, body) => Effect4.acquireUseRelease(
153
- Effect4.sync(() => db.exec("BEGIN")),
154
- () => body,
155
- (_, exit) => Effect4.sync(() => {
156
- if (Exit.isSuccess(exit)) {
157
- db.exec("COMMIT");
158
- } else {
159
- db.exec("ROLLBACK");
160
- }
161
- })
162
- );
163
- var SqliteService = class extends Effect4.Service()(
164
- "@ringi/SqliteService",
165
- {
166
- effect: Effect4.gen(function* effect() {
167
- const dbPath = yield* Config.string("DB_PATH").pipe(
168
- Config.withDefault(".ringi/reviews.db")
169
- );
170
- mkdirSync(dirname(dbPath), { recursive: true });
171
- const db = new DatabaseSync(dbPath);
172
- db.exec("PRAGMA journal_mode=WAL");
173
- db.exec("PRAGMA foreign_keys=ON");
174
- runMigrations(db);
175
- return { db };
176
- })
177
- }
178
- ) {
179
- };
180
-
181
- // ../../packages/core/src/repos/comment.repo.ts
182
- var rowToComment = (row) => ({
183
- content: row.content,
184
- createdAt: row.created_at,
185
- filePath: row.file_path,
186
- id: row.id,
187
- lineNumber: row.line_number,
188
- lineType: row.line_type,
189
- resolved: row.resolved === 1,
190
- reviewId: row.review_id,
191
- suggestion: row.suggestion,
192
- updatedAt: row.updated_at
193
- });
194
- var CommentRepo = class extends Effect4.Service()(
195
- "@ringi/CommentRepo",
196
- {
197
- dependencies: [SqliteService.Default],
198
- effect: Effect4.gen(function* effect() {
199
- const { db } = yield* SqliteService;
200
- const stmtFindById = db.prepare("SELECT * FROM comments WHERE id = ?");
201
- const stmtFindByReview = db.prepare(
202
- "SELECT * FROM comments WHERE review_id = ? ORDER BY created_at ASC"
203
- );
204
- const stmtFindByFile = db.prepare(
205
- "SELECT * FROM comments WHERE review_id = ? AND file_path = ? ORDER BY line_number ASC, created_at ASC"
206
- );
207
- const stmtInsert = db.prepare(
208
- `INSERT INTO comments (id, review_id, file_path, line_number, line_type, content, suggestion, resolved, created_at, updated_at)
209
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, datetime('now'), datetime('now'))`
210
- );
211
- const stmtDelete = db.prepare("DELETE FROM comments WHERE id = ?");
212
- const stmtDeleteByReview = db.prepare(
213
- "DELETE FROM comments WHERE review_id = ?"
214
- );
215
- const stmtSetResolved = db.prepare(
216
- "UPDATE comments SET resolved = ?, updated_at = datetime('now') WHERE id = ?"
217
- );
218
- const stmtCountByReview = db.prepare(
219
- `SELECT
220
- COUNT(*) as total,
221
- SUM(CASE WHEN resolved = 1 THEN 1 ELSE 0 END) as resolved,
222
- SUM(CASE WHEN resolved = 0 THEN 1 ELSE 0 END) as unresolved,
223
- SUM(CASE WHEN suggestion IS NOT NULL THEN 1 ELSE 0 END) as with_suggestions
224
- FROM comments WHERE review_id = ?`
225
- );
226
- const findById = (id) => Effect4.sync(() => {
227
- const row = stmtFindById.get(id);
228
- return row ? rowToComment(row) : null;
229
- });
230
- const findByReview = (reviewId) => Effect4.sync(() => {
231
- const rows = stmtFindByReview.all(
232
- reviewId
233
- );
234
- return rows.map(rowToComment);
235
- });
236
- const findByFile = (reviewId, filePath) => Effect4.sync(() => {
237
- const rows = stmtFindByFile.all(
238
- reviewId,
239
- filePath
240
- );
241
- return rows.map(rowToComment);
242
- });
243
- const create = (input) => Effect4.sync(() => {
244
- stmtInsert.run(
245
- input.id,
246
- input.reviewId,
247
- input.filePath,
248
- input.lineNumber,
249
- input.lineType,
250
- input.content,
251
- input.suggestion
252
- );
253
- return rowToComment(
254
- stmtFindById.get(input.id)
255
- );
256
- });
257
- const update = (id, updates) => Effect4.sync(() => {
258
- const setClauses = [];
259
- const params = [];
260
- if (updates.content !== void 0) {
261
- setClauses.push("content = ?");
262
- params.push(updates.content);
263
- }
264
- if (updates.suggestion !== void 0) {
265
- setClauses.push("suggestion = ?");
266
- params.push(updates.suggestion);
267
- }
268
- if (setClauses.length === 0) {
269
- const row2 = stmtFindById.get(id);
270
- return row2 ? rowToComment(row2) : null;
271
- }
272
- setClauses.push("updated_at = datetime('now')");
273
- params.push(id);
274
- db.prepare(
275
- `UPDATE comments SET ${setClauses.join(", ")} WHERE id = ?`
276
- ).run(...params);
277
- const row = stmtFindById.get(id);
278
- return row ? rowToComment(row) : null;
279
- });
280
- const setResolved = (id, resolved) => Effect4.sync(() => {
281
- stmtSetResolved.run(resolved ? 1 : 0, id);
282
- const row = stmtFindById.get(id);
283
- return row ? rowToComment(row) : null;
284
- });
285
- const remove = (id) => Effect4.sync(() => {
286
- const result = stmtDelete.run(id);
287
- return Number(result.changes) > 0;
288
- });
289
- const removeByReview = (reviewId) => Effect4.sync(() => {
290
- const result = stmtDeleteByReview.run(reviewId);
291
- return Number(result.changes);
292
- });
293
- const countByReview = (reviewId) => Effect4.sync(() => {
294
- const row = stmtCountByReview.get(reviewId);
295
- return {
296
- resolved: row.resolved,
297
- total: row.total,
298
- unresolved: row.unresolved,
299
- withSuggestions: row.with_suggestions
300
- };
301
- });
302
- return {
303
- countByReview,
304
- create,
305
- findByFile,
306
- findById,
307
- findByReview,
308
- remove,
309
- removeByReview,
310
- setResolved,
311
- update
312
- };
313
- })
314
- }
315
- ) {
316
- };
317
- var CommentId = Schema3.String.pipe(Schema3.brand("CommentId"));
318
- var LineType = Schema3.Literal("added", "removed", "context");
319
- Schema3.Struct({
320
- content: Schema3.String,
321
- createdAt: Schema3.String,
322
- filePath: Schema3.String,
323
- id: CommentId,
324
- lineNumber: Schema3.NullOr(Schema3.Number),
325
- lineType: Schema3.NullOr(LineType),
326
- resolved: Schema3.Boolean,
327
- reviewId: ReviewId,
328
- suggestion: Schema3.NullOr(Schema3.String),
329
- updatedAt: Schema3.String
330
- });
331
- Schema3.Struct({
332
- content: Schema3.String.pipe(Schema3.minLength(1)),
333
- filePath: Schema3.String.pipe(Schema3.minLength(1)),
334
- lineNumber: Schema3.optionalWith(Schema3.NullOr(Schema3.Number), {
335
- default: () => null
336
- }),
337
- lineType: Schema3.optionalWith(Schema3.NullOr(LineType), {
338
- default: () => null
339
- }),
340
- suggestion: Schema3.optionalWith(Schema3.NullOr(Schema3.String), {
341
- default: () => null
342
- })
343
- });
344
- Schema3.Struct({
345
- content: Schema3.optionalWith(Schema3.String.pipe(Schema3.minLength(1)), {
346
- as: "Option"
347
- }),
348
- suggestion: Schema3.optionalWith(Schema3.NullOr(Schema3.String), {
349
- as: "Option"
350
- })
351
- });
352
- var CommentNotFound = class extends Schema3.TaggedError()(
353
- "CommentNotFound",
354
- { id: CommentId },
355
- HttpApiSchema.annotations({ status: 404 })
356
- ) {
357
- };
358
-
359
- // ../../packages/core/src/services/comment.service.ts
360
- var CommentService = class extends Effect4.Service()(
361
- "@ringi/CommentService",
362
- {
363
- dependencies: [CommentRepo.Default],
364
- effect: Effect4.sync(() => {
365
- const create = Effect4.fn("CommentService.create")(function* create2(reviewId, input) {
366
- const repo = yield* CommentRepo;
367
- const id = randomUUID();
368
- return yield* repo.create({
369
- content: input.content,
370
- filePath: input.filePath,
371
- id,
372
- lineNumber: input.lineNumber,
373
- lineType: input.lineType,
374
- reviewId,
375
- suggestion: input.suggestion
376
- });
377
- });
378
- const getById = Effect4.fn("CommentService.getById")(function* getById2(id) {
379
- const repo = yield* CommentRepo;
380
- const comment = yield* repo.findById(id);
381
- if (!comment) {
382
- return yield* new CommentNotFound({ id });
383
- }
384
- return comment;
385
- });
386
- const getByReview = Effect4.fn("CommentService.getByReview")(
387
- function* getByReview2(reviewId) {
388
- const repo = yield* CommentRepo;
389
- return yield* repo.findByReview(reviewId);
390
- }
391
- );
392
- const getByFile = Effect4.fn("CommentService.getByFile")(
393
- function* getByFile2(reviewId, filePath) {
394
- const repo = yield* CommentRepo;
395
- return yield* repo.findByFile(reviewId, filePath);
396
- }
397
- );
398
- const update = Effect4.fn("CommentService.update")(function* update2(id, input) {
399
- const repo = yield* CommentRepo;
400
- const updates = {};
401
- if (Option.isSome(input.content)) {
402
- updates.content = input.content.value;
403
- }
404
- if (Option.isSome(input.suggestion)) {
405
- updates.suggestion = input.suggestion.value;
406
- }
407
- const comment = yield* repo.update(id, updates);
408
- if (!comment) {
409
- return yield* new CommentNotFound({ id });
410
- }
411
- return comment;
412
- });
413
- const resolve3 = Effect4.fn("CommentService.resolve")(function* resolve4(id) {
414
- const repo = yield* CommentRepo;
415
- const comment = yield* repo.setResolved(id, true);
416
- if (!comment) {
417
- return yield* new CommentNotFound({ id });
418
- }
419
- return comment;
420
- });
421
- const unresolve = Effect4.fn("CommentService.unresolve")(
422
- function* unresolve2(id) {
423
- const repo = yield* CommentRepo;
424
- const comment = yield* repo.setResolved(id, false);
425
- if (!comment) {
426
- return yield* new CommentNotFound({ id });
427
- }
428
- return comment;
429
- }
430
- );
431
- const remove = Effect4.fn("CommentService.remove")(function* remove2(id) {
432
- const repo = yield* CommentRepo;
433
- const existed = yield* repo.remove(id);
434
- if (!existed) {
435
- return yield* new CommentNotFound({ id });
436
- }
437
- return { success: true };
438
- });
439
- const getStats = Effect4.fn("CommentService.getStats")(function* getStats2(reviewId) {
440
- const repo = yield* CommentRepo;
441
- return yield* repo.countByReview(reviewId);
442
- });
443
- return {
444
- create,
445
- getByFile,
446
- getById,
447
- getByReview,
448
- getStats,
449
- remove,
450
- resolve: resolve3,
451
- unresolve,
452
- update
453
- };
454
- })
455
- }
456
- ) {
457
- };
458
-
459
- // ../../packages/core/src/services/diff.service.ts
460
- var HUNK_HEADER = /@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
461
- var splitIntoFiles = (diffText) => {
462
- const files = [];
463
- const lines2 = diffText.split("\n");
464
- let current = [];
465
- for (const line of lines2) {
466
- if (line.startsWith("diff --git")) {
467
- if (current.length > 0) {
468
- files.push(current.join("\n"));
469
- }
470
- current = [line];
471
- } else {
472
- current.push(line);
473
- }
474
- }
475
- if (current.length > 0) {
476
- files.push(current.join("\n"));
477
- }
478
- return files;
479
- };
480
- var parseHunks = (lines2) => {
481
- const hunks = [];
482
- let currentHunk = null;
483
- let oldLineNum = 0;
484
- let newLineNum = 0;
485
- for (const line of lines2) {
486
- const match = line.match(HUNK_HEADER);
487
- if (match) {
488
- if (currentHunk) {
489
- hunks.push(currentHunk);
490
- }
491
- const oldStart = Number.parseInt(match[1], 10);
492
- const oldLines = Number.parseInt(match[2] ?? "1", 10);
493
- const newStart = Number.parseInt(match[3], 10);
494
- const newLines = Number.parseInt(match[4] ?? "1", 10);
495
- currentHunk = { lines: [], newLines, newStart, oldLines, oldStart };
496
- oldLineNum = oldStart;
497
- newLineNum = newStart;
498
- continue;
499
- }
500
- if (!currentHunk) {
501
- continue;
502
- }
503
- if (line.startsWith("+") && !line.startsWith("+++")) {
504
- currentHunk.lines.push({
505
- content: line.slice(1),
506
- newLineNumber: newLineNum++,
507
- oldLineNumber: null,
508
- type: "added"
509
- });
510
- } else if (line.startsWith("-") && !line.startsWith("---")) {
511
- currentHunk.lines.push({
512
- content: line.slice(1),
513
- newLineNumber: null,
514
- oldLineNumber: oldLineNum++,
515
- type: "removed"
516
- });
517
- } else if (line.startsWith(" ")) {
518
- currentHunk.lines.push({
519
- content: line.slice(1),
520
- newLineNumber: newLineNum++,
521
- oldLineNumber: oldLineNum++,
522
- type: "context"
523
- });
524
- }
525
- }
526
- if (currentHunk) {
527
- hunks.push(currentHunk);
528
- }
529
- return hunks;
530
- };
531
- var parseFileDiff = (fileDiff) => {
532
- const lines2 = fileDiff.split("\n");
533
- const diffLine = lines2.find((l) => l.startsWith("diff --git"));
534
- if (!diffLine) {
535
- return null;
536
- }
537
- const pathMatch = diffLine.match(/diff --git a\/(.+) b\/(.+)/);
538
- if (!pathMatch) {
539
- return null;
540
- }
541
- const oldPath = pathMatch[1];
542
- const newPath = pathMatch[2];
543
- let status = "modified";
544
- if (lines2.some((l) => l.startsWith("deleted file mode"))) {
545
- status = "deleted";
546
- } else if (lines2.some((l) => l.startsWith("new file mode"))) {
547
- status = "added";
548
- } else if (lines2.some((l) => l.startsWith("rename from")) || oldPath !== newPath) {
549
- status = "renamed";
550
- }
551
- const hunks = parseHunks(lines2);
552
- let additions = 0;
553
- let deletions = 0;
554
- for (const hunk of hunks) {
555
- for (const line of hunk.lines) {
556
- if (line.type === "added") {
557
- additions++;
558
- } else if (line.type === "removed") {
559
- deletions++;
560
- }
561
- }
562
- }
563
- return { additions, deletions, hunks, newPath, oldPath, status };
564
- };
565
- var parseDiff = (diffText) => {
566
- if (!diffText.trim()) {
567
- return [];
568
- }
569
- const blocks = splitIntoFiles(diffText);
570
- const files = [];
571
- for (const block of blocks) {
572
- const parsed = parseFileDiff(block);
573
- if (parsed) {
574
- files.push(parsed);
575
- }
576
- }
577
- return files;
578
- };
579
- var getDiffSummary = (files) => {
580
- let totalAdditions = 0;
581
- let totalDeletions = 0;
582
- for (const file of files) {
583
- totalAdditions += file.additions;
584
- totalDeletions += file.deletions;
585
- }
586
- return {
587
- filesAdded: files.filter((f) => f.status === "added").length,
588
- filesDeleted: files.filter((f) => f.status === "deleted").length,
589
- filesModified: files.filter((f) => f.status === "modified").length,
590
- filesRenamed: files.filter((f) => f.status === "renamed").length,
591
- totalAdditions,
592
- totalDeletions,
593
- totalFiles: files.length
594
- };
595
- };
596
- var GitError = class extends Schema3.TaggedError()(
597
- "GitError",
598
- { message: Schema3.String },
599
- HttpApiSchema.annotations({ status: 500 })
600
- ) {
601
- };
602
- var MAX_STDOUT_BYTES = 200 * 1024 * 1024;
603
- var execGit = (args, repoPath) => Effect4.tryPromise({
604
- catch: (error) => new GitError({ message: String(error) }),
605
- try: () => new Promise((resolve3, reject) => {
606
- const child = spawn("git", [...args], { cwd: repoPath });
607
- const chunks = [];
608
- let bytes = 0;
609
- let truncated = false;
610
- child.stdout.on("data", (chunk) => {
611
- if (truncated) {
612
- return;
613
- }
614
- bytes += chunk.length;
615
- if (bytes > MAX_STDOUT_BYTES) {
616
- truncated = true;
617
- child.kill();
618
- return;
619
- }
620
- chunks.push(chunk);
621
- });
622
- let stderr = "";
623
- child.stderr.on("data", (chunk) => {
624
- stderr += chunk.toString();
625
- });
626
- child.on("error", reject);
627
- child.on("close", (code) => {
628
- if (truncated) {
629
- resolve3(Buffer.concat(chunks).toString("utf8"));
630
- return;
631
- }
632
- if (code !== 0) {
633
- reject(
634
- new Error(`git ${args[0]} exited with code ${code}: ${stderr}`)
635
- );
636
- } else {
637
- resolve3(Buffer.concat(chunks).toString("utf8"));
638
- }
639
- });
640
- })
641
- });
642
- var lines = (output) => output.trim().split("\n").filter(Boolean);
643
- var parseNameStatus = (output) => lines(output).map((line) => {
644
- const [status, ...rest] = line.split(" ");
645
- return { path: rest.join(" "), status };
646
- });
647
- var GitService = class extends Effect4.Service()(
648
- "@ringi/GitService",
649
- {
650
- effect: Effect4.gen(function* effect() {
651
- const repoPath = yield* Config.string("REPOSITORY_PATH").pipe(
652
- Config.withDefault(process.cwd())
653
- );
654
- const hasCommits = execGit(["rev-parse", "HEAD"], repoPath).pipe(
655
- Effect4.as(true),
656
- Effect4.catchTag("GitError", () => Effect4.succeed(false)),
657
- Effect4.withSpan("GitService.hasCommits")
658
- );
659
- const getRepositoryInfo = Effect4.gen(function* getRepositoryInfo2() {
660
- const name = yield* execGit(
661
- ["rev-parse", "--show-toplevel"],
662
- repoPath
663
- ).pipe(Effect4.map((s) => s.trim().split("/").pop() ?? "unknown"));
664
- const branch = yield* execGit(
665
- ["rev-parse", "--abbrev-ref", "HEAD"],
666
- repoPath
667
- ).pipe(Effect4.map((s) => s.trim()));
668
- const remote = yield* execGit(
669
- ["config", "--get", "remote.origin.url"],
670
- repoPath
671
- ).pipe(
672
- Effect4.map((s) => s.trim() || null),
673
- Effect4.catchTag("GitError", () => Effect4.succeed(null))
674
- );
675
- return { branch, name, path: repoPath, remote };
676
- }).pipe(Effect4.withSpan("GitService.getRepositoryInfo"));
677
- const getStagedDiff = execGit(
678
- ["diff", "--cached", "--no-color", "--unified=3"],
679
- repoPath
680
- ).pipe(Effect4.withSpan("GitService.getStagedDiff"));
681
- const getUncommittedDiff = hasCommits.pipe(
682
- Effect4.flatMap(
683
- (has) => has ? execGit(["diff", "HEAD", "--no-color", "--unified=3"], repoPath) : Effect4.succeed("")
684
- ),
685
- Effect4.withSpan("GitService.getUncommittedDiff")
686
- );
687
- const getUnstagedDiff = execGit(
688
- ["diff", "--no-color", "--unified=3"],
689
- repoPath
690
- ).pipe(Effect4.withSpan("GitService.getUnstagedDiff"));
691
- const getLastCommitDiff = hasCommits.pipe(
692
- Effect4.flatMap(
693
- (has) => has ? execGit(
694
- ["show", "HEAD", "--format=", "--no-color", "--unified=3"],
695
- repoPath
696
- ) : Effect4.succeed("")
697
- ),
698
- Effect4.withSpan("GitService.getLastCommitDiff")
699
- );
700
- const getBranchDiff = Effect4.fn("GitService.getBranchDiff")(
701
- function* getBranchDiff2(branch) {
702
- return yield* execGit(
703
- ["diff", `${branch}...HEAD`, "--no-color", "--unified=3"],
704
- repoPath
705
- );
706
- }
707
- );
708
- const getCommitDiff = Effect4.fn("GitService.getCommitDiff")(
709
- function* getCommitDiff2(shas) {
710
- if (shas.length === 1) {
711
- return yield* execGit(
712
- ["show", shas[0], "--format=", "--no-color", "--unified=3"],
713
- repoPath
714
- );
715
- }
716
- const first = shas.at(-1);
717
- const last = shas[0];
718
- return yield* execGit(
719
- ["diff", `${first}~1..${last}`, "--no-color", "--unified=3"],
720
- repoPath
721
- );
722
- }
723
- );
724
- const getStagedFiles = execGit(
725
- ["diff", "--cached", "--name-status"],
726
- repoPath
727
- ).pipe(
728
- Effect4.map(parseNameStatus),
729
- Effect4.withSpan("GitService.getStagedFiles")
730
- );
731
- const getUncommittedFiles = hasCommits.pipe(
732
- Effect4.flatMap(
733
- (has) => has ? execGit(["diff", "HEAD", "--name-status"], repoPath).pipe(
734
- Effect4.map(parseNameStatus)
735
- ) : Effect4.succeed([])
736
- ),
737
- Effect4.withSpan("GitService.getUncommittedFiles")
738
- );
739
- const getUnstagedFiles = execGit(
740
- ["diff", "--name-status"],
741
- repoPath
742
- ).pipe(
743
- Effect4.map(parseNameStatus),
744
- Effect4.withSpan("GitService.getUnstagedFiles")
745
- );
746
- const getLastCommitFiles = hasCommits.pipe(
747
- Effect4.flatMap(
748
- (has) => has ? execGit(
749
- ["show", "HEAD", "--format=", "--name-status"],
750
- repoPath
751
- ).pipe(Effect4.map(parseNameStatus)) : Effect4.succeed([])
752
- ),
753
- Effect4.withSpan("GitService.getLastCommitFiles")
754
- );
755
- const getFileContent = Effect4.fn("GitService.getFileContent")(
756
- function* getFileContent2(filePath, version) {
757
- switch (version) {
758
- case "staged": {
759
- return yield* execGit(["show", `:${filePath}`], repoPath);
760
- }
761
- case "head": {
762
- return yield* execGit(["show", `HEAD:${filePath}`], repoPath);
763
- }
764
- case "working":
765
- default: {
766
- return yield* Effect4.tryPromise({
767
- catch: (error) => new GitError({
768
- message: `Failed to read ${filePath}: ${String(error)}`
769
- }),
770
- try: () => readFile(join(repoPath, filePath), "utf8")
771
- });
772
- }
773
- }
774
- }
775
- );
776
- const getFileTree = Effect4.fn("GitService.getFileTree")(
777
- function* getFileTree2(ref) {
778
- return yield* execGit(
779
- ["ls-tree", "-r", "--name-only", ref],
780
- repoPath
781
- ).pipe(Effect4.map(lines));
782
- }
783
- );
784
- const getBranches = execGit(
785
- ["branch", "--format=%(refname:short) %(HEAD)"],
786
- repoPath
787
- ).pipe(
788
- Effect4.map(
789
- (output) => lines(output).map((line) => {
790
- const [name, head] = line.split(" ");
791
- return { current: head === "*", name };
792
- })
793
- ),
794
- Effect4.withSpan("GitService.getBranches")
795
- );
796
- const getCommits = Effect4.fn("GitService.getCommits")(
797
- function* getCommits2(opts) {
798
- const limit = (opts.limit ?? 20) + 1;
799
- const args = [
800
- "log",
801
- `--max-count=${limit}`,
802
- `--skip=${opts.offset ?? 0}`,
803
- "--format=%H %s %an %aI"
804
- ];
805
- if (opts.search) {
806
- args.push(`--grep=${opts.search}`, "-i");
807
- }
808
- const output = yield* execGit(args, repoPath);
809
- const rows = lines(output);
810
- const hasMore = rows.length === limit;
811
- const commits = (hasMore ? rows.slice(0, -1) : rows).map((line) => {
812
- const [hash, message, author, date] = line.split(" ");
813
- return {
814
- author,
815
- date,
816
- hash,
817
- message
818
- };
819
- });
820
- return { commits, hasMore };
821
- }
822
- );
823
- const stageFiles = Effect4.fn("GitService.stageFiles")(
824
- function* stageFiles2(files) {
825
- return yield* execGit(["add", "--", ...files], repoPath).pipe(
826
- Effect4.as(files)
827
- );
828
- }
829
- );
830
- const stageAll = execGit(["add", "-A"], repoPath).pipe(
831
- Effect4.flatMap(() => getStagedFiles),
832
- Effect4.map((files) => files.map((f) => f.path)),
833
- Effect4.withSpan("GitService.stageAll")
834
- );
835
- const unstageFiles = Effect4.fn("GitService.unstageFiles")(
836
- function* unstageFiles2(files) {
837
- return yield* execGit(
838
- ["reset", "HEAD", "--", ...files],
839
- repoPath
840
- ).pipe(Effect4.as(files));
841
- }
842
- );
843
- const getRepositoryPath = execGit(
844
- ["rev-parse", "--show-toplevel"],
845
- repoPath
846
- ).pipe(
847
- Effect4.map((s) => s.trim()),
848
- Effect4.withSpan("GitService.getRepositoryPath")
849
- );
850
- return {
851
- getBranchDiff,
852
- getBranches,
853
- getCommitDiff,
854
- getCommits,
855
- getFileContent,
856
- getFileTree,
857
- getLastCommitDiff,
858
- getLastCommitFiles,
859
- getRepositoryInfo,
860
- getRepositoryPath,
861
- getStagedDiff,
862
- getStagedFiles,
863
- getUncommittedDiff,
864
- getUncommittedFiles,
865
- getUnstagedDiff,
866
- getUnstagedFiles,
867
- hasCommits,
868
- stageAll,
869
- stageFiles,
870
- unstageFiles
871
- };
872
- })
873
- }
874
- ) {
875
- };
876
- var parseHunks2 = (hunksData) => hunksData == null ? Effect4.succeed([]) : Effect4.try(() => JSON.parse(hunksData)).pipe(
877
- Effect4.orElseSucceed(() => [])
878
- );
879
- var serializeHunks = (hunks) => JSON.stringify(hunks);
880
- var ReviewFileRepo = class extends Effect4.Service()(
881
- "@ringi/ReviewFileRepo",
882
- {
883
- dependencies: [SqliteService.Default],
884
- effect: Effect4.gen(function* effect() {
885
- const { db } = yield* SqliteService;
886
- const stmtFindByReview = db.prepare(
887
- `SELECT id, review_id, file_path, old_path, status, additions, deletions, created_at
888
- FROM review_files WHERE review_id = ? ORDER BY file_path`
889
- );
890
- const stmtFindByReviewAndPath = db.prepare(
891
- "SELECT * FROM review_files WHERE review_id = ? AND file_path = ?"
892
- );
893
- const stmtInsert = db.prepare(
894
- `INSERT INTO review_files (id, review_id, file_path, old_path, status, additions, deletions, hunks_data, created_at)
895
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`
896
- );
897
- const stmtDeleteByReview = db.prepare(
898
- "DELETE FROM review_files WHERE review_id = ?"
899
- );
900
- const stmtCountByReview = db.prepare(
901
- "SELECT COUNT(*) as count FROM review_files WHERE review_id = ?"
902
- );
903
- const findByReview = (reviewId) => Effect4.sync(
904
- () => stmtFindByReview.all(reviewId)
905
- );
906
- const findByReviewAndPath = (reviewId, filePath) => Effect4.sync(() => {
907
- const row = stmtFindByReviewAndPath.get(
908
- reviewId,
909
- filePath
910
- );
911
- return row ?? null;
912
- });
913
- const createBulk = (files) => withTransaction(
914
- db,
915
- Effect4.sync(() => {
916
- for (const f of files) {
917
- stmtInsert.run(
918
- randomUUID(),
919
- f.reviewId,
920
- f.filePath,
921
- f.oldPath,
922
- f.status,
923
- f.additions,
924
- f.deletions,
925
- f.hunksData
926
- );
927
- }
928
- })
929
- );
930
- const deleteByReview = (reviewId) => Effect4.sync(() => {
931
- const result = stmtDeleteByReview.run(reviewId);
932
- return Number(result.changes);
933
- });
934
- const countByReview = (reviewId) => Effect4.sync(() => {
935
- const row = stmtCountByReview.get(reviewId);
936
- return row.count;
937
- });
938
- return {
939
- countByReview,
940
- createBulk,
941
- deleteByReview,
942
- findByReview,
943
- findByReviewAndPath
944
- };
945
- })
946
- }
947
- ) {
948
- };
949
- var rowToReview = (row) => ({
950
- baseRef: row.base_ref,
951
- createdAt: row.created_at,
952
- id: row.id,
953
- repositoryPath: row.repository_path,
954
- snapshotData: row.snapshot_data,
955
- sourceRef: row.source_ref,
956
- sourceType: row.source_type,
957
- status: row.status,
958
- updatedAt: row.updated_at
959
- });
960
- var ReviewRepo = class extends Effect4.Service()(
961
- "@ringi/ReviewRepo",
962
- {
963
- dependencies: [SqliteService.Default],
964
- effect: Effect4.gen(function* effect() {
965
- const { db } = yield* SqliteService;
966
- const stmtFindById = db.prepare("SELECT * FROM reviews WHERE id = ?");
967
- const stmtInsert = db.prepare(
968
- `INSERT INTO reviews (id, repository_path, base_ref, source_type, source_ref, snapshot_data, status, created_at, updated_at)
969
- VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`
970
- );
971
- const stmtUpdate = db.prepare(
972
- `UPDATE reviews SET status = COALESCE(?, status), updated_at = datetime('now') WHERE id = ?`
973
- );
974
- const stmtDelete = db.prepare("DELETE FROM reviews WHERE id = ?");
975
- const stmtCountAll = db.prepare("SELECT COUNT(*) as count FROM reviews");
976
- const stmtCountByStatus = db.prepare(
977
- "SELECT COUNT(*) as count FROM reviews WHERE status = ?"
978
- );
979
- const findById = (id) => Effect4.sync(() => {
980
- const row = stmtFindById.get(id);
981
- return row ? rowToReview(row) : null;
982
- });
983
- const findAll = (opts = {}) => Effect4.sync(() => {
984
- const conditions = [];
985
- const params = [];
986
- if (opts.status != null) {
987
- conditions.push("status = ?");
988
- params.push(opts.status);
989
- }
990
- if (opts.repositoryPath != null) {
991
- conditions.push("repository_path = ?");
992
- params.push(opts.repositoryPath);
993
- }
994
- if (opts.sourceType != null) {
995
- conditions.push("source_type = ?");
996
- params.push(opts.sourceType);
997
- }
998
- const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
999
- const page = opts.page ?? 1;
1000
- const pageSize = opts.pageSize ?? 20;
1001
- const offset = (page - 1) * pageSize;
1002
- const totalRow = db.prepare(`SELECT COUNT(*) as count FROM reviews${where}`).get(
1003
- ...params
1004
- );
1005
- const rows = db.prepare(
1006
- `SELECT * FROM reviews${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`
1007
- ).all(
1008
- ...params,
1009
- pageSize,
1010
- offset
1011
- );
1012
- return { data: rows.map(rowToReview), total: totalRow.count };
1013
- });
1014
- const create = (input) => Effect4.sync(() => {
1015
- stmtInsert.run(
1016
- input.id,
1017
- input.repositoryPath,
1018
- input.baseRef,
1019
- input.sourceType,
1020
- input.sourceRef,
1021
- input.snapshotData,
1022
- input.status
1023
- );
1024
- return rowToReview(
1025
- stmtFindById.get(input.id)
1026
- );
1027
- });
1028
- const update = (id, status) => Effect4.sync(() => {
1029
- stmtUpdate.run(status, id);
1030
- const row = stmtFindById.get(id);
1031
- return row ? rowToReview(row) : null;
1032
- });
1033
- const remove = (id) => Effect4.sync(() => {
1034
- const result = stmtDelete.run(id);
1035
- return Number(result.changes) > 0;
1036
- });
1037
- const countAll = () => Effect4.sync(() => {
1038
- const row = stmtCountAll.get();
1039
- return row.count;
1040
- });
1041
- const countByStatus = (status) => Effect4.sync(() => {
1042
- const row = stmtCountByStatus.get(status);
1043
- return row.count;
1044
- });
1045
- return {
1046
- countAll,
1047
- countByStatus,
1048
- create,
1049
- findAll,
1050
- findById,
1051
- remove,
1052
- update
1053
- };
1054
- })
1055
- }
1056
- ) {
1057
- };
1058
-
1059
- // ../../packages/core/src/services/review.service.ts
1060
- var ReviewError = class extends Schema3.TaggedError()(
1061
- "ReviewError",
1062
- { code: Schema3.String, message: Schema3.String },
1063
- HttpApiSchema.annotations({ status: 400 })
1064
- ) {
1065
- };
1066
- var getHeadSha = (repoPath) => Effect4.tryPromise({
1067
- catch: () => new ReviewError({ code: "GIT_ERROR", message: "Failed to get HEAD" }),
1068
- try: () => new Promise((resolve3, reject) => {
1069
- execFile(
1070
- "git",
1071
- ["rev-parse", "HEAD"],
1072
- { cwd: repoPath },
1073
- (err, stdout) => {
1074
- if (err) {
1075
- reject(err);
1076
- } else {
1077
- resolve3(stdout.trim());
1078
- }
1079
- }
1080
- );
1081
- })
1082
- });
1083
- var parseSnapshotData = (s) => Effect4.try(() => JSON.parse(s)).pipe(
1084
- Effect4.orElseSucceed(() => ({}))
1085
- );
1086
- var ReviewService = class extends Effect4.Service()(
1087
- "@ringi/ReviewService",
1088
- {
1089
- dependencies: [
1090
- ReviewRepo.Default,
1091
- ReviewFileRepo.Default,
1092
- GitService.Default
1093
- ],
1094
- effect: Effect4.sync(() => {
1095
- const create = Effect4.fn("ReviewService.create")(function* create2(input) {
1096
- const git = yield* GitService;
1097
- const repo = yield* ReviewRepo;
1098
- const fileRepo = yield* ReviewFileRepo;
1099
- const repoPath = yield* git.getRepositoryPath;
1100
- const hasCommitsResult = yield* git.hasCommits;
1101
- if (!hasCommitsResult) {
1102
- return yield* new ReviewError({
1103
- code: "NO_COMMITS",
1104
- message: "Repository has no commits"
1105
- });
1106
- }
1107
- let diffText;
1108
- let baseRef = null;
1109
- const { sourceType, sourceRef } = input;
1110
- switch (sourceType) {
1111
- case "staged": {
1112
- diffText = yield* git.getStagedDiff;
1113
- if (!diffText.trim()) {
1114
- return yield* new ReviewError({
1115
- code: "NO_STAGED_CHANGES",
1116
- message: "No staged changes"
1117
- });
1118
- }
1119
- baseRef = yield* getHeadSha(repoPath);
1120
- break;
1121
- }
1122
- case "branch": {
1123
- if (!sourceRef) {
1124
- return yield* new ReviewError({
1125
- code: "INVALID_SOURCE",
1126
- message: "Branch name required"
1127
- });
1128
- }
1129
- diffText = yield* git.getBranchDiff(sourceRef);
1130
- baseRef = sourceRef;
1131
- break;
1132
- }
1133
- case "commits": {
1134
- if (!sourceRef) {
1135
- return yield* new ReviewError({
1136
- code: "INVALID_SOURCE",
1137
- message: "Commit SHAs required"
1138
- });
1139
- }
1140
- const shas = sourceRef.split(",").map((s) => s.trim()).filter(Boolean);
1141
- if (shas.length === 0) {
1142
- return yield* new ReviewError({
1143
- code: "INVALID_SOURCE",
1144
- message: "No valid commit SHAs"
1145
- });
1146
- }
1147
- diffText = yield* git.getCommitDiff(shas);
1148
- baseRef = shas.at(-1) ?? null;
1149
- break;
1150
- }
1151
- default: {
1152
- return yield* new ReviewError({
1153
- code: "INVALID_SOURCE",
1154
- message: "Unsupported review source"
1155
- });
1156
- }
1157
- }
1158
- const files = parseDiff(diffText);
1159
- if (files.length === 0) {
1160
- return yield* new ReviewError({
1161
- code: "NO_CHANGES",
1162
- message: "No changes found"
1163
- });
1164
- }
1165
- const repoInfo = yield* git.getRepositoryInfo;
1166
- const reviewId = crypto.randomUUID();
1167
- const storeHunks = sourceType === "staged";
1168
- const fileInputs = files.map((f) => ({
1169
- additions: f.additions,
1170
- deletions: f.deletions,
1171
- filePath: f.newPath,
1172
- hunksData: storeHunks ? serializeHunks(f.hunks) : null,
1173
- oldPath: f.oldPath !== f.newPath ? f.oldPath : null,
1174
- reviewId,
1175
- status: f.status
1176
- }));
1177
- const snapshotData = JSON.stringify({
1178
- repository: repoInfo,
1179
- version: 2
1180
- });
1181
- const review = yield* repo.create({
1182
- baseRef,
1183
- id: reviewId,
1184
- repositoryPath: repoPath,
1185
- snapshotData,
1186
- sourceRef: sourceRef ?? null,
1187
- sourceType,
1188
- status: "in_progress"
1189
- });
1190
- yield* fileRepo.createBulk(fileInputs);
1191
- return review;
1192
- });
1193
- const list = Effect4.fn("ReviewService.list")(function* list2(opts) {
1194
- const repo = yield* ReviewRepo;
1195
- const fileRepo = yield* ReviewFileRepo;
1196
- const page = opts.page ?? 1;
1197
- const pageSize = opts.pageSize ?? 20;
1198
- const result = yield* repo.findAll({
1199
- page,
1200
- pageSize,
1201
- repositoryPath: opts.repositoryPath,
1202
- sourceType: opts.sourceType,
1203
- status: opts.status
1204
- });
1205
- const reviews = [];
1206
- for (const review of result.data) {
1207
- const fileCount = yield* fileRepo.countByReview(review.id);
1208
- const snapshot = yield* parseSnapshotData(review.snapshotData);
1209
- reviews.push({
1210
- ...review,
1211
- fileCount,
1212
- repository: snapshot.repository ?? null
1213
- });
1214
- }
1215
- return {
1216
- hasMore: page * pageSize < result.total,
1217
- page,
1218
- pageSize,
1219
- reviews,
1220
- total: result.total
1221
- };
1222
- });
1223
- const getById = Effect4.fn("ReviewService.getById")(function* getById2(id) {
1224
- const repo = yield* ReviewRepo;
1225
- const fileRepo = yield* ReviewFileRepo;
1226
- const review = yield* repo.findById(id);
1227
- if (!review) {
1228
- return yield* new ReviewNotFound({ id });
1229
- }
1230
- const fileRows = yield* fileRepo.findByReview(id);
1231
- const files = fileRows.map((r) => ({
1232
- additions: r.additions,
1233
- deletions: r.deletions,
1234
- filePath: r.file_path,
1235
- id: r.id,
1236
- oldPath: r.old_path,
1237
- status: r.status
1238
- }));
1239
- const snapshot = yield* parseSnapshotData(review.snapshotData);
1240
- const summary = getDiffSummary(
1241
- files.map((f) => ({
1242
- additions: f.additions,
1243
- deletions: f.deletions,
1244
- hunks: [],
1245
- newPath: f.filePath,
1246
- oldPath: f.oldPath ?? f.filePath,
1247
- status: f.status
1248
- }))
1249
- );
1250
- return {
1251
- ...review,
1252
- files,
1253
- repository: snapshot.repository ?? null,
1254
- summary
1255
- };
1256
- });
1257
- const getFileHunks = Effect4.fn("ReviewService.getFileHunks")(
1258
- function* getFileHunks2(reviewId, filePath) {
1259
- const repo = yield* ReviewRepo;
1260
- const fileRepo = yield* ReviewFileRepo;
1261
- const git = yield* GitService;
1262
- const review = yield* repo.findById(reviewId);
1263
- if (!review) {
1264
- return yield* new ReviewNotFound({ id: reviewId });
1265
- }
1266
- const fileRecord = yield* fileRepo.findByReviewAndPath(
1267
- reviewId,
1268
- filePath
1269
- );
1270
- if (fileRecord?.hunks_data) {
1271
- return yield* parseHunks2(fileRecord.hunks_data);
1272
- }
1273
- if (review.sourceType === "branch" && review.sourceRef) {
1274
- const diff = yield* git.getBranchDiff(review.sourceRef);
1275
- const diffFiles = parseDiff(diff);
1276
- const file = diffFiles.find((f) => f.newPath === filePath);
1277
- return file?.hunks ?? [];
1278
- }
1279
- if (review.sourceType === "commits" && review.sourceRef) {
1280
- const shas = review.sourceRef.split(",").map((s) => s.trim());
1281
- const diff = yield* git.getCommitDiff(shas);
1282
- const diffFiles = parseDiff(diff);
1283
- const file = diffFiles.find((f) => f.newPath === filePath);
1284
- return file?.hunks ?? [];
1285
- }
1286
- const snapshot = yield* parseSnapshotData(review.snapshotData);
1287
- if (snapshot.files) {
1288
- const legacyFile = snapshot.files.find(
1289
- (f) => f.newPath === filePath
1290
- );
1291
- return legacyFile?.hunks ?? [];
1292
- }
1293
- return [];
1294
- }
1295
- );
1296
- const update = Effect4.fn("ReviewService.update")(function* update2(id, input) {
1297
- const repo = yield* ReviewRepo;
1298
- const existing = yield* repo.findById(id);
1299
- if (!existing) {
1300
- return yield* new ReviewNotFound({ id });
1301
- }
1302
- const status = Option.getOrNull(input.status);
1303
- const review = yield* repo.update(id, status);
1304
- if (!review) {
1305
- return yield* new ReviewNotFound({ id });
1306
- }
1307
- return review;
1308
- });
1309
- const remove = Effect4.fn("ReviewService.remove")(function* remove2(id) {
1310
- const repo = yield* ReviewRepo;
1311
- const fileRepo = yield* ReviewFileRepo;
1312
- const existing = yield* repo.findById(id);
1313
- if (!existing) {
1314
- return yield* new ReviewNotFound({ id });
1315
- }
1316
- yield* fileRepo.deleteByReview(id);
1317
- yield* repo.remove(id);
1318
- return { success: true };
1319
- });
1320
- const getStats = Effect4.fn("ReviewService.getStats")(
1321
- function* getStats2() {
1322
- const repo = yield* ReviewRepo;
1323
- const total = yield* repo.countAll();
1324
- const inProgress = yield* repo.countByStatus("in_progress");
1325
- const approved = yield* repo.countByStatus("approved");
1326
- const changesRequested = yield* repo.countByStatus("changes_requested");
1327
- return { approved, changesRequested, inProgress, total };
1328
- }
1329
- );
1330
- return {
1331
- create,
1332
- getById,
1333
- getFileHunks,
1334
- getStats,
1335
- list,
1336
- remove,
1337
- update
1338
- };
1339
- })
1340
- }
1341
- ) {
1342
- };
1343
- var rowToTodo = (row) => ({
1344
- completed: row.completed === 1,
1345
- content: row.content,
1346
- createdAt: row.created_at,
1347
- id: row.id,
1348
- position: row.position,
1349
- reviewId: row.review_id,
1350
- updatedAt: row.updated_at
1351
- });
1352
- var TodoRepo = class extends Effect4.Service()("@ringi/TodoRepo", {
1353
- dependencies: [SqliteService.Default],
1354
- effect: Effect4.gen(function* effect() {
1355
- const { db } = yield* SqliteService;
1356
- const stmtFindById = db.prepare("SELECT * FROM todos WHERE id = ?");
1357
- const stmtInsert = db.prepare(
1358
- `INSERT INTO todos (id, content, completed, review_id, position, created_at, updated_at)
1359
- VALUES (?, ?, 0, ?, ?, datetime('now'), datetime('now'))`
1360
- );
1361
- const stmtDelete = db.prepare("DELETE FROM todos WHERE id = ?");
1362
- const stmtDeleteCompleted = db.prepare(
1363
- "DELETE FROM todos WHERE completed = 1"
1364
- );
1365
- const stmtNextPosition = db.prepare(
1366
- "SELECT COALESCE(MAX(position), -1) + 1 AS next_pos FROM todos"
1367
- );
1368
- const stmtCountAll = db.prepare("SELECT COUNT(*) as count FROM todos");
1369
- const stmtCountCompleted = db.prepare(
1370
- "SELECT COUNT(*) as count FROM todos WHERE completed = 1"
1371
- );
1372
- const stmtCountPending = db.prepare(
1373
- "SELECT COUNT(*) as count FROM todos WHERE completed = 0"
1374
- );
1375
- const findById = (id) => Effect4.sync(() => {
1376
- const row = stmtFindById.get(id);
1377
- return row ? rowToTodo(row) : null;
1378
- });
1379
- const findAll = (opts = {}) => Effect4.sync(() => {
1380
- const conditions = [];
1381
- const params = [];
1382
- if (opts.reviewId != null) {
1383
- conditions.push("review_id = ?");
1384
- params.push(opts.reviewId);
1385
- }
1386
- if (opts.completed != null) {
1387
- conditions.push("completed = ?");
1388
- params.push(opts.completed ? 1 : 0);
1389
- }
1390
- const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
1391
- const totalRow = db.prepare(`SELECT COUNT(*) as count FROM todos${where}`).get(
1392
- ...params
1393
- );
1394
- const limitClause = opts.limit != null ? ` LIMIT ? OFFSET ?` : "";
1395
- const queryParams = opts.limit != null ? [...params, opts.limit, opts.offset ?? 0] : params;
1396
- const rows = db.prepare(
1397
- `SELECT * FROM todos${where} ORDER BY position ASC${limitClause}`
1398
- ).all(
1399
- ...queryParams
1400
- );
1401
- return { data: rows.map(rowToTodo), total: totalRow.count };
1402
- });
1403
- const create = (input) => Effect4.sync(() => {
1404
- const { next_pos } = stmtNextPosition.get();
1405
- stmtInsert.run(input.id, input.content, input.reviewId, next_pos);
1406
- return rowToTodo(stmtFindById.get(input.id));
1407
- });
1408
- const update = (id, updates) => Effect4.sync(() => {
1409
- const sets = [];
1410
- const params = [];
1411
- if (updates.content != null) {
1412
- sets.push("content = ?");
1413
- params.push(updates.content);
1414
- }
1415
- if (updates.completed != null) {
1416
- sets.push("completed = ?");
1417
- params.push(updates.completed ? 1 : 0);
1418
- }
1419
- if (sets.length === 0) {
1420
- const row2 = stmtFindById.get(id);
1421
- return row2 ? rowToTodo(row2) : null;
1422
- }
1423
- sets.push("updated_at = datetime('now')");
1424
- params.push(id);
1425
- db.prepare(`UPDATE todos SET ${sets.join(", ")} WHERE id = ?`).run(
1426
- ...params
1427
- );
1428
- const row = stmtFindById.get(id);
1429
- return row ? rowToTodo(row) : null;
1430
- });
1431
- const toggle = (id) => Effect4.sync(() => {
1432
- const row = stmtFindById.get(id);
1433
- if (!row) {
1434
- return null;
1435
- }
1436
- const newCompleted = row.completed === 1 ? 0 : 1;
1437
- db.prepare(
1438
- "UPDATE todos SET completed = ?, updated_at = datetime('now') WHERE id = ?"
1439
- ).run(newCompleted, id);
1440
- return rowToTodo(stmtFindById.get(id));
1441
- });
1442
- const remove = (id) => Effect4.sync(() => {
1443
- const result = stmtDelete.run(id);
1444
- return Number(result.changes) > 0;
1445
- });
1446
- const removeCompleted = () => Effect4.sync(() => {
1447
- const result = stmtDeleteCompleted.run();
1448
- return Number(result.changes);
1449
- });
1450
- const reorder = (orderedIds) => withTransaction(
1451
- db,
1452
- Effect4.sync(() => {
1453
- const stmt = db.prepare(
1454
- "UPDATE todos SET position = ?, updated_at = datetime('now') WHERE id = ?"
1455
- );
1456
- let updated = 0;
1457
- for (let i = 0; i < orderedIds.length; i++) {
1458
- const result = stmt.run(i, orderedIds[i]);
1459
- updated += Number(result.changes);
1460
- }
1461
- return updated;
1462
- })
1463
- );
1464
- const move = (id, newPosition) => Effect4.gen(function* move2() {
1465
- const row = stmtFindById.get(id);
1466
- if (!row) {
1467
- return null;
1468
- }
1469
- const oldPosition = row.position;
1470
- yield* withTransaction(
1471
- db,
1472
- Effect4.sync(() => {
1473
- if (newPosition < oldPosition) {
1474
- db.prepare(
1475
- "UPDATE todos SET position = position + 1, updated_at = datetime('now') WHERE position >= ? AND position < ? AND id != ?"
1476
- ).run(newPosition, oldPosition, id);
1477
- } else if (newPosition > oldPosition) {
1478
- db.prepare(
1479
- "UPDATE todos SET position = position - 1, updated_at = datetime('now') WHERE position > ? AND position <= ? AND id != ?"
1480
- ).run(oldPosition, newPosition, id);
1481
- }
1482
- db.prepare(
1483
- "UPDATE todos SET position = ?, updated_at = datetime('now') WHERE id = ?"
1484
- ).run(newPosition, id);
1485
- })
1486
- );
1487
- return rowToTodo(stmtFindById.get(id));
1488
- });
1489
- const countAll = () => Effect4.sync(() => {
1490
- const row = stmtCountAll.get();
1491
- return row.count;
1492
- });
1493
- const countCompleted = () => Effect4.sync(() => {
1494
- const row = stmtCountCompleted.get();
1495
- return row.count;
1496
- });
1497
- const countPending = () => Effect4.sync(() => {
1498
- const row = stmtCountPending.get();
1499
- return row.count;
1500
- });
1501
- return {
1502
- countAll,
1503
- countCompleted,
1504
- countPending,
1505
- create,
1506
- findAll,
1507
- findById,
1508
- move,
1509
- remove,
1510
- removeCompleted,
1511
- reorder,
1512
- toggle,
1513
- update
1514
- };
1515
- })
1516
- }) {
1517
- };
1518
-
1519
- // ../../packages/core/src/services/todo.service.ts
1520
- var TodoService = class extends Effect4.Service()(
1521
- "@ringi/TodoService",
1522
- {
1523
- dependencies: [TodoRepo.Default],
1524
- effect: Effect4.sync(() => {
1525
- const create = Effect4.fn("TodoService.create")(function* create2(input) {
1526
- const repo = yield* TodoRepo;
1527
- const id = randomUUID();
1528
- return yield* repo.create({
1529
- content: input.content,
1530
- id,
1531
- reviewId: input.reviewId
1532
- });
1533
- });
1534
- const getById = Effect4.fn("TodoService.getById")(function* getById2(id) {
1535
- const repo = yield* TodoRepo;
1536
- const todo = yield* repo.findById(id);
1537
- if (!todo) {
1538
- return yield* new TodoNotFound({ id });
1539
- }
1540
- return todo;
1541
- });
1542
- const list = Effect4.fn("TodoService.list")(function* list2(opts = {}) {
1543
- const repo = yield* TodoRepo;
1544
- const result = yield* repo.findAll(opts);
1545
- return {
1546
- data: result.data,
1547
- limit: opts.limit ?? null,
1548
- offset: opts.offset ?? 0,
1549
- total: result.total
1550
- };
1551
- });
1552
- const update = Effect4.fn("TodoService.update")(function* update2(id, input) {
1553
- const repo = yield* TodoRepo;
1554
- const existing = yield* repo.findById(id);
1555
- if (!existing) {
1556
- return yield* new TodoNotFound({ id });
1557
- }
1558
- const updates = {};
1559
- if (Option.isSome(input.content)) {
1560
- updates.content = input.content.value;
1561
- }
1562
- if (Option.isSome(input.completed)) {
1563
- updates.completed = input.completed.value;
1564
- }
1565
- const todo = yield* repo.update(id, updates);
1566
- if (!todo) {
1567
- return yield* new TodoNotFound({ id });
1568
- }
1569
- return todo;
1570
- });
1571
- const toggle = Effect4.fn("TodoService.toggle")(function* toggle2(id) {
1572
- const repo = yield* TodoRepo;
1573
- const todo = yield* repo.toggle(id);
1574
- if (!todo) {
1575
- return yield* new TodoNotFound({ id });
1576
- }
1577
- return todo;
1578
- });
1579
- const remove = Effect4.fn("TodoService.remove")(function* remove2(id) {
1580
- const repo = yield* TodoRepo;
1581
- const existing = yield* repo.findById(id);
1582
- if (!existing) {
1583
- return yield* new TodoNotFound({ id });
1584
- }
1585
- yield* repo.remove(id);
1586
- return { success: true };
1587
- });
1588
- const removeCompleted = Effect4.fn("TodoService.removeCompleted")(
1589
- function* removeCompleted2() {
1590
- const repo = yield* TodoRepo;
1591
- const deleted = yield* repo.removeCompleted();
1592
- return { deleted };
1593
- }
1594
- );
1595
- const reorder = Effect4.fn("TodoService.reorder")(function* reorder2(orderedIds) {
1596
- const repo = yield* TodoRepo;
1597
- const updated = yield* repo.reorder(orderedIds);
1598
- return { updated };
1599
- });
1600
- const move = Effect4.fn("TodoService.move")(function* move2(id, position) {
1601
- const repo = yield* TodoRepo;
1602
- const todo = yield* repo.move(id, position);
1603
- if (!todo) {
1604
- return yield* new TodoNotFound({ id });
1605
- }
1606
- return todo;
1607
- });
1608
- const getStats = Effect4.fn("TodoService.getStats")(function* getStats2() {
1609
- const repo = yield* TodoRepo;
1610
- const total = yield* repo.countAll();
1611
- const completed = yield* repo.countCompleted();
1612
- const pending = yield* repo.countPending();
1613
- return { completed, pending, total };
1614
- });
1615
- return {
1616
- create,
1617
- getById,
1618
- getStats,
1619
- list,
1620
- move,
1621
- remove,
1622
- removeCompleted,
1623
- reorder,
1624
- toggle,
1625
- update
1626
- };
1627
- })
1628
- }
1629
- ) {
1630
- };
1631
- var ExportService = class extends Effect4.Service()(
1632
- "@ringi/ExportService",
1633
- {
1634
- dependencies: [
1635
- ReviewService.Default,
1636
- CommentService.Default,
1637
- TodoService.Default
1638
- ],
1639
- effect: Effect4.sync(() => {
1640
- const exportReview = Effect4.fn("ExportService.exportReview")(
1641
- function* exportReview2(reviewId) {
1642
- const reviewSvc = yield* ReviewService;
1643
- const commentSvc = yield* CommentService;
1644
- const todoSvc = yield* TodoService;
1645
- const review = yield* reviewSvc.getById(reviewId);
1646
- const repo = review.repository;
1647
- const repoName = repo?.name ?? "Unknown";
1648
- const branch = repo?.branch ?? "unknown";
1649
- const comments = yield* commentSvc.getByReview(reviewId);
1650
- const commentStats = yield* commentSvc.getStats(reviewId);
1651
- const todos = yield* todoSvc.list({ reviewId });
1652
- const lines2 = [];
1653
- lines2.push(`# Code Review: ${repoName}`);
1654
- lines2.push("");
1655
- lines2.push(`**Status:** ${review.status}`);
1656
- lines2.push(`**Branch:** ${branch}`);
1657
- lines2.push(`**Created:** ${review.createdAt}`);
1658
- if (review.files && review.files.length > 0) {
1659
- lines2.push("");
1660
- lines2.push("## Files Changed");
1661
- lines2.push("");
1662
- lines2.push("| File | Status | Additions | Deletions |");
1663
- lines2.push("|------|--------|-----------|-----------|");
1664
- for (const f of review.files) {
1665
- const statusLabel = f.status === "modified" ? "M" : f.status === "added" ? "A" : f.status === "deleted" ? "D" : f.status;
1666
- lines2.push(
1667
- `| ${f.filePath} | ${statusLabel} | +${f.additions} | -${f.deletions} |`
1668
- );
1669
- }
1670
- }
1671
- if (comments.length > 0) {
1672
- lines2.push("");
1673
- lines2.push(
1674
- `## Comments (${commentStats.total} total, ${commentStats.resolved} resolved)`
1675
- );
1676
- const byFile = /* @__PURE__ */ new Map();
1677
- for (const c of comments) {
1678
- const key = c.filePath ?? "(general)";
1679
- const arr = byFile.get(key) ?? [];
1680
- arr.push(c);
1681
- byFile.set(key, arr);
1682
- }
1683
- for (const [filePath, fileComments] of byFile) {
1684
- lines2.push("");
1685
- lines2.push(`### ${filePath}`);
1686
- for (const c of fileComments) {
1687
- lines2.push("");
1688
- lines2.push(
1689
- `**Line ${c.lineNumber ?? "\u2013"}** (${c.lineType ?? "context"})`
1690
- );
1691
- lines2.push(`> ${c.content}`);
1692
- if (c.suggestion) {
1693
- lines2.push("");
1694
- lines2.push("```suggestion");
1695
- lines2.push(c.suggestion);
1696
- lines2.push("```");
1697
- }
1698
- }
1699
- }
1700
- }
1701
- if (todos.data.length > 0) {
1702
- const completed = todos.data.filter((t) => t.completed).length;
1703
- lines2.push("");
1704
- lines2.push("---");
1705
- lines2.push("");
1706
- lines2.push(
1707
- `## Todos (${todos.total} total, ${completed} completed)`
1708
- );
1709
- lines2.push("");
1710
- for (const t of todos.data) {
1711
- const check = t.completed ? "x" : " ";
1712
- lines2.push(`- [${check}] ${t.content}`);
1713
- }
1714
- }
1715
- lines2.push("");
1716
- return lines2.join("\n");
1717
- }
1718
- );
1719
- return { exportReview };
1720
- })
1721
- }
1722
- ) {
1723
- };
1724
- var EntryTypes = {
1725
- FILE_TYPE: "files",
1726
- DIR_TYPE: "directories",
1727
- FILE_DIR_TYPE: "files_directories",
1728
- EVERYTHING_TYPE: "all"
1729
- };
1730
- var defaultOptions = {
1731
- root: ".",
1732
- fileFilter: (_entryInfo) => true,
1733
- directoryFilter: (_entryInfo) => true,
1734
- type: EntryTypes.FILE_TYPE,
1735
- lstat: false,
1736
- depth: 2147483648,
1737
- alwaysStat: false,
1738
- highWaterMark: 4096
1739
- };
1740
- Object.freeze(defaultOptions);
1741
- var RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
1742
- var NORMAL_FLOW_ERRORS = /* @__PURE__ */ new Set(["ENOENT", "EPERM", "EACCES", "ELOOP", RECURSIVE_ERROR_CODE]);
1743
- var ALL_TYPES = [
1744
- EntryTypes.DIR_TYPE,
1745
- EntryTypes.EVERYTHING_TYPE,
1746
- EntryTypes.FILE_DIR_TYPE,
1747
- EntryTypes.FILE_TYPE
1748
- ];
1749
- var DIR_TYPES = /* @__PURE__ */ new Set([
1750
- EntryTypes.DIR_TYPE,
1751
- EntryTypes.EVERYTHING_TYPE,
1752
- EntryTypes.FILE_DIR_TYPE
1753
- ]);
1754
- var FILE_TYPES = /* @__PURE__ */ new Set([
1755
- EntryTypes.EVERYTHING_TYPE,
1756
- EntryTypes.FILE_DIR_TYPE,
1757
- EntryTypes.FILE_TYPE
1758
- ]);
1759
- var isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
1760
- var wantBigintFsStats = process.platform === "win32";
1761
- var emptyFn = (_entryInfo) => true;
1762
- var normalizeFilter = (filter) => {
1763
- if (filter === void 0)
1764
- return emptyFn;
1765
- if (typeof filter === "function")
1766
- return filter;
1767
- if (typeof filter === "string") {
1768
- const fl = filter.trim();
1769
- return (entry) => entry.basename === fl;
1770
- }
1771
- if (Array.isArray(filter)) {
1772
- const trItems = filter.map((item) => item.trim());
1773
- return (entry) => trItems.some((f) => entry.basename === f);
1774
- }
1775
- return emptyFn;
1776
- };
1777
- var ReaddirpStream = class extends Readable {
1778
- parents;
1779
- reading;
1780
- parent;
1781
- _stat;
1782
- _maxDepth;
1783
- _wantsDir;
1784
- _wantsFile;
1785
- _wantsEverything;
1786
- _root;
1787
- _isDirent;
1788
- _statsProp;
1789
- _rdOptions;
1790
- _fileFilter;
1791
- _directoryFilter;
1792
- constructor(options = {}) {
1793
- super({
1794
- objectMode: true,
1795
- autoDestroy: true,
1796
- highWaterMark: options.highWaterMark
1797
- });
1798
- const opts = { ...defaultOptions, ...options };
1799
- const { root, type } = opts;
1800
- this._fileFilter = normalizeFilter(opts.fileFilter);
1801
- this._directoryFilter = normalizeFilter(opts.directoryFilter);
1802
- const statMethod = opts.lstat ? lstat : stat;
1803
- if (wantBigintFsStats) {
1804
- this._stat = (path) => statMethod(path, { bigint: true });
1805
- } else {
1806
- this._stat = statMethod;
1807
- }
1808
- this._maxDepth = opts.depth != null && Number.isSafeInteger(opts.depth) ? opts.depth : defaultOptions.depth;
1809
- this._wantsDir = type ? DIR_TYPES.has(type) : false;
1810
- this._wantsFile = type ? FILE_TYPES.has(type) : false;
1811
- this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
1812
- this._root = resolve(root);
1813
- this._isDirent = !opts.alwaysStat;
1814
- this._statsProp = this._isDirent ? "dirent" : "stats";
1815
- this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
1816
- this.parents = [this._exploreDir(root, 1)];
1817
- this.reading = false;
1818
- this.parent = void 0;
1819
- }
1820
- async _read(batch) {
1821
- if (this.reading)
1822
- return;
1823
- this.reading = true;
1824
- try {
1825
- while (!this.destroyed && batch > 0) {
1826
- const par = this.parent;
1827
- const fil = par && par.files;
1828
- if (fil && fil.length > 0) {
1829
- const { path, depth } = par;
1830
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path));
1831
- const awaited = await Promise.all(slice);
1832
- for (const entry of awaited) {
1833
- if (!entry)
1834
- continue;
1835
- if (this.destroyed)
1836
- return;
1837
- const entryType = await this._getEntryType(entry);
1838
- if (entryType === "directory" && this._directoryFilter(entry)) {
1839
- if (depth <= this._maxDepth) {
1840
- this.parents.push(this._exploreDir(entry.fullPath, depth + 1));
1841
- }
1842
- if (this._wantsDir) {
1843
- this.push(entry);
1844
- batch--;
1845
- }
1846
- } else if ((entryType === "file" || this._includeAsFile(entry)) && this._fileFilter(entry)) {
1847
- if (this._wantsFile) {
1848
- this.push(entry);
1849
- batch--;
1850
- }
1851
- }
1852
- }
1853
- } else {
1854
- const parent = this.parents.pop();
1855
- if (!parent) {
1856
- this.push(null);
1857
- break;
1858
- }
1859
- this.parent = await parent;
1860
- if (this.destroyed)
1861
- return;
1862
- }
1863
- }
1864
- } catch (error) {
1865
- this.destroy(error);
1866
- } finally {
1867
- this.reading = false;
1868
- }
1869
- }
1870
- async _exploreDir(path, depth) {
1871
- let files;
1872
- try {
1873
- files = await readdir(path, this._rdOptions);
1874
- } catch (error) {
1875
- this._onError(error);
1876
- }
1877
- return { files, depth, path };
1878
- }
1879
- async _formatEntry(dirent, path) {
1880
- let entry;
1881
- const basename3 = this._isDirent ? dirent.name : dirent;
1882
- try {
1883
- const fullPath = resolve(join(path, basename3));
1884
- entry = { path: relative(this._root, fullPath), fullPath, basename: basename3 };
1885
- entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
1886
- } catch (err) {
1887
- this._onError(err);
1888
- return;
1889
- }
1890
- return entry;
1891
- }
1892
- _onError(err) {
1893
- if (isNormalFlowError(err) && !this.destroyed) {
1894
- this.emit("warn", err);
1895
- } else {
1896
- this.destroy(err);
1897
- }
1898
- }
1899
- async _getEntryType(entry) {
1900
- if (!entry && this._statsProp in entry) {
1901
- return "";
1902
- }
1903
- const stats = entry[this._statsProp];
1904
- if (stats.isFile())
1905
- return "file";
1906
- if (stats.isDirectory())
1907
- return "directory";
1908
- if (stats && stats.isSymbolicLink()) {
1909
- const full = entry.fullPath;
1910
- try {
1911
- const entryRealPath = await realpath(full);
1912
- const entryRealPathStats = await lstat(entryRealPath);
1913
- if (entryRealPathStats.isFile()) {
1914
- return "file";
1915
- }
1916
- if (entryRealPathStats.isDirectory()) {
1917
- const len = entryRealPath.length;
1918
- if (full.startsWith(entryRealPath) && full.substr(len, 1) === sep) {
1919
- const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
1920
- recursiveError.code = RECURSIVE_ERROR_CODE;
1921
- return this._onError(recursiveError);
1922
- }
1923
- return "directory";
1924
- }
1925
- } catch (error) {
1926
- this._onError(error);
1927
- return "";
1928
- }
1929
- }
1930
- }
1931
- _includeAsFile(entry) {
1932
- const stats = entry && entry[this._statsProp];
1933
- return stats && this._wantsEverything && !stats.isDirectory();
1934
- }
1935
- };
1936
- function readdirp(root, options = {}) {
1937
- let type = options.entryType || options.type;
1938
- if (type === "both")
1939
- type = EntryTypes.FILE_DIR_TYPE;
1940
- if (type)
1941
- options.type = type;
1942
- if (!root) {
1943
- throw new Error("readdirp: root argument is required. Usage: readdirp(root, options)");
1944
- } else if (typeof root !== "string") {
1945
- throw new TypeError("readdirp: root argument must be a string. Usage: readdirp(root, options)");
1946
- } else if (type && !ALL_TYPES.includes(type)) {
1947
- throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(", ")}`);
1948
- }
1949
- options.root = root;
1950
- return new ReaddirpStream(options);
1951
- }
1952
- var STR_DATA = "data";
1953
- var STR_END = "end";
1954
- var STR_CLOSE = "close";
1955
- var EMPTY_FN = () => {
1956
- };
1957
- var pl = process.platform;
1958
- var isWindows = pl === "win32";
1959
- var isMacos = pl === "darwin";
1960
- var isLinux = pl === "linux";
1961
- var isFreeBSD = pl === "freebsd";
1962
- var isIBMi = type() === "OS400";
1963
- var EVENTS = {
1964
- ALL: "all",
1965
- READY: "ready",
1966
- ADD: "add",
1967
- CHANGE: "change",
1968
- ADD_DIR: "addDir",
1969
- UNLINK: "unlink",
1970
- UNLINK_DIR: "unlinkDir",
1971
- RAW: "raw",
1972
- ERROR: "error"
1973
- };
1974
- var EV = EVENTS;
1975
- var THROTTLE_MODE_WATCH = "watch";
1976
- var statMethods = { lstat: lstat, stat: stat };
1977
- var KEY_LISTENERS = "listeners";
1978
- var KEY_ERR = "errHandlers";
1979
- var KEY_RAW = "rawEmitters";
1980
- var HANDLER_KEYS = [KEY_LISTENERS, KEY_ERR, KEY_RAW];
1981
- var binaryExtensions = /* @__PURE__ */ new Set([
1982
- "3dm",
1983
- "3ds",
1984
- "3g2",
1985
- "3gp",
1986
- "7z",
1987
- "a",
1988
- "aac",
1989
- "adp",
1990
- "afdesign",
1991
- "afphoto",
1992
- "afpub",
1993
- "ai",
1994
- "aif",
1995
- "aiff",
1996
- "alz",
1997
- "ape",
1998
- "apk",
1999
- "appimage",
2000
- "ar",
2001
- "arj",
2002
- "asf",
2003
- "au",
2004
- "avi",
2005
- "bak",
2006
- "baml",
2007
- "bh",
2008
- "bin",
2009
- "bk",
2010
- "bmp",
2011
- "btif",
2012
- "bz2",
2013
- "bzip2",
2014
- "cab",
2015
- "caf",
2016
- "cgm",
2017
- "class",
2018
- "cmx",
2019
- "cpio",
2020
- "cr2",
2021
- "cur",
2022
- "dat",
2023
- "dcm",
2024
- "deb",
2025
- "dex",
2026
- "djvu",
2027
- "dll",
2028
- "dmg",
2029
- "dng",
2030
- "doc",
2031
- "docm",
2032
- "docx",
2033
- "dot",
2034
- "dotm",
2035
- "dra",
2036
- "DS_Store",
2037
- "dsk",
2038
- "dts",
2039
- "dtshd",
2040
- "dvb",
2041
- "dwg",
2042
- "dxf",
2043
- "ecelp4800",
2044
- "ecelp7470",
2045
- "ecelp9600",
2046
- "egg",
2047
- "eol",
2048
- "eot",
2049
- "epub",
2050
- "exe",
2051
- "f4v",
2052
- "fbs",
2053
- "fh",
2054
- "fla",
2055
- "flac",
2056
- "flatpak",
2057
- "fli",
2058
- "flv",
2059
- "fpx",
2060
- "fst",
2061
- "fvt",
2062
- "g3",
2063
- "gh",
2064
- "gif",
2065
- "graffle",
2066
- "gz",
2067
- "gzip",
2068
- "h261",
2069
- "h263",
2070
- "h264",
2071
- "icns",
2072
- "ico",
2073
- "ief",
2074
- "img",
2075
- "ipa",
2076
- "iso",
2077
- "jar",
2078
- "jpeg",
2079
- "jpg",
2080
- "jpgv",
2081
- "jpm",
2082
- "jxr",
2083
- "key",
2084
- "ktx",
2085
- "lha",
2086
- "lib",
2087
- "lvp",
2088
- "lz",
2089
- "lzh",
2090
- "lzma",
2091
- "lzo",
2092
- "m3u",
2093
- "m4a",
2094
- "m4v",
2095
- "mar",
2096
- "mdi",
2097
- "mht",
2098
- "mid",
2099
- "midi",
2100
- "mj2",
2101
- "mka",
2102
- "mkv",
2103
- "mmr",
2104
- "mng",
2105
- "mobi",
2106
- "mov",
2107
- "movie",
2108
- "mp3",
2109
- "mp4",
2110
- "mp4a",
2111
- "mpeg",
2112
- "mpg",
2113
- "mpga",
2114
- "mxu",
2115
- "nef",
2116
- "npx",
2117
- "numbers",
2118
- "nupkg",
2119
- "o",
2120
- "odp",
2121
- "ods",
2122
- "odt",
2123
- "oga",
2124
- "ogg",
2125
- "ogv",
2126
- "otf",
2127
- "ott",
2128
- "pages",
2129
- "pbm",
2130
- "pcx",
2131
- "pdb",
2132
- "pdf",
2133
- "pea",
2134
- "pgm",
2135
- "pic",
2136
- "png",
2137
- "pnm",
2138
- "pot",
2139
- "potm",
2140
- "potx",
2141
- "ppa",
2142
- "ppam",
2143
- "ppm",
2144
- "pps",
2145
- "ppsm",
2146
- "ppsx",
2147
- "ppt",
2148
- "pptm",
2149
- "pptx",
2150
- "psd",
2151
- "pya",
2152
- "pyc",
2153
- "pyo",
2154
- "pyv",
2155
- "qt",
2156
- "rar",
2157
- "ras",
2158
- "raw",
2159
- "resources",
2160
- "rgb",
2161
- "rip",
2162
- "rlc",
2163
- "rmf",
2164
- "rmvb",
2165
- "rpm",
2166
- "rtf",
2167
- "rz",
2168
- "s3m",
2169
- "s7z",
2170
- "scpt",
2171
- "sgi",
2172
- "shar",
2173
- "snap",
2174
- "sil",
2175
- "sketch",
2176
- "slk",
2177
- "smv",
2178
- "snk",
2179
- "so",
2180
- "stl",
2181
- "suo",
2182
- "sub",
2183
- "swf",
2184
- "tar",
2185
- "tbz",
2186
- "tbz2",
2187
- "tga",
2188
- "tgz",
2189
- "thmx",
2190
- "tif",
2191
- "tiff",
2192
- "tlz",
2193
- "ttc",
2194
- "ttf",
2195
- "txz",
2196
- "udf",
2197
- "uvh",
2198
- "uvi",
2199
- "uvm",
2200
- "uvp",
2201
- "uvs",
2202
- "uvu",
2203
- "viv",
2204
- "vob",
2205
- "war",
2206
- "wav",
2207
- "wax",
2208
- "wbmp",
2209
- "wdp",
2210
- "weba",
2211
- "webm",
2212
- "webp",
2213
- "whl",
2214
- "wim",
2215
- "wm",
2216
- "wma",
2217
- "wmv",
2218
- "wmx",
2219
- "woff",
2220
- "woff2",
2221
- "wrm",
2222
- "wvx",
2223
- "xbm",
2224
- "xif",
2225
- "xla",
2226
- "xlam",
2227
- "xls",
2228
- "xlsb",
2229
- "xlsm",
2230
- "xlsx",
2231
- "xlt",
2232
- "xltm",
2233
- "xltx",
2234
- "xm",
2235
- "xmind",
2236
- "xpi",
2237
- "xpm",
2238
- "xwd",
2239
- "xz",
2240
- "z",
2241
- "zip",
2242
- "zipx"
2243
- ]);
2244
- var isBinaryPath = (filePath) => binaryExtensions.has(sp2.extname(filePath).slice(1).toLowerCase());
2245
- var foreach = (val, fn7) => {
2246
- if (val instanceof Set) {
2247
- val.forEach(fn7);
2248
- } else {
2249
- fn7(val);
2250
- }
2251
- };
2252
- var addAndConvert = (main, prop, item) => {
2253
- let container = main[prop];
2254
- if (!(container instanceof Set)) {
2255
- main[prop] = container = /* @__PURE__ */ new Set([container]);
2256
- }
2257
- container.add(item);
2258
- };
2259
- var clearItem = (cont) => (key) => {
2260
- const set = cont[key];
2261
- if (set instanceof Set) {
2262
- set.clear();
2263
- } else {
2264
- delete cont[key];
2265
- }
2266
- };
2267
- var delFromSet = (main, prop, item) => {
2268
- const container = main[prop];
2269
- if (container instanceof Set) {
2270
- container.delete(item);
2271
- } else if (container === item) {
2272
- delete main[prop];
2273
- }
2274
- };
2275
- var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
2276
- var FsWatchInstances = /* @__PURE__ */ new Map();
2277
- function createFsWatchInstance(path, options, listener, errHandler, emitRaw) {
2278
- const handleEvent = (rawEvent, evPath) => {
2279
- listener(path);
2280
- emitRaw(rawEvent, evPath, { watchedPath: path });
2281
- if (evPath && path !== evPath) {
2282
- fsWatchBroadcast(sp2.resolve(path, evPath), KEY_LISTENERS, sp2.join(path, evPath));
2283
- }
2284
- };
2285
- try {
2286
- return watch$1(path, {
2287
- persistent: options.persistent
2288
- }, handleEvent);
2289
- } catch (error) {
2290
- errHandler(error);
2291
- return void 0;
2292
- }
2293
- }
2294
- var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
2295
- const cont = FsWatchInstances.get(fullPath);
2296
- if (!cont)
2297
- return;
2298
- foreach(cont[listenerType], (listener) => {
2299
- listener(val1, val2, val3);
2300
- });
2301
- };
2302
- var setFsWatchListener = (path, fullPath, options, handlers) => {
2303
- const { listener, errHandler, rawEmitter } = handlers;
2304
- let cont = FsWatchInstances.get(fullPath);
2305
- let watcher;
2306
- if (!options.persistent) {
2307
- watcher = createFsWatchInstance(path, options, listener, errHandler, rawEmitter);
2308
- if (!watcher)
2309
- return;
2310
- return watcher.close.bind(watcher);
2311
- }
2312
- if (cont) {
2313
- addAndConvert(cont, KEY_LISTENERS, listener);
2314
- addAndConvert(cont, KEY_ERR, errHandler);
2315
- addAndConvert(cont, KEY_RAW, rawEmitter);
2316
- } else {
2317
- watcher = createFsWatchInstance(
2318
- path,
2319
- options,
2320
- fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
2321
- errHandler,
2322
- // no need to use broadcast here
2323
- fsWatchBroadcast.bind(null, fullPath, KEY_RAW)
2324
- );
2325
- if (!watcher)
2326
- return;
2327
- watcher.on(EV.ERROR, async (error) => {
2328
- const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);
2329
- if (cont)
2330
- cont.watcherUnusable = true;
2331
- if (isWindows && error.code === "EPERM") {
2332
- try {
2333
- const fd = await open(path, "r");
2334
- await fd.close();
2335
- broadcastErr(error);
2336
- } catch (err) {
2337
- }
2338
- } else {
2339
- broadcastErr(error);
2340
- }
2341
- });
2342
- cont = {
2343
- listeners: listener,
2344
- errHandlers: errHandler,
2345
- rawEmitters: rawEmitter,
2346
- watcher
2347
- };
2348
- FsWatchInstances.set(fullPath, cont);
2349
- }
2350
- return () => {
2351
- delFromSet(cont, KEY_LISTENERS, listener);
2352
- delFromSet(cont, KEY_ERR, errHandler);
2353
- delFromSet(cont, KEY_RAW, rawEmitter);
2354
- if (isEmptySet(cont.listeners)) {
2355
- cont.watcher.close();
2356
- FsWatchInstances.delete(fullPath);
2357
- HANDLER_KEYS.forEach(clearItem(cont));
2358
- cont.watcher = void 0;
2359
- Object.freeze(cont);
2360
- }
2361
- };
2362
- };
2363
- var FsWatchFileInstances = /* @__PURE__ */ new Map();
2364
- var setFsWatchFileListener = (path, fullPath, options, handlers) => {
2365
- const { listener, rawEmitter } = handlers;
2366
- let cont = FsWatchFileInstances.get(fullPath);
2367
- const copts = cont && cont.options;
2368
- if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
2369
- unwatchFile(fullPath);
2370
- cont = void 0;
2371
- }
2372
- if (cont) {
2373
- addAndConvert(cont, KEY_LISTENERS, listener);
2374
- addAndConvert(cont, KEY_RAW, rawEmitter);
2375
- } else {
2376
- cont = {
2377
- listeners: listener,
2378
- rawEmitters: rawEmitter,
2379
- options,
2380
- watcher: watchFile(fullPath, options, (curr, prev) => {
2381
- foreach(cont.rawEmitters, (rawEmitter2) => {
2382
- rawEmitter2(EV.CHANGE, fullPath, { curr, prev });
2383
- });
2384
- const currmtime = curr.mtimeMs;
2385
- if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
2386
- foreach(cont.listeners, (listener2) => listener2(path, curr));
2387
- }
2388
- })
2389
- };
2390
- FsWatchFileInstances.set(fullPath, cont);
2391
- }
2392
- return () => {
2393
- delFromSet(cont, KEY_LISTENERS, listener);
2394
- delFromSet(cont, KEY_RAW, rawEmitter);
2395
- if (isEmptySet(cont.listeners)) {
2396
- FsWatchFileInstances.delete(fullPath);
2397
- unwatchFile(fullPath);
2398
- cont.options = cont.watcher = void 0;
2399
- Object.freeze(cont);
2400
- }
2401
- };
2402
- };
2403
- var NodeFsHandler = class {
2404
- fsw;
2405
- _boundHandleError;
2406
- constructor(fsW) {
2407
- this.fsw = fsW;
2408
- this._boundHandleError = (error) => fsW._handleError(error);
2409
- }
2410
- /**
2411
- * Watch file for changes with fs_watchFile or fs_watch.
2412
- * @param path to file or dir
2413
- * @param listener on fs change
2414
- * @returns closer for the watcher instance
2415
- */
2416
- _watchWithNodeFs(path, listener) {
2417
- const opts = this.fsw.options;
2418
- const directory = sp2.dirname(path);
2419
- const basename3 = sp2.basename(path);
2420
- const parent = this.fsw._getWatchedDir(directory);
2421
- parent.add(basename3);
2422
- const absolutePath = sp2.resolve(path);
2423
- const options = {
2424
- persistent: opts.persistent
2425
- };
2426
- if (!listener)
2427
- listener = EMPTY_FN;
2428
- let closer;
2429
- if (opts.usePolling) {
2430
- const enableBin = opts.interval !== opts.binaryInterval;
2431
- options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
2432
- closer = setFsWatchFileListener(path, absolutePath, options, {
2433
- listener,
2434
- rawEmitter: this.fsw._emitRaw
2435
- });
2436
- } else {
2437
- closer = setFsWatchListener(path, absolutePath, options, {
2438
- listener,
2439
- errHandler: this._boundHandleError,
2440
- rawEmitter: this.fsw._emitRaw
2441
- });
2442
- }
2443
- return closer;
2444
- }
2445
- /**
2446
- * Watch a file and emit add event if warranted.
2447
- * @returns closer for the watcher instance
2448
- */
2449
- _handleFile(file, stats, initialAdd) {
2450
- if (this.fsw.closed) {
2451
- return;
2452
- }
2453
- const dirname4 = sp2.dirname(file);
2454
- const basename3 = sp2.basename(file);
2455
- const parent = this.fsw._getWatchedDir(dirname4);
2456
- let prevStats = stats;
2457
- if (parent.has(basename3))
2458
- return;
2459
- const listener = async (path, newStats) => {
2460
- if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
2461
- return;
2462
- if (!newStats || newStats.mtimeMs === 0) {
2463
- try {
2464
- const newStats2 = await stat(file);
2465
- if (this.fsw.closed)
2466
- return;
2467
- const at = newStats2.atimeMs;
2468
- const mt = newStats2.mtimeMs;
2469
- if (!at || at <= mt || mt !== prevStats.mtimeMs) {
2470
- this.fsw._emit(EV.CHANGE, file, newStats2);
2471
- }
2472
- if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
2473
- this.fsw._closeFile(path);
2474
- prevStats = newStats2;
2475
- const closer2 = this._watchWithNodeFs(file, listener);
2476
- if (closer2)
2477
- this.fsw._addPathCloser(path, closer2);
2478
- } else {
2479
- prevStats = newStats2;
2480
- }
2481
- } catch (error) {
2482
- this.fsw._remove(dirname4, basename3);
2483
- }
2484
- } else if (parent.has(basename3)) {
2485
- const at = newStats.atimeMs;
2486
- const mt = newStats.mtimeMs;
2487
- if (!at || at <= mt || mt !== prevStats.mtimeMs) {
2488
- this.fsw._emit(EV.CHANGE, file, newStats);
2489
- }
2490
- prevStats = newStats;
2491
- }
2492
- };
2493
- const closer = this._watchWithNodeFs(file, listener);
2494
- if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
2495
- if (!this.fsw._throttle(EV.ADD, file, 0))
2496
- return;
2497
- this.fsw._emit(EV.ADD, file, stats);
2498
- }
2499
- return closer;
2500
- }
2501
- /**
2502
- * Handle symlinks encountered while reading a dir.
2503
- * @param entry returned by readdirp
2504
- * @param directory path of dir being read
2505
- * @param path of this item
2506
- * @param item basename of this item
2507
- * @returns true if no more processing is needed for this entry.
2508
- */
2509
- async _handleSymlink(entry, directory, path, item) {
2510
- if (this.fsw.closed) {
2511
- return;
2512
- }
2513
- const full = entry.fullPath;
2514
- const dir = this.fsw._getWatchedDir(directory);
2515
- if (!this.fsw.options.followSymlinks) {
2516
- this.fsw._incrReadyCount();
2517
- let linkPath;
2518
- try {
2519
- linkPath = await realpath(path);
2520
- } catch (e) {
2521
- this.fsw._emitReady();
2522
- return true;
2523
- }
2524
- if (this.fsw.closed)
2525
- return;
2526
- if (dir.has(item)) {
2527
- if (this.fsw._symlinkPaths.get(full) !== linkPath) {
2528
- this.fsw._symlinkPaths.set(full, linkPath);
2529
- this.fsw._emit(EV.CHANGE, path, entry.stats);
2530
- }
2531
- } else {
2532
- dir.add(item);
2533
- this.fsw._symlinkPaths.set(full, linkPath);
2534
- this.fsw._emit(EV.ADD, path, entry.stats);
2535
- }
2536
- this.fsw._emitReady();
2537
- return true;
2538
- }
2539
- if (this.fsw._symlinkPaths.has(full)) {
2540
- return true;
2541
- }
2542
- this.fsw._symlinkPaths.set(full, true);
2543
- }
2544
- _handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {
2545
- directory = sp2.join(directory, "");
2546
- const throttleKey = target ? `${directory}:${target}` : directory;
2547
- throttler = this.fsw._throttle("readdir", throttleKey, 1e3);
2548
- if (!throttler)
2549
- return;
2550
- const previous = this.fsw._getWatchedDir(wh.path);
2551
- const current = /* @__PURE__ */ new Set();
2552
- let stream = this.fsw._readdirp(directory, {
2553
- fileFilter: (entry) => wh.filterPath(entry),
2554
- directoryFilter: (entry) => wh.filterDir(entry)
2555
- });
2556
- if (!stream)
2557
- return;
2558
- stream.on(STR_DATA, async (entry) => {
2559
- if (this.fsw.closed) {
2560
- stream = void 0;
2561
- return;
2562
- }
2563
- const item = entry.path;
2564
- let path = sp2.join(directory, item);
2565
- current.add(item);
2566
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path, item)) {
2567
- return;
2568
- }
2569
- if (this.fsw.closed) {
2570
- stream = void 0;
2571
- return;
2572
- }
2573
- if (item === target || !target && !previous.has(item)) {
2574
- this.fsw._incrReadyCount();
2575
- path = sp2.join(dir, sp2.relative(dir, path));
2576
- this._addToNodeFs(path, initialAdd, wh, depth + 1);
2577
- }
2578
- }).on(EV.ERROR, this._boundHandleError);
2579
- return new Promise((resolve3, reject) => {
2580
- if (!stream)
2581
- return reject();
2582
- stream.once(STR_END, () => {
2583
- if (this.fsw.closed) {
2584
- stream = void 0;
2585
- return;
2586
- }
2587
- const wasThrottled = throttler ? throttler.clear() : false;
2588
- resolve3(void 0);
2589
- previous.getChildren().filter((item) => {
2590
- return item !== directory && !current.has(item);
2591
- }).forEach((item) => {
2592
- this.fsw._remove(directory, item);
2593
- });
2594
- stream = void 0;
2595
- if (wasThrottled)
2596
- this._handleRead(directory, false, wh, target, dir, depth, throttler);
2597
- });
2598
- });
2599
- }
2600
- /**
2601
- * Read directory to add / remove files from `@watched` list and re-read it on change.
2602
- * @param dir fs path
2603
- * @param stats
2604
- * @param initialAdd
2605
- * @param depth relative to user-supplied path
2606
- * @param target child path targeted for watch
2607
- * @param wh Common watch helpers for this path
2608
- * @param realpath
2609
- * @returns closer for the watcher instance.
2610
- */
2611
- async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath2) {
2612
- const parentDir = this.fsw._getWatchedDir(sp2.dirname(dir));
2613
- const tracked = parentDir.has(sp2.basename(dir));
2614
- if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {
2615
- this.fsw._emit(EV.ADD_DIR, dir, stats);
2616
- }
2617
- parentDir.add(sp2.basename(dir));
2618
- this.fsw._getWatchedDir(dir);
2619
- let throttler;
2620
- let closer;
2621
- const oDepth = this.fsw.options.depth;
2622
- if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath2)) {
2623
- if (!target) {
2624
- await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
2625
- if (this.fsw.closed)
2626
- return;
2627
- }
2628
- closer = this._watchWithNodeFs(dir, (dirPath, stats2) => {
2629
- if (stats2 && stats2.mtimeMs === 0)
2630
- return;
2631
- this._handleRead(dirPath, false, wh, target, dir, depth, throttler);
2632
- });
2633
- }
2634
- return closer;
2635
- }
2636
- /**
2637
- * Handle added file, directory, or glob pattern.
2638
- * Delegates call to _handleFile / _handleDir after checks.
2639
- * @param path to file or ir
2640
- * @param initialAdd was the file added at watch instantiation?
2641
- * @param priorWh depth relative to user-supplied path
2642
- * @param depth Child path actually targeted for watch
2643
- * @param target Child path actually targeted for watch
2644
- */
2645
- async _addToNodeFs(path, initialAdd, priorWh, depth, target) {
2646
- const ready = this.fsw._emitReady;
2647
- if (this.fsw._isIgnored(path) || this.fsw.closed) {
2648
- ready();
2649
- return false;
2650
- }
2651
- const wh = this.fsw._getWatchHelpers(path);
2652
- if (priorWh) {
2653
- wh.filterPath = (entry) => priorWh.filterPath(entry);
2654
- wh.filterDir = (entry) => priorWh.filterDir(entry);
2655
- }
2656
- try {
2657
- const stats = await statMethods[wh.statMethod](wh.watchPath);
2658
- if (this.fsw.closed)
2659
- return;
2660
- if (this.fsw._isIgnored(wh.watchPath, stats)) {
2661
- ready();
2662
- return false;
2663
- }
2664
- const follow = this.fsw.options.followSymlinks;
2665
- let closer;
2666
- if (stats.isDirectory()) {
2667
- const absPath = sp2.resolve(path);
2668
- const targetPath = follow ? await realpath(path) : path;
2669
- if (this.fsw.closed)
2670
- return;
2671
- closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
2672
- if (this.fsw.closed)
2673
- return;
2674
- if (absPath !== targetPath && targetPath !== void 0) {
2675
- this.fsw._symlinkPaths.set(absPath, targetPath);
2676
- }
2677
- } else if (stats.isSymbolicLink()) {
2678
- const targetPath = follow ? await realpath(path) : path;
2679
- if (this.fsw.closed)
2680
- return;
2681
- const parent = sp2.dirname(wh.watchPath);
2682
- this.fsw._getWatchedDir(parent).add(wh.watchPath);
2683
- this.fsw._emit(EV.ADD, wh.watchPath, stats);
2684
- closer = await this._handleDir(parent, stats, initialAdd, depth, path, wh, targetPath);
2685
- if (this.fsw.closed)
2686
- return;
2687
- if (targetPath !== void 0) {
2688
- this.fsw._symlinkPaths.set(sp2.resolve(path), targetPath);
2689
- }
2690
- } else {
2691
- closer = this._handleFile(wh.watchPath, stats, initialAdd);
2692
- }
2693
- ready();
2694
- if (closer)
2695
- this.fsw._addPathCloser(path, closer);
2696
- return false;
2697
- } catch (error) {
2698
- if (this.fsw._handleError(error)) {
2699
- ready();
2700
- return path;
2701
- }
2702
- }
2703
- }
2704
- };
2705
-
2706
- // ../../node_modules/.pnpm/chokidar@5.0.0/node_modules/chokidar/index.js
2707
- var SLASH = "/";
2708
- var SLASH_SLASH = "//";
2709
- var ONE_DOT = ".";
2710
- var TWO_DOTS = "..";
2711
- var STRING_TYPE = "string";
2712
- var BACK_SLASH_RE = /\\/g;
2713
- var DOUBLE_SLASH_RE = /\/\//g;
2714
- var DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
2715
- var REPLACER_RE = /^\.[/\\]/;
2716
- function arrify(item) {
2717
- return Array.isArray(item) ? item : [item];
2718
- }
2719
- var isMatcherObject = (matcher) => typeof matcher === "object" && matcher !== null && !(matcher instanceof RegExp);
2720
- function createPattern(matcher) {
2721
- if (typeof matcher === "function")
2722
- return matcher;
2723
- if (typeof matcher === "string")
2724
- return (string3) => matcher === string3;
2725
- if (matcher instanceof RegExp)
2726
- return (string3) => matcher.test(string3);
2727
- if (typeof matcher === "object" && matcher !== null) {
2728
- return (string3) => {
2729
- if (matcher.path === string3)
2730
- return true;
2731
- if (matcher.recursive) {
2732
- const relative4 = sp2.relative(matcher.path, string3);
2733
- if (!relative4) {
2734
- return false;
2735
- }
2736
- return !relative4.startsWith("..") && !sp2.isAbsolute(relative4);
2737
- }
2738
- return false;
2739
- };
2740
- }
2741
- return () => false;
2742
- }
2743
- function normalizePath(path) {
2744
- if (typeof path !== "string")
2745
- throw new Error("string expected");
2746
- path = sp2.normalize(path);
2747
- path = path.replace(/\\/g, "/");
2748
- let prepend = false;
2749
- if (path.startsWith("//"))
2750
- prepend = true;
2751
- path = path.replace(DOUBLE_SLASH_RE, "/");
2752
- if (prepend)
2753
- path = "/" + path;
2754
- return path;
2755
- }
2756
- function matchPatterns(patterns, testString, stats) {
2757
- const path = normalizePath(testString);
2758
- for (let index = 0; index < patterns.length; index++) {
2759
- const pattern = patterns[index];
2760
- if (pattern(path, stats)) {
2761
- return true;
2762
- }
2763
- }
2764
- return false;
2765
- }
2766
- function anymatch(matchers, testString) {
2767
- if (matchers == null) {
2768
- throw new TypeError("anymatch: specify first argument");
2769
- }
2770
- const matchersArray = arrify(matchers);
2771
- const patterns = matchersArray.map((matcher) => createPattern(matcher));
2772
- {
2773
- return (testString2, stats) => {
2774
- return matchPatterns(patterns, testString2, stats);
2775
- };
2776
- }
2777
- }
2778
- var unifyPaths = (paths_) => {
2779
- const paths = arrify(paths_).flat();
2780
- if (!paths.every((p) => typeof p === STRING_TYPE)) {
2781
- throw new TypeError(`Non-string provided as watch path: ${paths}`);
2782
- }
2783
- return paths.map(normalizePathToUnix);
2784
- };
2785
- var toUnix = (string3) => {
2786
- let str = string3.replace(BACK_SLASH_RE, SLASH);
2787
- let prepend = false;
2788
- if (str.startsWith(SLASH_SLASH)) {
2789
- prepend = true;
2790
- }
2791
- str = str.replace(DOUBLE_SLASH_RE, SLASH);
2792
- if (prepend) {
2793
- str = SLASH + str;
2794
- }
2795
- return str;
2796
- };
2797
- var normalizePathToUnix = (path) => toUnix(sp2.normalize(toUnix(path)));
2798
- var normalizeIgnored = (cwd = "") => (path) => {
2799
- if (typeof path === "string") {
2800
- return normalizePathToUnix(sp2.isAbsolute(path) ? path : sp2.join(cwd, path));
2801
- } else {
2802
- return path;
2803
- }
2804
- };
2805
- var getAbsolutePath = (path, cwd) => {
2806
- if (sp2.isAbsolute(path)) {
2807
- return path;
2808
- }
2809
- return sp2.join(cwd, path);
2810
- };
2811
- var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
2812
- var DirEntry = class {
2813
- path;
2814
- _removeWatcher;
2815
- items;
2816
- constructor(dir, removeWatcher) {
2817
- this.path = dir;
2818
- this._removeWatcher = removeWatcher;
2819
- this.items = /* @__PURE__ */ new Set();
2820
- }
2821
- add(item) {
2822
- const { items } = this;
2823
- if (!items)
2824
- return;
2825
- if (item !== ONE_DOT && item !== TWO_DOTS)
2826
- items.add(item);
2827
- }
2828
- async remove(item) {
2829
- const { items } = this;
2830
- if (!items)
2831
- return;
2832
- items.delete(item);
2833
- if (items.size > 0)
2834
- return;
2835
- const dir = this.path;
2836
- try {
2837
- await readdir(dir);
2838
- } catch (err) {
2839
- if (this._removeWatcher) {
2840
- this._removeWatcher(sp2.dirname(dir), sp2.basename(dir));
2841
- }
2842
- }
2843
- }
2844
- has(item) {
2845
- const { items } = this;
2846
- if (!items)
2847
- return;
2848
- return items.has(item);
2849
- }
2850
- getChildren() {
2851
- const { items } = this;
2852
- if (!items)
2853
- return [];
2854
- return [...items.values()];
2855
- }
2856
- dispose() {
2857
- this.items.clear();
2858
- this.path = "";
2859
- this._removeWatcher = EMPTY_FN;
2860
- this.items = EMPTY_SET;
2861
- Object.freeze(this);
2862
- }
2863
- };
2864
- var STAT_METHOD_F = "stat";
2865
- var STAT_METHOD_L = "lstat";
2866
- var WatchHelper = class {
2867
- fsw;
2868
- path;
2869
- watchPath;
2870
- fullWatchPath;
2871
- dirParts;
2872
- followSymlinks;
2873
- statMethod;
2874
- constructor(path, follow, fsw) {
2875
- this.fsw = fsw;
2876
- const watchPath = path;
2877
- this.path = path = path.replace(REPLACER_RE, "");
2878
- this.watchPath = watchPath;
2879
- this.fullWatchPath = sp2.resolve(watchPath);
2880
- this.dirParts = [];
2881
- this.dirParts.forEach((parts) => {
2882
- if (parts.length > 1)
2883
- parts.pop();
2884
- });
2885
- this.followSymlinks = follow;
2886
- this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
2887
- }
2888
- entryPath(entry) {
2889
- return sp2.join(this.watchPath, sp2.relative(this.watchPath, entry.fullPath));
2890
- }
2891
- filterPath(entry) {
2892
- const { stats } = entry;
2893
- if (stats && stats.isSymbolicLink())
2894
- return this.filterDir(entry);
2895
- const resolvedPath = this.entryPath(entry);
2896
- return this.fsw._isntIgnored(resolvedPath, stats) && this.fsw._hasReadPermissions(stats);
2897
- }
2898
- filterDir(entry) {
2899
- return this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
2900
- }
2901
- };
2902
- var FSWatcher = class extends EventEmitter {
2903
- closed;
2904
- options;
2905
- _closers;
2906
- _ignoredPaths;
2907
- _throttled;
2908
- _streams;
2909
- _symlinkPaths;
2910
- _watched;
2911
- _pendingWrites;
2912
- _pendingUnlinks;
2913
- _readyCount;
2914
- _emitReady;
2915
- _closePromise;
2916
- _userIgnored;
2917
- _readyEmitted;
2918
- _emitRaw;
2919
- _boundRemove;
2920
- _nodeFsHandler;
2921
- // Not indenting methods for history sake; for now.
2922
- constructor(_opts = {}) {
2923
- super();
2924
- this.closed = false;
2925
- this._closers = /* @__PURE__ */ new Map();
2926
- this._ignoredPaths = /* @__PURE__ */ new Set();
2927
- this._throttled = /* @__PURE__ */ new Map();
2928
- this._streams = /* @__PURE__ */ new Set();
2929
- this._symlinkPaths = /* @__PURE__ */ new Map();
2930
- this._watched = /* @__PURE__ */ new Map();
2931
- this._pendingWrites = /* @__PURE__ */ new Map();
2932
- this._pendingUnlinks = /* @__PURE__ */ new Map();
2933
- this._readyCount = 0;
2934
- this._readyEmitted = false;
2935
- const awf = _opts.awaitWriteFinish;
2936
- const DEF_AWF = { stabilityThreshold: 2e3, pollInterval: 100 };
2937
- const opts = {
2938
- // Defaults
2939
- persistent: true,
2940
- ignoreInitial: false,
2941
- ignorePermissionErrors: false,
2942
- interval: 100,
2943
- binaryInterval: 300,
2944
- followSymlinks: true,
2945
- usePolling: false,
2946
- // useAsync: false,
2947
- atomic: true,
2948
- // NOTE: overwritten later (depends on usePolling)
2949
- ..._opts,
2950
- // Change format
2951
- ignored: _opts.ignored ? arrify(_opts.ignored) : arrify([]),
2952
- awaitWriteFinish: awf === true ? DEF_AWF : typeof awf === "object" ? { ...DEF_AWF, ...awf } : false
2953
- };
2954
- if (isIBMi)
2955
- opts.usePolling = true;
2956
- if (opts.atomic === void 0)
2957
- opts.atomic = !opts.usePolling;
2958
- const envPoll = process.env.CHOKIDAR_USEPOLLING;
2959
- if (envPoll !== void 0) {
2960
- const envLower = envPoll.toLowerCase();
2961
- if (envLower === "false" || envLower === "0")
2962
- opts.usePolling = false;
2963
- else if (envLower === "true" || envLower === "1")
2964
- opts.usePolling = true;
2965
- else
2966
- opts.usePolling = !!envLower;
2967
- }
2968
- const envInterval = process.env.CHOKIDAR_INTERVAL;
2969
- if (envInterval)
2970
- opts.interval = Number.parseInt(envInterval, 10);
2971
- let readyCalls = 0;
2972
- this._emitReady = () => {
2973
- readyCalls++;
2974
- if (readyCalls >= this._readyCount) {
2975
- this._emitReady = EMPTY_FN;
2976
- this._readyEmitted = true;
2977
- process.nextTick(() => this.emit(EVENTS.READY));
2978
- }
2979
- };
2980
- this._emitRaw = (...args) => this.emit(EVENTS.RAW, ...args);
2981
- this._boundRemove = this._remove.bind(this);
2982
- this.options = opts;
2983
- this._nodeFsHandler = new NodeFsHandler(this);
2984
- Object.freeze(opts);
2985
- }
2986
- _addIgnoredPath(matcher) {
2987
- if (isMatcherObject(matcher)) {
2988
- for (const ignored of this._ignoredPaths) {
2989
- if (isMatcherObject(ignored) && ignored.path === matcher.path && ignored.recursive === matcher.recursive) {
2990
- return;
2991
- }
2992
- }
2993
- }
2994
- this._ignoredPaths.add(matcher);
2995
- }
2996
- _removeIgnoredPath(matcher) {
2997
- this._ignoredPaths.delete(matcher);
2998
- if (typeof matcher === "string") {
2999
- for (const ignored of this._ignoredPaths) {
3000
- if (isMatcherObject(ignored) && ignored.path === matcher) {
3001
- this._ignoredPaths.delete(ignored);
3002
- }
3003
- }
3004
- }
3005
- }
3006
- // Public methods
3007
- /**
3008
- * Adds paths to be watched on an existing FSWatcher instance.
3009
- * @param paths_ file or file list. Other arguments are unused
3010
- */
3011
- add(paths_, _origAdd, _internal) {
3012
- const { cwd } = this.options;
3013
- this.closed = false;
3014
- this._closePromise = void 0;
3015
- let paths = unifyPaths(paths_);
3016
- if (cwd) {
3017
- paths = paths.map((path) => {
3018
- const absPath = getAbsolutePath(path, cwd);
3019
- return absPath;
3020
- });
3021
- }
3022
- paths.forEach((path) => {
3023
- this._removeIgnoredPath(path);
3024
- });
3025
- this._userIgnored = void 0;
3026
- if (!this._readyCount)
3027
- this._readyCount = 0;
3028
- this._readyCount += paths.length;
3029
- Promise.all(paths.map(async (path) => {
3030
- const res = await this._nodeFsHandler._addToNodeFs(path, !_internal, void 0, 0, _origAdd);
3031
- if (res)
3032
- this._emitReady();
3033
- return res;
3034
- })).then((results) => {
3035
- if (this.closed)
3036
- return;
3037
- results.forEach((item) => {
3038
- if (item)
3039
- this.add(sp2.dirname(item), sp2.basename(_origAdd || item));
3040
- });
3041
- });
3042
- return this;
3043
- }
3044
- /**
3045
- * Close watchers or start ignoring events from specified paths.
3046
- */
3047
- unwatch(paths_) {
3048
- if (this.closed)
3049
- return this;
3050
- const paths = unifyPaths(paths_);
3051
- const { cwd } = this.options;
3052
- paths.forEach((path) => {
3053
- if (!sp2.isAbsolute(path) && !this._closers.has(path)) {
3054
- if (cwd)
3055
- path = sp2.join(cwd, path);
3056
- path = sp2.resolve(path);
3057
- }
3058
- this._closePath(path);
3059
- this._addIgnoredPath(path);
3060
- if (this._watched.has(path)) {
3061
- this._addIgnoredPath({
3062
- path,
3063
- recursive: true
3064
- });
3065
- }
3066
- this._userIgnored = void 0;
3067
- });
3068
- return this;
3069
- }
3070
- /**
3071
- * Close watchers and remove all listeners from watched paths.
3072
- */
3073
- close() {
3074
- if (this._closePromise) {
3075
- return this._closePromise;
3076
- }
3077
- this.closed = true;
3078
- this.removeAllListeners();
3079
- const closers = [];
3080
- this._closers.forEach((closerList) => closerList.forEach((closer) => {
3081
- const promise2 = closer();
3082
- if (promise2 instanceof Promise)
3083
- closers.push(promise2);
3084
- }));
3085
- this._streams.forEach((stream) => stream.destroy());
3086
- this._userIgnored = void 0;
3087
- this._readyCount = 0;
3088
- this._readyEmitted = false;
3089
- this._watched.forEach((dirent) => dirent.dispose());
3090
- this._closers.clear();
3091
- this._watched.clear();
3092
- this._streams.clear();
3093
- this._symlinkPaths.clear();
3094
- this._throttled.clear();
3095
- this._closePromise = closers.length ? Promise.all(closers).then(() => void 0) : Promise.resolve();
3096
- return this._closePromise;
3097
- }
3098
- /**
3099
- * Expose list of watched paths
3100
- * @returns for chaining
3101
- */
3102
- getWatched() {
3103
- const watchList = {};
3104
- this._watched.forEach((entry, dir) => {
3105
- const key = this.options.cwd ? sp2.relative(this.options.cwd, dir) : dir;
3106
- const index = key || ONE_DOT;
3107
- watchList[index] = entry.getChildren().sort();
3108
- });
3109
- return watchList;
3110
- }
3111
- emitWithAll(event, args) {
3112
- this.emit(event, ...args);
3113
- if (event !== EVENTS.ERROR)
3114
- this.emit(EVENTS.ALL, event, ...args);
3115
- }
3116
- // Common helpers
3117
- // --------------
3118
- /**
3119
- * Normalize and emit events.
3120
- * Calling _emit DOES NOT MEAN emit() would be called!
3121
- * @param event Type of event
3122
- * @param path File or directory path
3123
- * @param stats arguments to be passed with event
3124
- * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
3125
- */
3126
- async _emit(event, path, stats) {
3127
- if (this.closed)
3128
- return;
3129
- const opts = this.options;
3130
- if (isWindows)
3131
- path = sp2.normalize(path);
3132
- if (opts.cwd)
3133
- path = sp2.relative(opts.cwd, path);
3134
- const args = [path];
3135
- if (stats != null)
3136
- args.push(stats);
3137
- const awf = opts.awaitWriteFinish;
3138
- let pw;
3139
- if (awf && (pw = this._pendingWrites.get(path))) {
3140
- pw.lastChange = /* @__PURE__ */ new Date();
3141
- return this;
3142
- }
3143
- if (opts.atomic) {
3144
- if (event === EVENTS.UNLINK) {
3145
- this._pendingUnlinks.set(path, [event, ...args]);
3146
- setTimeout(() => {
3147
- this._pendingUnlinks.forEach((entry, path2) => {
3148
- this.emit(...entry);
3149
- this.emit(EVENTS.ALL, ...entry);
3150
- this._pendingUnlinks.delete(path2);
3151
- });
3152
- }, typeof opts.atomic === "number" ? opts.atomic : 100);
3153
- return this;
3154
- }
3155
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path)) {
3156
- event = EVENTS.CHANGE;
3157
- this._pendingUnlinks.delete(path);
3158
- }
3159
- }
3160
- if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
3161
- const awfEmit = (err, stats2) => {
3162
- if (err) {
3163
- event = EVENTS.ERROR;
3164
- args[0] = err;
3165
- this.emitWithAll(event, args);
3166
- } else if (stats2) {
3167
- if (args.length > 1) {
3168
- args[1] = stats2;
3169
- } else {
3170
- args.push(stats2);
3171
- }
3172
- this.emitWithAll(event, args);
3173
- }
3174
- };
3175
- this._awaitWriteFinish(path, awf.stabilityThreshold, event, awfEmit);
3176
- return this;
3177
- }
3178
- if (event === EVENTS.CHANGE) {
3179
- const isThrottled = !this._throttle(EVENTS.CHANGE, path, 50);
3180
- if (isThrottled)
3181
- return this;
3182
- }
3183
- if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
3184
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path) : path;
3185
- let stats2;
3186
- try {
3187
- stats2 = await stat(fullPath);
3188
- } catch (err) {
3189
- }
3190
- if (!stats2 || this.closed)
3191
- return;
3192
- args.push(stats2);
3193
- }
3194
- this.emitWithAll(event, args);
3195
- return this;
3196
- }
3197
- /**
3198
- * Common handler for errors
3199
- * @returns The error if defined, otherwise the value of the FSWatcher instance's `closed` flag
3200
- */
3201
- _handleError(error) {
3202
- const code = error && error.code;
3203
- if (error && code !== "ENOENT" && code !== "ENOTDIR" && (!this.options.ignorePermissionErrors || code !== "EPERM" && code !== "EACCES")) {
3204
- this.emit(EVENTS.ERROR, error);
3205
- }
3206
- return error || this.closed;
3207
- }
3208
- /**
3209
- * Helper utility for throttling
3210
- * @param actionType type being throttled
3211
- * @param path being acted upon
3212
- * @param timeout duration of time to suppress duplicate actions
3213
- * @returns tracking object or false if action should be suppressed
3214
- */
3215
- _throttle(actionType, path, timeout) {
3216
- if (!this._throttled.has(actionType)) {
3217
- this._throttled.set(actionType, /* @__PURE__ */ new Map());
3218
- }
3219
- const action = this._throttled.get(actionType);
3220
- if (!action)
3221
- throw new Error("invalid throttle");
3222
- const actionPath = action.get(path);
3223
- if (actionPath) {
3224
- actionPath.count++;
3225
- return false;
3226
- }
3227
- let timeoutObject;
3228
- const clear = () => {
3229
- const item = action.get(path);
3230
- const count = item ? item.count : 0;
3231
- action.delete(path);
3232
- clearTimeout(timeoutObject);
3233
- if (item)
3234
- clearTimeout(item.timeoutObject);
3235
- return count;
3236
- };
3237
- timeoutObject = setTimeout(clear, timeout);
3238
- const thr = { timeoutObject, clear, count: 0 };
3239
- action.set(path, thr);
3240
- return thr;
3241
- }
3242
- _incrReadyCount() {
3243
- return this._readyCount++;
3244
- }
3245
- /**
3246
- * Awaits write operation to finish.
3247
- * Polls a newly created file for size variations. When files size does not change for 'threshold' milliseconds calls callback.
3248
- * @param path being acted upon
3249
- * @param threshold Time in milliseconds a file size must be fixed before acknowledging write OP is finished
3250
- * @param event
3251
- * @param awfEmit Callback to be called when ready for event to be emitted.
3252
- */
3253
- _awaitWriteFinish(path, threshold, event, awfEmit) {
3254
- const awf = this.options.awaitWriteFinish;
3255
- if (typeof awf !== "object")
3256
- return;
3257
- const pollInterval = awf.pollInterval;
3258
- let timeoutHandler;
3259
- let fullPath = path;
3260
- if (this.options.cwd && !sp2.isAbsolute(path)) {
3261
- fullPath = sp2.join(this.options.cwd, path);
3262
- }
3263
- const now = /* @__PURE__ */ new Date();
3264
- const writes = this._pendingWrites;
3265
- function awaitWriteFinishFn(prevStat) {
3266
- stat$1(fullPath, (err, curStat) => {
3267
- if (err || !writes.has(path)) {
3268
- if (err && err.code !== "ENOENT")
3269
- awfEmit(err);
3270
- return;
3271
- }
3272
- const now2 = Number(/* @__PURE__ */ new Date());
3273
- if (prevStat && curStat.size !== prevStat.size) {
3274
- writes.get(path).lastChange = now2;
3275
- }
3276
- const pw = writes.get(path);
3277
- const df = now2 - pw.lastChange;
3278
- if (df >= threshold) {
3279
- writes.delete(path);
3280
- awfEmit(void 0, curStat);
3281
- } else {
3282
- timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
3283
- }
3284
- });
3285
- }
3286
- if (!writes.has(path)) {
3287
- writes.set(path, {
3288
- lastChange: now,
3289
- cancelWait: () => {
3290
- writes.delete(path);
3291
- clearTimeout(timeoutHandler);
3292
- return event;
3293
- }
3294
- });
3295
- timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
3296
- }
3297
- }
3298
- /**
3299
- * Determines whether user has asked to ignore this path.
3300
- */
3301
- _isIgnored(path, stats) {
3302
- if (this.options.atomic && DOT_RE.test(path))
3303
- return true;
3304
- if (!this._userIgnored) {
3305
- const { cwd } = this.options;
3306
- const ign = this.options.ignored;
3307
- const ignored = (ign || []).map(normalizeIgnored(cwd));
3308
- const ignoredPaths = [...this._ignoredPaths];
3309
- const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
3310
- this._userIgnored = anymatch(list);
3311
- }
3312
- return this._userIgnored(path, stats);
3313
- }
3314
- _isntIgnored(path, stat4) {
3315
- return !this._isIgnored(path, stat4);
3316
- }
3317
- /**
3318
- * Provides a set of common helpers and properties relating to symlink handling.
3319
- * @param path file or directory pattern being watched
3320
- */
3321
- _getWatchHelpers(path) {
3322
- return new WatchHelper(path, this.options.followSymlinks, this);
3323
- }
3324
- // Directory helpers
3325
- // -----------------
3326
- /**
3327
- * Provides directory tracking objects
3328
- * @param directory path of the directory
3329
- */
3330
- _getWatchedDir(directory) {
3331
- const dir = sp2.resolve(directory);
3332
- if (!this._watched.has(dir))
3333
- this._watched.set(dir, new DirEntry(dir, this._boundRemove));
3334
- return this._watched.get(dir);
3335
- }
3336
- // File helpers
3337
- // ------------
3338
- /**
3339
- * Check for read permissions: https://stackoverflow.com/a/11781404/1358405
3340
- */
3341
- _hasReadPermissions(stats) {
3342
- if (this.options.ignorePermissionErrors)
3343
- return true;
3344
- return Boolean(Number(stats.mode) & 256);
3345
- }
3346
- /**
3347
- * Handles emitting unlink events for
3348
- * files and directories, and via recursion, for
3349
- * files and directories within directories that are unlinked
3350
- * @param directory within which the following item is located
3351
- * @param item base path of item/directory
3352
- */
3353
- _remove(directory, item, isDirectory) {
3354
- const path = sp2.join(directory, item);
3355
- const fullPath = sp2.resolve(path);
3356
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path) || this._watched.has(fullPath);
3357
- if (!this._throttle("remove", path, 100))
3358
- return;
3359
- if (!isDirectory && this._watched.size === 1) {
3360
- this.add(directory, item, true);
3361
- }
3362
- const wp = this._getWatchedDir(path);
3363
- const nestedDirectoryChildren = wp.getChildren();
3364
- nestedDirectoryChildren.forEach((nested) => this._remove(path, nested));
3365
- const parent = this._getWatchedDir(directory);
3366
- const wasTracked = parent.has(item);
3367
- parent.remove(item);
3368
- if (this._symlinkPaths.has(fullPath)) {
3369
- this._symlinkPaths.delete(fullPath);
3370
- }
3371
- let relPath = path;
3372
- if (this.options.cwd)
3373
- relPath = sp2.relative(this.options.cwd, path);
3374
- if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
3375
- const event = this._pendingWrites.get(relPath).cancelWait();
3376
- if (event === EVENTS.ADD)
3377
- return;
3378
- }
3379
- this._watched.delete(path);
3380
- this._watched.delete(fullPath);
3381
- const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
3382
- if (wasTracked && !this._isIgnored(path))
3383
- this._emit(eventName, path);
3384
- this._closePath(path);
3385
- }
3386
- /**
3387
- * Closes all watchers for a path
3388
- */
3389
- _closePath(path) {
3390
- this._closeFile(path);
3391
- const dir = sp2.dirname(path);
3392
- this._getWatchedDir(dir).remove(sp2.basename(path));
3393
- }
3394
- /**
3395
- * Closes only file-specific watchers
3396
- */
3397
- _closeFile(path) {
3398
- const closers = this._closers.get(path);
3399
- if (!closers)
3400
- return;
3401
- closers.forEach((closer) => closer());
3402
- this._closers.delete(path);
3403
- }
3404
- _addPathCloser(path, closer) {
3405
- if (!closer)
3406
- return;
3407
- let list = this._closers.get(path);
3408
- if (!list) {
3409
- list = [];
3410
- this._closers.set(path, list);
3411
- }
3412
- list.push(closer);
3413
- }
3414
- _readdirp(root, opts) {
3415
- if (this.closed)
3416
- return;
3417
- const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
3418
- let stream = readdirp(root, options);
3419
- this._streams.add(stream);
3420
- stream.once(STR_CLOSE, () => {
3421
- stream = void 0;
3422
- });
3423
- stream.once(STR_END, () => {
3424
- if (stream) {
3425
- this._streams.delete(stream);
3426
- stream = void 0;
3427
- }
3428
- });
3429
- return stream;
3430
- }
3431
- };
3432
- function watch(paths, options = {}) {
3433
- const watcher = new FSWatcher(options);
3434
- watcher.add(paths);
3435
- return watcher;
3436
- }
3437
- var chokidar_default = { watch, FSWatcher };
3438
- var EventService = class extends Effect4.Service()(
3439
- "@ringi/EventService",
3440
- {
3441
- effect: Effect4.gen(function* effect() {
3442
- const rt = yield* Effect4.runtime();
3443
- const runFork2 = Runtime.runFork(rt);
3444
- const subscribers = /* @__PURE__ */ new Set();
3445
- const broadcast = Effect4.fn("EventService.broadcast")(function* broadcast2(type, data) {
3446
- const event = { data, timestamp: Date.now(), type };
3447
- for (const queue of subscribers) {
3448
- yield* Queue.offer(queue, event);
3449
- }
3450
- });
3451
- const subscribe = Effect4.fn("EventService.subscribe")(
3452
- function* subscribe2() {
3453
- const queue = yield* Queue.sliding(100);
3454
- subscribers.add(queue);
3455
- const stream = Stream.fromQueue(queue);
3456
- const unsubscribe = Effect4.sync(() => {
3457
- subscribers.delete(queue);
3458
- }).pipe(Effect4.andThen(Queue.shutdown(queue)));
3459
- return { stream, unsubscribe };
3460
- }
3461
- );
3462
- const startFileWatcher = (repoPath) => Effect4.acquireRelease(
3463
- Effect4.sync(() => {
3464
- let debounceTimer = null;
3465
- const watcher = chokidar_default.watch(repoPath, {
3466
- ignoreInitial: true,
3467
- ignored: [
3468
- "**/node_modules/**",
3469
- "**/.git/**",
3470
- "**/.ringi/**",
3471
- "**/dist/**"
3472
- ],
3473
- persistent: true,
3474
- ...platform() === "darwin" ? { interval: 1e3, usePolling: true } : {}
3475
- });
3476
- const debouncedBroadcast = (filePath) => {
3477
- if (debounceTimer) {
3478
- clearTimeout(debounceTimer);
3479
- }
3480
- debounceTimer = setTimeout(() => {
3481
- const rel = relative(repoPath, filePath);
3482
- runFork2(broadcast("files", { path: rel }));
3483
- }, 300);
3484
- };
3485
- watcher.on("add", debouncedBroadcast);
3486
- watcher.on("change", debouncedBroadcast);
3487
- watcher.on("unlink", debouncedBroadcast);
3488
- return watcher;
3489
- }),
3490
- (watcher) => Effect4.promise(() => watcher.close())
3491
- );
3492
- const getClientCount = () => Effect4.sync(() => subscribers.size);
3493
- return {
3494
- broadcast,
3495
- getClientCount,
3496
- startFileWatcher,
3497
- subscribe
3498
- };
3499
- })
3500
- }
3501
- ) {
3502
- };
3503
-
3504
- // ../../packages/core/src/runtime.ts
3505
- var CoreLive = Layer.mergeAll(
3506
- ReviewService.Default,
3507
- ReviewRepo.Default,
3508
- ReviewFileRepo.Default,
3509
- CommentService.Default,
3510
- CommentRepo.Default,
3511
- TodoService.Default,
3512
- TodoRepo.Default,
3513
- GitService.Default,
3514
- EventService.Default,
3515
- ExportService.Default,
3516
- SqliteService.Default
3517
- );
3518
- /*! Bundled license information:
3519
-
3520
- chokidar/index.js:
3521
- (*! chokidar - MIT License (c) 2012 Paul Miller (paulmillr.com) *)
3522
- */
3523
-
3524
- export { CommentService, CoreLive, ExportService, GitService, ReviewId, ReviewNotFound, ReviewService, ReviewSourceType, TodoId, TodoNotFound, TodoService, getDiffSummary, parseDiff };
3525
- //# sourceMappingURL=chunk-KMYSGMD3.js.map
3526
- //# sourceMappingURL=chunk-KMYSGMD3.js.map