@coderook/cli 0.30.0 → 0.30.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.
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "coderook",
|
|
3
3
|
"displayName": "CodeRook",
|
|
4
4
|
"description": "Save, browse and restore whole-snapshot versions of a project on CodeRook, from Claude Code.",
|
|
5
|
-
"version": "0.30.
|
|
5
|
+
"version": "0.30.1",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ACCA Gaming Productions",
|
|
8
8
|
"url": "https://coderook.com"
|
|
@@ -60,24 +60,24 @@ const RULES = [
|
|
|
60
60
|
project where the question "what should I leave out?" has the largest
|
|
61
61
|
possible answer was the one the detector had nothing to say about.
|
|
62
62
|
*/
|
|
63
|
-
{ directory: "Library", reason: "Unity's import cache — rebuilt when the project next opens", recommended: true },
|
|
64
|
-
{ directory: "Temp", reason: "Unity's scratch folder for the running editor", recommended: true },
|
|
65
|
-
{ directory: "MemoryCaptures", reason: "Unity memory snapshots — large, and not the project", recommended: true },
|
|
66
|
-
{ directory: "Recordings", reason: "Unity recorder output", recommended: true },
|
|
67
|
-
{ directory: "UserSettings", reason: "Your own editor layout — not part of the project", recommended: true },
|
|
68
|
-
{ directory: "DerivedDataCache", reason: "Unreal's derived data cache — rebuilt on demand", recommended: true },
|
|
69
|
-
{ directory: "Intermediate", reason: "Unreal build intermediates — rebuilt when you build", recommended: true },
|
|
70
|
-
{ directory: "Binaries", reason: "Unreal compiled output — rebuilt when you build", recommended: true },
|
|
63
|
+
{ directory: "Library", atRoot: true, reason: "Unity's import cache — rebuilt when the project next opens", recommended: true },
|
|
64
|
+
{ directory: "Temp", atRoot: true, reason: "Unity's scratch folder for the running editor", recommended: true },
|
|
65
|
+
{ directory: "MemoryCaptures", atRoot: true, reason: "Unity memory snapshots — large, and not the project", recommended: true },
|
|
66
|
+
{ directory: "Recordings", atRoot: true, reason: "Unity recorder output", recommended: true },
|
|
67
|
+
{ directory: "UserSettings", atRoot: true, reason: "Your own editor layout — not part of the project", recommended: true },
|
|
68
|
+
{ directory: "DerivedDataCache", atRoot: true, reason: "Unreal's derived data cache — rebuilt on demand", recommended: true },
|
|
69
|
+
{ directory: "Intermediate", atRoot: true, reason: "Unreal build intermediates — rebuilt when you build", recommended: true },
|
|
70
|
+
{ directory: "Binaries", atRoot: true, reason: "Unreal compiled output — rebuilt when you build", recommended: true },
|
|
71
71
|
/*
|
|
72
72
|
Named like the work, and it is not: Unreal keeps logs, crash reports and
|
|
73
73
|
autosaves here. Offered rather than ticked, because a folder called
|
|
74
74
|
"Saved" is the one nobody should have excluded on our say-so.
|
|
75
75
|
*/
|
|
76
|
-
{ directory: "Saved", reason: "Unreal logs and autosaves — check before excluding", recommended: false },
|
|
77
|
-
{ directory: ".godot", reason: "Godot's import cache — rebuilt when the project next opens", recommended: true },
|
|
78
|
-
{ directory: ".import", reason: "Godot's import cache — rebuilt when the project next opens", recommended: true },
|
|
79
|
-
{ directory: "Logs", reason: "Editor logs", recommended: true },
|
|
80
|
-
{ directory: "obj", reason: "Compiler intermediates — rebuilt when you build", recommended: true },
|
|
76
|
+
{ directory: "Saved", atRoot: true, reason: "Unreal logs and autosaves — check before excluding", recommended: false },
|
|
77
|
+
{ directory: ".godot", atRoot: true, reason: "Godot's import cache — rebuilt when the project next opens", recommended: true },
|
|
78
|
+
{ directory: ".import", atRoot: true, reason: "Godot's import cache — rebuilt when the project next opens", recommended: true },
|
|
79
|
+
{ directory: "Logs", atRoot: true, reason: "Editor logs", recommended: true },
|
|
80
|
+
{ directory: "obj", atRoot: true, reason: "Compiler intermediates — rebuilt when you build", recommended: true },
|
|
81
81
|
{ directory: ".vs", reason: "Visual Studio's local cache", recommended: true },
|
|
82
82
|
/*
|
|
83
83
|
Offered, not ticked. JetBrains keeps run configurations in here that some
|
|
@@ -104,7 +104,7 @@ const RULES = [
|
|
|
104
104
|
{ directory: "dist", reason: "Usually build output — check before excluding", recommended: false },
|
|
105
105
|
{ directory: "build", reason: "Usually build output — check before excluding", recommended: false },
|
|
106
106
|
{ directory: "out", reason: "Usually build output — check before excluding", recommended: false },
|
|
107
|
-
{ directory: "bin", reason: "Usually build output — check before excluding", recommended: false },
|
|
107
|
+
{ directory: "bin", atRoot: true, reason: "Usually build output — check before excluding", recommended: false },
|
|
108
108
|
{ extension: ".safetensors", reason: "Model weights — large, and usually downloadable", recommended: false },
|
|
109
109
|
{ extension: ".ckpt", reason: "Model weights — large, and usually downloadable", recommended: false },
|
|
110
110
|
{ extension: ".pt", reason: "Model weights — large, and usually downloadable", recommended: false },
|
|
@@ -222,11 +222,17 @@ function suggestExclusions(files) {
|
|
|
222
222
|
gets written matching the folder that is there.
|
|
223
223
|
*/
|
|
224
224
|
const wanted = rule.directory.toLowerCase();
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
/*
|
|
226
|
+
At the root, where the rule is about the project's own output, and
|
|
227
|
+
written anchored so it stays there. Unanchored, this offered to
|
|
228
|
+
leave out `Assets/Props/obj` — a folder of 3D models — because a
|
|
229
|
+
C# build also writes to a folder called obj. The file it proposed
|
|
230
|
+
removing was the one the rules had just been fixed to protect.
|
|
231
|
+
*/
|
|
232
|
+
const within = rule.atRoot ? segments.slice(0, 1) : segments.slice(0, -1);
|
|
233
|
+
const found = within.find((segment) => segment.toLowerCase() === wanted);
|
|
228
234
|
if (found)
|
|
229
|
-
pattern = `${found}/`;
|
|
235
|
+
pattern = rule.atRoot ? `/${found}/` : `${found}/`;
|
|
230
236
|
}
|
|
231
237
|
if (!pattern && rule.file && name === rule.file) {
|
|
232
238
|
pattern = rule.file;
|
|
@@ -132,17 +132,36 @@ function matcher(pattern) {
|
|
|
132
132
|
}
|
|
133
133
|
return expression;
|
|
134
134
|
}
|
|
135
|
+
/** A directory pattern with or without its trailing slash is the same rule. */
|
|
136
|
+
function trailingSlashOff(pattern) {
|
|
137
|
+
let end = pattern.length;
|
|
138
|
+
while (end > 0 && pattern[end - 1] === "/")
|
|
139
|
+
end -= 1;
|
|
140
|
+
return pattern.slice(0, end);
|
|
141
|
+
}
|
|
135
142
|
function ruleMatches(relativePath, isDirectory, rule) {
|
|
136
143
|
const expression = matcher(rule.pattern);
|
|
137
144
|
if (rule.directoryOnly) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
/*
|
|
146
|
+
A rule written `dir/` speaks about directories and about nothing else.
|
|
147
|
+
Everything under an excluded directory goes too, but that is git
|
|
148
|
+
declining to look inside it rather than the pattern reaching down, and
|
|
149
|
+
the difference shows the moment a later rule re-includes the directory:
|
|
150
|
+
`build/` then `!build/` leaves `build/keep.txt` travelling, which is the
|
|
151
|
+
escape hatch people are told to use. It shows again in reverse, which is
|
|
152
|
+
how this was found — asking `!Library/PackageCache/a.package/` about a
|
|
153
|
+
file let the whole cache out of a folder only meant to be opened.
|
|
154
|
+
|
|
155
|
+
So the pattern answers only for directories, and a file's ancestors are
|
|
156
|
+
asked separately, outermost in, by `decidingRule`.
|
|
157
|
+
*/
|
|
158
|
+
if (!isDirectory)
|
|
159
|
+
return false;
|
|
160
|
+
const bare = trailingSlashOff(rule.pattern);
|
|
161
|
+
if (rule.anchored || bare.includes("/"))
|
|
162
|
+
return expression.test(relativePath);
|
|
163
|
+
const own = relativePath.split("/").pop() ?? relativePath;
|
|
164
|
+
return expression.test(own);
|
|
146
165
|
}
|
|
147
166
|
if (rule.anchored || rule.pattern.includes("/")) {
|
|
148
167
|
return expression.test(relativePath);
|
|
@@ -3,13 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.LOCAL_EXCLUDE_FILE = exports.IGNORE_FILE = exports.STARTER_IGNORE = exports.CODEROOK_TAIL = exports.CODEROOK_TAIL_HEADING = exports.KEPT_ANYWAY = exports.KEPT_ANYWAY_HEADING = exports.ALSO_LEFT_OUT = exports.ALSO_LEFT_OUT_HEADING = exports.STARTER_MARKER = exports.EVALUATION_FILE_LIMIT = void 0;
|
|
6
|
+
exports.IGNORE_HEADING = exports.KEEP_HEADING = exports.LOCAL_EXCLUDE_FILE = exports.IGNORE_FILE = exports.STARTER_IGNORE = exports.CODEROOK_TAIL = exports.CODEROOK_TAIL_HEADING = exports.KEPT_ANYWAY = exports.KEPT_ANYWAY_HEADING = exports.ALSO_LEFT_OUT = exports.ALSO_LEFT_OUT_HEADING = exports.STARTER_MARKER = exports.EVALUATION_FILE_LIMIT = void 0;
|
|
7
7
|
exports.starterAuthored = starterAuthored;
|
|
8
8
|
exports.missingStarterRules = missingStarterRules;
|
|
9
9
|
exports.missingEngineRules = missingEngineRules;
|
|
10
10
|
exports.isRepository = isRepository;
|
|
11
11
|
exports.migrateRetiredRules = migrateRetiredRules;
|
|
12
12
|
exports.starterFor = starterFor;
|
|
13
|
+
exports.moveRule = moveRule;
|
|
13
14
|
exports.readRules = readRules;
|
|
14
15
|
exports.writeRules = writeRules;
|
|
15
16
|
exports.collectLayers = collectLayers;
|
|
@@ -38,6 +39,8 @@ const ignore_templates_js_1 = require("./ignore_templates.js");
|
|
|
38
39
|
const rules_js_1 = require("./rules.js");
|
|
39
40
|
const secret_patterns_js_1 = require("./secret_patterns.js");
|
|
40
41
|
const run = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
42
|
+
/* A code point, so nothing between here and disk can eat the escape. */
|
|
43
|
+
const NEWLINE = String.fromCharCode(10);
|
|
41
44
|
/** Past this the evaluation reports truncated rather than walking forever. */
|
|
42
45
|
exports.EVALUATION_FILE_LIMIT = 300_000;
|
|
43
46
|
async function git(root, ...args) {
|
|
@@ -359,9 +362,14 @@ const EXTRA_TEMPLATES = {
|
|
|
359
362
|
/.creator/`,
|
|
360
363
|
},
|
|
361
364
|
};
|
|
362
|
-
async function starterFor(root) {
|
|
365
|
+
async function starterFor(root, chosen) {
|
|
366
|
+
/*
|
|
367
|
+
What somebody said it is beats what the folder looks like.
|
|
368
|
+
`none` is a real answer — "this is not any of those" — and gets the plain
|
|
369
|
+
starter rather than being read as "no answer given".
|
|
370
|
+
*/
|
|
363
371
|
const entries = await (0, promises_1.readdir)(root).catch(() => []);
|
|
364
|
-
const keys = (0, detect_js_1.templatesFor)(entries);
|
|
372
|
+
const keys = chosen ? (chosen === "none" ? [] : [chosen]) : (0, detect_js_1.templatesFor)(entries);
|
|
365
373
|
if (!keys.length)
|
|
366
374
|
return exports.STARTER_IGNORE;
|
|
367
375
|
const parts = [exports.STARTER_MARKER];
|
|
@@ -377,6 +385,106 @@ async function starterFor(root) {
|
|
|
377
385
|
parts.push("", exports.CODEROOK_TAIL_HEADING, exports.CODEROOK_TAIL);
|
|
378
386
|
return `${parts.join("\n")}\n`;
|
|
379
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* Move one path onto the ignore list, or onto the keep list.
|
|
390
|
+
*
|
|
391
|
+
* Both are the same act — writing a line into the project's own rules — which
|
|
392
|
+
* is why they are one function. Keeping means a `!` rule, and a `!` rule only
|
|
393
|
+
* means anything if it comes after whatever would have taken the file, so the
|
|
394
|
+
* line goes at the end under its own heading.
|
|
395
|
+
*
|
|
396
|
+
* Lifted out of the window's event handler so it can be tested. It could not
|
|
397
|
+
* be before, which is how a folder came to be written as a rule that also
|
|
398
|
+
* matched a file of the same name.
|
|
399
|
+
*/
|
|
400
|
+
async function moveRule(root, file, keep) {
|
|
401
|
+
/*
|
|
402
|
+
The rules travel with the project. A .gitignore that excluded itself would
|
|
403
|
+
never reach the website, the command line, or another machine.
|
|
404
|
+
*/
|
|
405
|
+
if (file === exports.IGNORE_FILE) {
|
|
406
|
+
throw new Error("The ignore rules have to travel with the project");
|
|
407
|
+
}
|
|
408
|
+
const rules = await readRules(root);
|
|
409
|
+
/*
|
|
410
|
+
A folder is written as a folder rule.
|
|
411
|
+
|
|
412
|
+
The path arrives without a trailing slash, and writing it as-is makes a
|
|
413
|
+
rule that also matches a *file* of that name — not what anybody meant by
|
|
414
|
+
excluding a directory, and a disagreement with what the command line
|
|
415
|
+
prints for the same folder. Any slash already there is taken off first, so
|
|
416
|
+
a caller passing a rule rather than a path cannot produce `name//`.
|
|
417
|
+
*/
|
|
418
|
+
const bare = file.replace(/\/+$/, "");
|
|
419
|
+
const directory = await (0, promises_1.stat)(node_path_1.default.join(root, bare))
|
|
420
|
+
.then((entry) => entry.isDirectory())
|
|
421
|
+
.catch(() => file.endsWith("/"));
|
|
422
|
+
const target = directory ? `${bare}/` : bare;
|
|
423
|
+
const line = keep ? `!${target}` : target;
|
|
424
|
+
/* Saying it twice changes nothing and makes the file harder to read. */
|
|
425
|
+
const already = ruleLines(rules.shared).some((one) => one === line);
|
|
426
|
+
if (already)
|
|
427
|
+
return rules;
|
|
428
|
+
const heading = keep ? exports.KEEP_HEADING : exports.IGNORE_HEADING;
|
|
429
|
+
let body = rules.shared.replace(/\s*$/, "");
|
|
430
|
+
/*
|
|
431
|
+
A keep inside an excluded folder needs the folder opened first.
|
|
432
|
+
|
|
433
|
+
Rules cannot re-include a file whose directory is excluded — nothing ever
|
|
434
|
+
looks inside a directory it has already decided to skip, so `!Library/x`
|
|
435
|
+
under `Library/` does precisely nothing. That is most of what this button
|
|
436
|
+
is for: rescuing one file out of a cache.
|
|
437
|
+
|
|
438
|
+
The way through is the documented one. `Library/` becomes `Library/**`,
|
|
439
|
+
which leaves the directory itself includable, and every directory on the
|
|
440
|
+
way down to the file is named back in. Written here rather than asked of
|
|
441
|
+
the person, because the alternative is a button that looks like it worked.
|
|
442
|
+
*/
|
|
443
|
+
if (keep) {
|
|
444
|
+
/*
|
|
445
|
+
Which rule is in the way is a question for the matcher, not for string
|
|
446
|
+
comparison. The line is `/[Ll]ibrary/` — a pattern — and no amount of
|
|
447
|
+
comparing it against `Library/PackageCache/...` will ever match, which
|
|
448
|
+
is exactly how the first version of this wrote a negation that did
|
|
449
|
+
nothing at all.
|
|
450
|
+
*/
|
|
451
|
+
const parts = target.split("/");
|
|
452
|
+
for (let depth = 1; depth < parts.length; depth += 1) {
|
|
453
|
+
const ancestor = parts.slice(0, depth).join("/");
|
|
454
|
+
const layers = [{ base: "", rules: (0, rules_js_1.parseRules)(body) }];
|
|
455
|
+
const blocking = (0, rules_js_1.decide)(ancestor, true, layers);
|
|
456
|
+
if (!blocking || blocking.negated || !blocking.directoryOnly)
|
|
457
|
+
continue;
|
|
458
|
+
/*
|
|
459
|
+
Replaced line by line rather than by pattern. The thing being found
|
|
460
|
+
is itself a pattern — `/[Ll]ibrary/` — so building a regular
|
|
461
|
+
expression out of it means escaping a string that is mostly regular
|
|
462
|
+
expression already, for no gain over comparing the text.
|
|
463
|
+
*/
|
|
464
|
+
const was = (blocking.anchored ? "/" : "") + blocking.pattern + "/";
|
|
465
|
+
const opened = (blocking.anchored ? "/" : "") + blocking.pattern + "/**";
|
|
466
|
+
body = body
|
|
467
|
+
.split(NEWLINE)
|
|
468
|
+
.map((one) => (one.trim() === was ? opened : one))
|
|
469
|
+
.join(NEWLINE);
|
|
470
|
+
/* Then name every directory between it and the file back in. */
|
|
471
|
+
for (let at = depth; at < parts.length - 1; at += 1) {
|
|
472
|
+
const open = "!" + parts.slice(0, at + 1).join("/") + "/";
|
|
473
|
+
if (!ruleLines(body).includes(open))
|
|
474
|
+
body += NEWLINE + open;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const shared = body.includes(heading)
|
|
479
|
+
? body + NEWLINE + line + NEWLINE
|
|
480
|
+
: body + NEWLINE + NEWLINE + heading + NEWLINE + line + NEWLINE;
|
|
481
|
+
const next = { ...rules, shared };
|
|
482
|
+
await writeRules(root, next);
|
|
483
|
+
return next;
|
|
484
|
+
}
|
|
485
|
+
/** The two headings a move writes under, so a person can find their own edits. */
|
|
486
|
+
exports.KEEP_HEADING = "# Always include";
|
|
487
|
+
exports.IGNORE_HEADING = "# Left out from the filter screen";
|
|
380
488
|
async function readRules(root) {
|
|
381
489
|
const shared = await readIfPresent(node_path_1.default.join(root, exports.IGNORE_FILE));
|
|
382
490
|
const local = await readIfPresent(node_path_1.default.join(root, exports.LOCAL_EXCLUDE_FILE));
|