@webpieces/pr-gate 0.4.493 → 0.4.494
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/pr-gate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.494",
|
|
4
4
|
"description": "Gated PR system: 3-point squash-merge, merge validation gate, and red/yellow/green PR dashboard. Standalone scripts, no Nx dependency required.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"directory": "packages/tooling/pr-gate"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@webpieces/rules-config": "0.4.
|
|
28
|
+
"@webpieces/rules-config": "0.4.494",
|
|
29
29
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
30
30
|
"inversify": "7.10.4",
|
|
31
31
|
"reflect-metadata": "0.2.2"
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { RepoRootFinder } from '@webpieces/rules-config';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* 30-day garbage collection of the whole `.webpieces` tree.
|
|
4
|
+
*
|
|
5
|
+
* Runs at the end of every merge/PR flow (see merge-end.ts). It walks `.webpieces` depth-first and:
|
|
6
|
+
* 1. deletes ANY file whose mtime is older than the cutoff, anywhere in the tree, and
|
|
7
|
+
* 2. removes ANY directory that is left empty afterwards (including dirs that were already empty).
|
|
8
|
+
*
|
|
9
|
+
* The `.webpieces` root itself is never removed. Everything the tooling still needs is rewritten on
|
|
10
|
+
* each run under a fresh mtime (instruct-ai/ docs, the active hooks/ logs, the *-status.json state
|
|
11
|
+
* files), and every writer `mkdirSync(..., { recursive: true })`s its target first — so pruning a
|
|
12
|
+
* stale home dir is harmless; the next write recreates it. Stale per-feature merge-info/pr-review
|
|
13
|
+
* subdirs and the retired legacy flat layout all fall out of this single pass with no special-casing.
|
|
14
|
+
*/
|
|
3
15
|
export declare class CleanTmp {
|
|
4
16
|
private readonly repoRootFinder;
|
|
5
17
|
constructor(repoRootFinder: RepoRootFinder);
|
|
6
18
|
cleanTmp(): Promise<void>;
|
|
7
|
-
private
|
|
8
|
-
private cleanLegacyTopLevel;
|
|
19
|
+
private sweep;
|
|
9
20
|
}
|
|
@@ -8,12 +8,28 @@ const rules_config_1 = require("@webpieces/rules-config");
|
|
|
8
8
|
const inversify_1 = require("inversify");
|
|
9
9
|
const CUTOFF_DAYS = 30;
|
|
10
10
|
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
// Result of one sweep: how many files were reaped for age, and how many now-empty dirs were pruned.
|
|
12
|
+
class SweepResult {
|
|
13
|
+
files;
|
|
14
|
+
dirs;
|
|
15
|
+
constructor(files, dirs) {
|
|
16
|
+
this.files = files;
|
|
17
|
+
this.dirs = dirs;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 30-day garbage collection of the whole `.webpieces` tree.
|
|
22
|
+
*
|
|
23
|
+
* Runs at the end of every merge/PR flow (see merge-end.ts). It walks `.webpieces` depth-first and:
|
|
24
|
+
* 1. deletes ANY file whose mtime is older than the cutoff, anywhere in the tree, and
|
|
25
|
+
* 2. removes ANY directory that is left empty afterwards (including dirs that were already empty).
|
|
26
|
+
*
|
|
27
|
+
* The `.webpieces` root itself is never removed. Everything the tooling still needs is rewritten on
|
|
28
|
+
* each run under a fresh mtime (instruct-ai/ docs, the active hooks/ logs, the *-status.json state
|
|
29
|
+
* files), and every writer `mkdirSync(..., { recursive: true })`s its target first — so pruning a
|
|
30
|
+
* stale home dir is harmless; the next write recreates it. Stale per-feature merge-info/pr-review
|
|
31
|
+
* subdirs and the retired legacy flat layout all fall out of this single pass with no special-casing.
|
|
32
|
+
*/
|
|
17
33
|
let CleanTmp = class CleanTmp {
|
|
18
34
|
repoRootFinder;
|
|
19
35
|
constructor(repoRootFinder) {
|
|
@@ -27,69 +43,58 @@ let CleanTmp = class CleanTmp {
|
|
|
27
43
|
}
|
|
28
44
|
process.stdout.write('\n');
|
|
29
45
|
process.stdout.write(SEP);
|
|
30
|
-
process.stdout.write('🧹
|
|
46
|
+
process.stdout.write('🧹 Garbage-Collecting .webpieces\n');
|
|
31
47
|
process.stdout.write(SEP);
|
|
32
48
|
process.stdout.write('\n');
|
|
33
49
|
process.stdout.write(`Location: ${tmpBase}\n`);
|
|
34
|
-
process.stdout.write(`Retention: ${CUTOFF_DAYS} days\n`);
|
|
50
|
+
process.stdout.write(`Retention: ${CUTOFF_DAYS} days (older files reaped; empty dirs pruned)\n`);
|
|
35
51
|
process.stdout.write('\n');
|
|
36
52
|
const cutoffMs = CUTOFF_DAYS * 24 * 60 * 60 * 1000;
|
|
37
53
|
const now = Date.now();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// Legacy flat top-level dirs from before the merge-info/pr-review nesting (also reaps old pr-info/).
|
|
43
|
-
deletedCount += this.cleanLegacyTopLevel(tmpBase, now, cutoffMs);
|
|
44
|
-
if (deletedCount === 0) {
|
|
45
|
-
process.stdout.write(` ✅ No directories older than ${CUTOFF_DAYS} days found\n`);
|
|
54
|
+
// `true` => this is the root, which is swept into but never itself removed.
|
|
55
|
+
const result = this.sweep(tmpBase, tmpBase, now, cutoffMs, true);
|
|
56
|
+
if (result.files === 0 && result.dirs === 0) {
|
|
57
|
+
process.stdout.write(` ✅ Nothing older than ${CUTOFF_DAYS} days; no empty directories\n`);
|
|
46
58
|
}
|
|
47
59
|
else {
|
|
60
|
+
const fileWord = result.files === 1 ? 'file' : 'files';
|
|
61
|
+
const dirWord = result.dirs === 1 ? 'directory' : 'directories';
|
|
48
62
|
process.stdout.write('\n');
|
|
49
|
-
process.stdout.write(` ✅
|
|
63
|
+
process.stdout.write(` ✅ Reaped ${result.files} old ${fileWord} and pruned ${result.dirs} empty ${dirWord}\n`);
|
|
50
64
|
}
|
|
51
65
|
process.stdout.write('\n');
|
|
52
66
|
process.stdout.write(SEP);
|
|
53
67
|
process.stdout.write('\n');
|
|
54
68
|
}
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
// Depth-first, post-order sweep of `dir` (rooted at `tmpBase`, only used for tidy relative logging).
|
|
70
|
+
// Files older than the cutoff are deleted; a directory left empty once its children are processed is
|
|
71
|
+
// pruned — except the root, which is kept even when empty so `.webpieces/` itself survives.
|
|
72
|
+
sweep(dir, tmpBase, now, cutoffMs, isRoot) {
|
|
73
|
+
let files = 0;
|
|
74
|
+
let dirs = 0;
|
|
75
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
76
|
+
const fullPath = path.join(dir, entry);
|
|
77
|
+
// lstat, not stat: a symlink is treated as a leaf (aged out like a file), never followed —
|
|
78
|
+
// so we can never wander outside `.webpieces` or delete a link target elsewhere.
|
|
79
|
+
const stat = fs.lstatSync(fullPath);
|
|
80
|
+
if (stat.isDirectory()) {
|
|
81
|
+
const nested = this.sweep(fullPath, tmpBase, now, cutoffMs, false);
|
|
82
|
+
files += nested.files;
|
|
83
|
+
dirs += nested.dirs;
|
|
84
|
+
}
|
|
85
|
+
else if (now - stat.mtimeMs >= cutoffMs) {
|
|
86
|
+
process.stdout.write(` 🗑️ file: ${path.relative(tmpBase, fullPath)}\n`);
|
|
87
|
+
fs.rmSync(fullPath, { force: true });
|
|
88
|
+
files += 1;
|
|
89
|
+
}
|
|
70
90
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
let count = 0;
|
|
77
|
-
for (const entry of fs.readdirSync(tmpBase)) {
|
|
78
|
-
if (entry === rules_config_1.MERGE_INFO_DIR || entry === rules_config_1.PR_REVIEW_DIR)
|
|
79
|
-
continue;
|
|
80
|
-
if (!LEGACY_PREFIXES.some((prefix) => entry.startsWith(prefix)))
|
|
81
|
-
continue;
|
|
82
|
-
const fullPath = path.join(tmpBase, entry);
|
|
83
|
-
const stat = fs.statSync(fullPath);
|
|
84
|
-
if (!stat.isDirectory())
|
|
85
|
-
continue;
|
|
86
|
-
if (now - stat.mtimeMs < cutoffMs)
|
|
87
|
-
continue;
|
|
88
|
-
process.stdout.write(` 🗑️ Deleting: ${entry}\n`);
|
|
89
|
-
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
90
|
-
count += 1;
|
|
91
|
+
// Post-order: prune this dir if reaping its children (or nothing) left it empty.
|
|
92
|
+
if (!isRoot && fs.readdirSync(dir).length === 0) {
|
|
93
|
+
process.stdout.write(` 🗑️ dir: ${path.relative(tmpBase, dir)}/\n`);
|
|
94
|
+
fs.rmdirSync(dir);
|
|
95
|
+
dirs += 1;
|
|
91
96
|
}
|
|
92
|
-
return
|
|
97
|
+
return new SweepResult(files, dirs);
|
|
93
98
|
}
|
|
94
99
|
};
|
|
95
100
|
exports.CleanTmp = CleanTmp;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleanTmp.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/cleanTmp.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAC7B,
|
|
1
|
+
{"version":3,"file":"cleanTmp.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/cleanTmp.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAC7B,0DAA4E;AAC5E,yCAA2D;AAE3D,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,oGAAoG;AACpG,MAAM,WAAW;IAEF;IACA;IAFX,YACW,KAAa,EACb,IAAY;QADZ,UAAK,GAAL,KAAK,CAAQ;QACb,SAAI,GAAJ,IAAI,CAAQ;IACpB,CAAC;CACP;AAED;;;;;;;;;;;;GAYG;AAEI,IAAM,QAAQ,GAAd,MAAM,QAAQ;IACY;IAA7B,YAA6B,cAA8B;QAA9B,mBAAc,GAAd,cAAc,CAAgB;IAAG,CAAC;IAE/D,KAAK,CAAC,QAAQ;QACV,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACpE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,gCAAiB,CAAC,CAAC;QAEvD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1B,OAAO;QACX,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QAC3D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,OAAO,IAAI,CAAC,CAAC;QAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,WAAW,iDAAiD,CAAC,CAAC;QACjG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAE3B,MAAM,QAAQ,GAAG,WAAW,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QACnD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,4EAA4E;QAC5E,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QAEjE,IAAI,MAAM,CAAC,KAAK,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,WAAW,+BAA+B,CAAC,CAAC;QAC/F,CAAC;aAAM,CAAC;YACJ,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;YACvD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC;YAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,MAAM,CAAC,KAAK,QAAQ,QAAQ,eAAe,MAAM,CAAC,IAAI,UAAU,OAAO,IAAI,CAAC,CAAC;QACpH,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,qGAAqG;IACrG,qGAAqG;IACrG,4FAA4F;IACpF,KAAK,CAAC,GAAW,EAAE,OAAe,EAAE,GAAW,EAAE,QAAgB,EAAE,MAAe;QACtF,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,IAAI,GAAG,CAAC,CAAC;QAEb,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;YACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACvC,2FAA2F;YAC3F,iFAAiF;YACjF,MAAM,IAAI,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YACpC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;gBACrB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;gBACnE,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC;gBACtB,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC;YACxB,CAAC;iBAAM,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,QAAQ,EAAE,CAAC;gBACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;gBAC5E,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACrC,KAAK,IAAI,CAAC,CAAC;YACf,CAAC;QACL,CAAC;QAED,iFAAiF;QACjF,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;YACxE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAClB,IAAI,IAAI,CAAC,CAAC;QACd,CAAC;QAED,OAAO,IAAI,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACxC,CAAC;CACJ,CAAA;AAxEY,4BAAQ;mBAAR,QAAQ;IADpB,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAEQ,6BAAc;GADlD,QAAQ,CAwEpB","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { WEBPIECES_TMP_DIR, RepoRootFinder } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nconst CUTOFF_DAYS = 30;\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// Result of one sweep: how many files were reaped for age, and how many now-empty dirs were pruned.\nclass SweepResult {\n constructor(\n public files: number,\n public dirs: number,\n ) {}\n}\n\n/**\n * 30-day garbage collection of the whole `.webpieces` tree.\n *\n * Runs at the end of every merge/PR flow (see merge-end.ts). It walks `.webpieces` depth-first and:\n * 1. deletes ANY file whose mtime is older than the cutoff, anywhere in the tree, and\n * 2. removes ANY directory that is left empty afterwards (including dirs that were already empty).\n *\n * The `.webpieces` root itself is never removed. Everything the tooling still needs is rewritten on\n * each run under a fresh mtime (instruct-ai/ docs, the active hooks/ logs, the *-status.json state\n * files), and every writer `mkdirSync(..., { recursive: true })`s its target first — so pruning a\n * stale home dir is harmless; the next write recreates it. Stale per-feature merge-info/pr-review\n * subdirs and the retired legacy flat layout all fall out of this single pass with no special-casing.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class CleanTmp {\n constructor(private readonly repoRootFinder: RepoRootFinder) {}\n\n async cleanTmp(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n const tmpBase = path.join(repoRoot, WEBPIECES_TMP_DIR);\n\n if (!fs.existsSync(tmpBase)) {\n return;\n }\n\n process.stdout.write('\\n');\n process.stdout.write(SEP);\n process.stdout.write('🧹 Garbage-Collecting .webpieces\\n');\n process.stdout.write(SEP);\n process.stdout.write('\\n');\n process.stdout.write(`Location: ${tmpBase}\\n`);\n process.stdout.write(`Retention: ${CUTOFF_DAYS} days (older files reaped; empty dirs pruned)\\n`);\n process.stdout.write('\\n');\n\n const cutoffMs = CUTOFF_DAYS * 24 * 60 * 60 * 1000;\n const now = Date.now();\n\n // `true` => this is the root, which is swept into but never itself removed.\n const result = this.sweep(tmpBase, tmpBase, now, cutoffMs, true);\n\n if (result.files === 0 && result.dirs === 0) {\n process.stdout.write(` ✅ Nothing older than ${CUTOFF_DAYS} days; no empty directories\\n`);\n } else {\n const fileWord = result.files === 1 ? 'file' : 'files';\n const dirWord = result.dirs === 1 ? 'directory' : 'directories';\n process.stdout.write('\\n');\n process.stdout.write(` ✅ Reaped ${result.files} old ${fileWord} and pruned ${result.dirs} empty ${dirWord}\\n`);\n }\n\n process.stdout.write('\\n');\n process.stdout.write(SEP);\n process.stdout.write('\\n');\n }\n\n // Depth-first, post-order sweep of `dir` (rooted at `tmpBase`, only used for tidy relative logging).\n // Files older than the cutoff are deleted; a directory left empty once its children are processed is\n // pruned — except the root, which is kept even when empty so `.webpieces/` itself survives.\n private sweep(dir: string, tmpBase: string, now: number, cutoffMs: number, isRoot: boolean): SweepResult {\n let files = 0;\n let dirs = 0;\n\n for (const entry of fs.readdirSync(dir)) {\n const fullPath = path.join(dir, entry);\n // lstat, not stat: a symlink is treated as a leaf (aged out like a file), never followed —\n // so we can never wander outside `.webpieces` or delete a link target elsewhere.\n const stat = fs.lstatSync(fullPath);\n if (stat.isDirectory()) {\n const nested = this.sweep(fullPath, tmpBase, now, cutoffMs, false);\n files += nested.files;\n dirs += nested.dirs;\n } else if (now - stat.mtimeMs >= cutoffMs) {\n process.stdout.write(` 🗑️ file: ${path.relative(tmpBase, fullPath)}\\n`);\n fs.rmSync(fullPath, { force: true });\n files += 1;\n }\n }\n\n // Post-order: prune this dir if reaping its children (or nothing) left it empty.\n if (!isRoot && fs.readdirSync(dir).length === 0) {\n process.stdout.write(` 🗑️ dir: ${path.relative(tmpBase, dir)}/\\n`);\n fs.rmdirSync(dir);\n dirs += 1;\n }\n\n return new SweepResult(files, dirs);\n }\n}\n"]}
|