@coderook/cli 0.34.11 → 0.36.0

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.
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.crc32 = crc32;
7
+ exports.listFiles = listFiles;
8
+ exports.writeZip = writeZip;
9
+ /**
10
+ * Write a folder as a zip, one file at a time, to disk.
11
+ *
12
+ * Mod repositories take each part as a zip, and a game's mod can hold
13
+ * hundreds of thousands of files and run to gigabytes — ZT1's art packs do —
14
+ * so the archive is written as it goes rather than built in memory, and
15
+ * ZIP64 records are used once sizes, offsets or the file count pass what the
16
+ * classic format can hold. Each file is deflated, or stored when deflating
17
+ * would not make it smaller (already-compressed images and audio).
18
+ */
19
+ const node_fs_1 = require("node:fs");
20
+ const promises_1 = require("node:fs/promises");
21
+ const node_path_1 = __importDefault(require("node:path"));
22
+ const node_zlib_1 = require("node:zlib");
23
+ const CRC_TABLE = (() => {
24
+ const table = new Uint32Array(256);
25
+ for (let n = 0; n < 256; n += 1) {
26
+ let c = n;
27
+ for (let k = 0; k < 8; k += 1)
28
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
29
+ table[n] = c >>> 0;
30
+ }
31
+ return table;
32
+ })();
33
+ function crc32(bytes) {
34
+ let crc = 0xffffffff;
35
+ for (let i = 0; i < bytes.length; i += 1)
36
+ crc = CRC_TABLE[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
37
+ return (crc ^ 0xffffffff) >>> 0;
38
+ }
39
+ const MAX32 = 0xffffffff;
40
+ const MAX16 = 0xffff;
41
+ function dosTime(when) {
42
+ return {
43
+ time: (when.getHours() << 11) | (when.getMinutes() << 5) | Math.floor(when.getSeconds() / 2),
44
+ date: ((Math.max(1980, when.getFullYear()) - 1980) << 9) | ((when.getMonth() + 1) << 5) | when.getDate(),
45
+ };
46
+ }
47
+ /** Every file under a folder, as zip paths with forward slashes, in a stable order. */
48
+ async function listFiles(root, skip = () => false) {
49
+ const found = [];
50
+ const walk = async (dir) => {
51
+ const entries = (await (0, promises_1.readdir)(node_path_1.default.join(root, dir), { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
52
+ for (const entry of entries) {
53
+ const relative = dir ? `${dir}/${entry.name}` : entry.name;
54
+ if (skip(relative))
55
+ continue;
56
+ if (entry.isSymbolicLink())
57
+ continue;
58
+ if (entry.isDirectory())
59
+ await walk(relative);
60
+ else if (entry.isFile())
61
+ found.push(relative);
62
+ }
63
+ };
64
+ await walk("");
65
+ return found;
66
+ }
67
+ /**
68
+ * Zip these files (paths relative to root) into `out`. `extra` adds entries
69
+ * written from memory — a generated manifest, for one.
70
+ */
71
+ async function writeZip(root, files, out, extra = [], onProgress) {
72
+ const stream = (0, node_fs_1.createWriteStream)(out);
73
+ let failed = null;
74
+ stream.on("error", (error) => {
75
+ failed = error;
76
+ });
77
+ const write = async (chunk) => {
78
+ if (failed)
79
+ throw failed;
80
+ if (!stream.write(chunk))
81
+ await new Promise((resolve) => stream.once("drain", resolve));
82
+ if (failed)
83
+ throw failed;
84
+ };
85
+ const written = [];
86
+ let offset = 0;
87
+ const { time, date } = dosTime(new Date());
88
+ const extraNames = new Set(extra.map((one) => one.path));
89
+ const sources = [
90
+ ...extra.map((one) => ({ path: one.path, load: async () => one.bytes })),
91
+ ...files.filter((file) => !extraNames.has(file)).map((file) => ({ path: file, load: () => (0, promises_1.readFile)(node_path_1.default.join(root, file)) })),
92
+ ];
93
+ for (const [index, source] of sources.entries()) {
94
+ const raw = await source.load();
95
+ const deflated = (0, node_zlib_1.deflateRawSync)(raw, { level: 6 });
96
+ const store = deflated.length >= raw.length;
97
+ const body = store ? raw : deflated;
98
+ const name = Buffer.from(source.path, "utf8");
99
+ const crc = crc32(raw);
100
+ const zip64 = raw.length >= MAX32 || body.length >= MAX32;
101
+ const local = Buffer.alloc(30);
102
+ local.writeUInt32LE(0x04034b50, 0);
103
+ local.writeUInt16LE(zip64 ? 45 : 20, 4);
104
+ local.writeUInt16LE(0x0800, 6); /* UTF-8 names */
105
+ local.writeUInt16LE(store ? 0 : 8, 8);
106
+ local.writeUInt16LE(time, 10);
107
+ local.writeUInt16LE(date, 12);
108
+ local.writeUInt32LE(crc, 14);
109
+ local.writeUInt32LE(zip64 ? MAX32 : body.length, 18);
110
+ local.writeUInt32LE(zip64 ? MAX32 : raw.length, 22);
111
+ local.writeUInt16LE(name.length, 26);
112
+ const localExtra = zip64 ? zip64Extra([raw.length, body.length]) : Buffer.alloc(0);
113
+ local.writeUInt16LE(localExtra.length, 28);
114
+ await write(local);
115
+ await write(name);
116
+ if (localExtra.length)
117
+ await write(localExtra);
118
+ await write(body);
119
+ written.push({ name, crc, method: store ? 0 : 8, compressed: body.length, size: raw.length, offset, time, date });
120
+ offset += local.length + name.length + localExtra.length + body.length;
121
+ onProgress?.(index + 1, sources.length);
122
+ }
123
+ const centralStart = offset;
124
+ for (const entry of written) {
125
+ const big = [entry.size >= MAX32, entry.compressed >= MAX32, entry.offset >= MAX32];
126
+ const values = [
127
+ ...(big[0] ? [entry.size] : []),
128
+ ...(big[1] ? [entry.compressed] : []),
129
+ ...(big[2] ? [entry.offset] : []),
130
+ ];
131
+ const extraField = values.length ? zip64Extra(values) : Buffer.alloc(0);
132
+ const central = Buffer.alloc(46);
133
+ central.writeUInt32LE(0x02014b50, 0);
134
+ central.writeUInt16LE(values.length ? 45 : 20, 4);
135
+ central.writeUInt16LE(values.length ? 45 : 20, 6);
136
+ central.writeUInt16LE(0x0800, 8);
137
+ central.writeUInt16LE(entry.method, 10);
138
+ central.writeUInt16LE(entry.time, 12);
139
+ central.writeUInt16LE(entry.date, 14);
140
+ central.writeUInt32LE(entry.crc, 16);
141
+ central.writeUInt32LE(big[1] ? MAX32 : entry.compressed, 20);
142
+ central.writeUInt32LE(big[0] ? MAX32 : entry.size, 24);
143
+ central.writeUInt16LE(entry.name.length, 28);
144
+ central.writeUInt16LE(extraField.length, 30);
145
+ central.writeUInt32LE((0o100644 * 0x10000) >>> 0, 38); /* a plain file, never a link */
146
+ central.writeUInt32LE(big[2] ? MAX32 : entry.offset, 42);
147
+ await write(central);
148
+ await write(entry.name);
149
+ if (extraField.length)
150
+ await write(extraField);
151
+ offset += central.length + entry.name.length + extraField.length;
152
+ }
153
+ const centralSize = offset - centralStart;
154
+ const needs64 = written.length >= MAX16 || centralStart >= MAX32 || centralSize >= MAX32;
155
+ if (needs64) {
156
+ const record = Buffer.alloc(56);
157
+ record.writeUInt32LE(0x06064b50, 0);
158
+ record.writeBigUInt64LE(44n, 4);
159
+ record.writeUInt16LE(45, 12);
160
+ record.writeUInt16LE(45, 14);
161
+ record.writeBigUInt64LE(BigInt(written.length), 24);
162
+ record.writeBigUInt64LE(BigInt(written.length), 32);
163
+ record.writeBigUInt64LE(BigInt(centralSize), 40);
164
+ record.writeBigUInt64LE(BigInt(centralStart), 48);
165
+ const locator = Buffer.alloc(20);
166
+ locator.writeUInt32LE(0x07064b50, 0);
167
+ locator.writeBigUInt64LE(BigInt(offset), 8);
168
+ locator.writeUInt32LE(1, 16);
169
+ await write(record);
170
+ await write(locator);
171
+ }
172
+ const end = Buffer.alloc(22);
173
+ end.writeUInt32LE(0x06054b50, 0);
174
+ end.writeUInt16LE(needs64 ? MAX16 : written.length, 8);
175
+ end.writeUInt16LE(needs64 ? MAX16 : written.length, 10);
176
+ end.writeUInt32LE(needs64 ? MAX32 : centralSize, 12);
177
+ end.writeUInt32LE(needs64 ? MAX32 : centralStart, 16);
178
+ await write(end);
179
+ await new Promise((resolve, reject) => stream.end((error) => (error ? reject(error) : resolve())));
180
+ return { files: written.length, bytes: offset + (needs64 ? 76 : 0) + 22 };
181
+ }
182
+ function zip64Extra(values) {
183
+ const field = Buffer.alloc(4 + values.length * 8);
184
+ field.writeUInt16LE(0x0001, 0);
185
+ field.writeUInt16LE(values.length * 8, 2);
186
+ values.forEach((value, index) => field.writeBigUInt64LE(BigInt(value), 4 + index * 8));
187
+ return field;
188
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coderook/cli",
3
- "version": "0.34.11",
3
+ "version": "0.36.0",
4
4
  "description": "The command line for CodeRook (coderook.com): version control and project hosting for projects of any size. Every save is a compressed snapshot, private or public, never used to train AI.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://coderook.com",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: coderook
3
- description: Save, browse and restore versions of a project on CodeRook — a version host where every version is a complete snapshot, reached with the cbx command or with plain git. Use when asked to save, submit or push work to CodeRook, check what has changed, look at a project's versions or files, fetch a project, start or switch a line of work, resolve a save that landed at the same time as somebody else's, or build a project's executables for Windows, Linux and macOS from one machine (cbx build).
3
+ description: Save, browse and restore versions of a project on CodeRook — a version host where every version is a complete snapshot, reached with the cbx command or with plain git. Use when asked to save, submit or push work to CodeRook, check what has changed, look at a project's versions or files, fetch a project, start or switch a line of work, resolve a save that landed at the same time as somebody else's, build a project's executables for Windows, Linux and macOS from one machine (cbx build), or publish a mod to a game's mod repository or set one up (cbx mods).
4
4
  allowed-tools: Bash Read
5
5
  ---
6
6
 
@@ -306,6 +306,50 @@ unasked. `--ship` attaches the results to the newest save, so **ask before
306
306
  `--ship`**, as before any save. A project's own build description lives in
307
307
  `coderook.build.json`.
308
308
 
309
+ ## Reviewing proposals with GitHub tools
310
+
311
+ When someone wants CodeRabbit, Blacksmith or GitHub Actions on their CodeRook
312
+ proposals, that is `cbx github`: a GitHub repository mirrors each open
313
+ proposal as a pull request, and the results come back as checks named
314
+ `github/<check>` and `review/<reviewer>`.
315
+
316
+ ```bash
317
+ cbx github setup OWNER/REPO # token limited to this project, secret, workflow file
318
+ cbx submit # the workflow has to be saved into the project
319
+ cbx github sync --repo OWNER/REPO
320
+ ```
321
+
322
+ **Ask before `setup`**: it creates a token and stores it in GitHub. The person
323
+ adds the second secret, `CODEROOK_BRIDGE_PUSH` (a GitHub fine-grained token
324
+ with Contents and Pull requests read/write), themselves, because it is their
325
+ GitHub credential. Every proposal also carries a `coderook/malware` check; a
326
+ failure names the file. Do not try to get round it.
327
+
328
+ ## Mod repositories for games
329
+
330
+ A game's maker can run a mod repository: players' game clients read a signed
331
+ catalogue from it, modders publish to it, and nothing is listed until it
332
+ passes the repository's rules and a malware scan.
333
+
334
+ ```bash
335
+ cbx mods info zt1 # a repository's rules, for anybody
336
+ cbx mods publish ./BetterTrees --repo zt1 # zip, upload, wait for the checks
337
+ cbx mods publish ./HD-Art --repo zt1 --part animals # one part of a bigger mod
338
+ cbx mods list zt1 # your mods and how their checks went
339
+ cbx mods create my-game --name "My Game" --allow .png,.ogg,.json
340
+ ```
341
+
342
+ `publish` reads the folder's `mod.json` (or the repository's manifest) for the
343
+ mod's id, name, folder and version; `--id`, `--name`, `--folder` and
344
+ `--version` override it. It waits up to 30 minutes for the checks and prints
345
+ what failed, file by file — pass that on as it is. Programs and scripts
346
+ (`.exe`, `.dll`, `.ps1`, `.js` …) are refused in every repository, so do not
347
+ try to rename one past the check. **Ask before `publish`**, as before any
348
+ save: it makes the mod public once it passes. Ask before `remove`, `block`
349
+ and `delete`, which only the person should decide. The game-side protocol
350
+ (signed requests, the catalogue signature, downloads) is at
351
+ https://coderook.com/docs#mod-repositories.
352
+
309
353
  ## Things not to do
310
354
 
311
355
  - Do not run `cbx delete` unless the person has asked for that project to