@coderook/cli 0.9.0 → 0.10.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.
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ /**
3
+ * Commands about a project as it exists on the service.
4
+ *
5
+ * Issues, releases, people, actions, tokens — the things a folder on disk
6
+ * knows nothing about. Kept apart from the folder commands because they fail
7
+ * differently: these need the network and an account, and none of them can be
8
+ * answered by looking at what is on the machine.
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.commandIssues = commandIssues;
15
+ exports.commandReleases = commandReleases;
16
+ exports.commandCollaborators = commandCollaborators;
17
+ exports.commandWatch = commandWatch;
18
+ exports.commandWorkflows = commandWorkflows;
19
+ exports.commandTokens = commandTokens;
20
+ exports.commandDelete = commandDelete;
21
+ const node_process_1 = __importDefault(require("node:process"));
22
+ const promises_1 = require("node:readline/promises");
23
+ const api_js_1 = require("./api.js");
24
+ const project_commands_js_1 = require("./project_commands.js");
25
+ const dim = (value) => `${value}`;
26
+ const bold = (value) => `${value}`;
27
+ const red = (value) => `${value}`;
28
+ const green = (value) => `${value}`;
29
+ const accent = (value) => `${value}`;
30
+ function bytes(value) {
31
+ if (value >= 1024 ** 3)
32
+ return `${(value / 1024 ** 3).toFixed(2)} GB`;
33
+ if (value >= 1024 ** 2)
34
+ return `${(value / 1024 ** 2).toFixed(1)} MB`;
35
+ if (value >= 1024)
36
+ return `${(value / 1024).toFixed(0)} KB`;
37
+ return `${value} B`;
38
+ }
39
+ /** Issues on a project, or open a new one. */
40
+ async function commandIssues(parsed) {
41
+ const project = await (0, project_commands_js_1.resolveProject)(parsed.positional[0]);
42
+ if (!project)
43
+ return 1;
44
+ const title = parsed.flags.get("new");
45
+ if (title === true) {
46
+ console.error(red('What is it about? Try: --new "Crash on export"'));
47
+ return 1;
48
+ }
49
+ if (typeof title === "string" && title.trim()) {
50
+ const body = parsed.flags.get("body");
51
+ const created = await (0, api_js_1.createIssue)(project.id, {
52
+ title: title.trim(),
53
+ body: typeof body === "string" ? body : "",
54
+ });
55
+ console.log(green(`Opened issue #${created.number} on ${project.name}.`));
56
+ return 0;
57
+ }
58
+ const found = await (0, api_js_1.issues)(project.id);
59
+ if (!found.issues.length) {
60
+ console.log(dim("No issues on this project."));
61
+ return 0;
62
+ }
63
+ console.log(`${bold(project.name)} ${dim(`${found.openCount} open, ${found.closedCount} closed`)}`);
64
+ for (const issue of found.issues) {
65
+ const state = issue.state === "open" ? accent("open") : dim("closed");
66
+ console.log(` ${dim(`#${issue.number}`).padEnd(14)} ${state.padEnd(16)} ${issue.title}`);
67
+ if (issue.labels.length)
68
+ console.log(` ${dim(issue.labels.join(", "))}`);
69
+ }
70
+ return 0;
71
+ }
72
+ /** Published releases, newest first. */
73
+ async function commandReleases(parsed) {
74
+ const project = await (0, project_commands_js_1.resolveProject)(parsed.positional[0]);
75
+ if (!project)
76
+ return 1;
77
+ const published = await (0, api_js_1.releases)(project.id);
78
+ if (!published.length) {
79
+ console.log(dim("Nothing has been released from this project."));
80
+ return 0;
81
+ }
82
+ console.log(bold(project.name));
83
+ for (const release of published) {
84
+ const when = release.releasedAt
85
+ ? new Date(release.releasedAt).toLocaleDateString()
86
+ : "";
87
+ console.log(` ${accent(`v${release.sequence}`).padEnd(16)} ` +
88
+ `${(release.name || "—").padEnd(28)} ${when.padEnd(14)} ` +
89
+ `${release.assets.length} files`);
90
+ const firstLine = release.notes.split("\n")[0];
91
+ if (firstLine)
92
+ console.log(` ${dim(firstLine)}`);
93
+ }
94
+ return 0;
95
+ }
96
+ /** Who can reach a project, and who has been asked. */
97
+ async function commandCollaborators(parsed) {
98
+ const project = await (0, project_commands_js_1.resolveProject)(parsed.positional[0]);
99
+ if (!project)
100
+ return 1;
101
+ const email = parsed.flags.get("invite");
102
+ if (email === true) {
103
+ console.error(red("Whose address? Try: --invite someone@example.com"));
104
+ return 1;
105
+ }
106
+ if (typeof email === "string" && email.trim()) {
107
+ const role = parsed.flags.get("role");
108
+ await (0, api_js_1.invite)(project.id, {
109
+ email: email.trim(),
110
+ role: typeof role === "string" ? role : "member",
111
+ });
112
+ console.log(green(`Invited ${email.trim()} to ${project.name}.`));
113
+ return 0;
114
+ }
115
+ const people = await (0, api_js_1.collaborators)(project.id);
116
+ console.log(bold(project.name));
117
+ if (!people.collaborators.length) {
118
+ console.log(dim(" Nobody else has access."));
119
+ }
120
+ for (const person of people.collaborators) {
121
+ console.log(` ${(person.displayName || person.email).padEnd(28)} ${dim(person.role)}`);
122
+ }
123
+ if (people.invites.length) {
124
+ console.log("");
125
+ console.log(dim(" Waiting on a reply"));
126
+ for (const pending of people.invites) {
127
+ console.log(` ${pending.email.padEnd(28)} ${dim(pending.role)}`);
128
+ }
129
+ }
130
+ return 0;
131
+ }
132
+ const WATCH_LEVELS = ["everything", "versions", "issues", "releases", "ignore"];
133
+ /** What reaches you about a project. */
134
+ async function commandWatch(parsed) {
135
+ const project = await (0, project_commands_js_1.resolveProject)(parsed.positional[0]);
136
+ if (!project)
137
+ return 1;
138
+ const change = {};
139
+ const level = parsed.flags.get("level");
140
+ if (typeof level === "string") {
141
+ if (!WATCH_LEVELS.includes(level)) {
142
+ console.error(red(`Level must be one of: ${WATCH_LEVELS.join(", ")}`));
143
+ return 1;
144
+ }
145
+ change.level = level;
146
+ }
147
+ if (parsed.flags.has("email")) {
148
+ change.emailDigest = parsed.flags.get("email") !== "off";
149
+ }
150
+ if (Object.keys(change).length) {
151
+ await (0, api_js_1.setWatch)(project.id, change);
152
+ console.log(green(`Updated what reaches you about ${project.name}.`));
153
+ }
154
+ const settings = await (0, api_js_1.watch)(project.id);
155
+ console.log("");
156
+ console.log(bold(project.name));
157
+ console.log(` Level ${accent(settings.level)}`);
158
+ console.log(` In the app ${settings.inApp ? "yes" : "no"}`);
159
+ console.log(` Daily email ${settings.emailDigest ? "yes" : "no"}`);
160
+ console.log("");
161
+ console.log(dim(` Levels: ${WATCH_LEVELS.join(", ")}`));
162
+ return 0;
163
+ }
164
+ /** The automations a project has, and how they have been going. */
165
+ async function commandWorkflows(parsed) {
166
+ const project = await (0, project_commands_js_1.resolveProject)(parsed.positional[0]);
167
+ if (!project)
168
+ return 1;
169
+ const found = (await (0, api_js_1.workflows)(project.id)).filter((workflow) => !workflow.archivedAt);
170
+ if (!found.length) {
171
+ console.log(dim("No actions on this project."));
172
+ return 0;
173
+ }
174
+ console.log(bold(project.name));
175
+ for (const workflow of found) {
176
+ const health = workflow.runs
177
+ ? `${workflow.passing}/${workflow.runs} passing`
178
+ : "never run";
179
+ console.log(` ${workflow.name.padEnd(24)} ${dim(workflow.trigger).padEnd(20)} ` +
180
+ `${dim(workflow.runsOn).padEnd(18)} ${health}`);
181
+ console.log(` ${dim(workflow.command)}`);
182
+ }
183
+ return 0;
184
+ }
185
+ /** The tokens that can act as this account. */
186
+ async function commandTokens(parsed) {
187
+ const revoke = parsed.flags.get("revoke");
188
+ if (typeof revoke === "string" && revoke.trim()) {
189
+ await (0, api_js_1.revokeToken)(revoke.trim());
190
+ console.log(green("Revoked. Anything using it stops working now."));
191
+ return 0;
192
+ }
193
+ const found = await (0, api_js_1.tokens)();
194
+ if (!found.length) {
195
+ console.log(dim("No personal access tokens on this account."));
196
+ return 0;
197
+ }
198
+ for (const token of found) {
199
+ const used = token.lastUsedAt
200
+ ? new Date(token.lastUsedAt).toLocaleDateString()
201
+ : "never used";
202
+ console.log(` ${token.name.padEnd(26)} ${used.padEnd(16)} ${dim(token.id)}`);
203
+ }
204
+ console.log("");
205
+ console.log(dim(" Revoke one with: coderook tokens --revoke <id>"));
206
+ return 0;
207
+ }
208
+ /**
209
+ * Delete a project.
210
+ *
211
+ * Asks for the name to be typed rather than a yes. A confirmation somebody can
212
+ * answer without reading is not a confirmation, and this is the command that
213
+ * takes away work.
214
+ */
215
+ async function commandDelete(parsed) {
216
+ const project = await (0, project_commands_js_1.resolveProject)(parsed.positional[0]);
217
+ if (!project)
218
+ return 1;
219
+ if (parsed.flags.get("yes") !== true) {
220
+ console.log(`This deletes ${bold(project.name)} — ${project.versionCount} versions, ` +
221
+ `${bytes(project.storedBytes)}.`);
222
+ console.log(dim("It is kept for 30 days before the storage is reclaimed."));
223
+ const prompt = (0, promises_1.createInterface)({
224
+ input: node_process_1.default.stdin,
225
+ output: node_process_1.default.stdout,
226
+ });
227
+ const typed = await prompt.question("Type the project name to confirm: ");
228
+ prompt.close();
229
+ const answer = typed.trim();
230
+ if (answer !== project.slug && answer !== project.name) {
231
+ console.error(red("That did not match. Nothing was deleted."));
232
+ return 1;
233
+ }
234
+ }
235
+ await (0, api_js_1.deleteProject)(project.id);
236
+ console.log(green(`Deleted ${project.name}.`));
237
+ return 0;
238
+ }
package/package.json CHANGED
@@ -1,47 +1,48 @@
1
- {
2
- "name": "@coderook/cli",
3
- "version": "0.9.0",
4
- "description": "CodeRook from the command line, on any operating system",
5
- "license": "SEE LICENSE IN LICENSE.txt",
6
- "homepage": "https://coderook.com",
7
- "bugs": {
8
- "url": "https://coderook.com/contact"
9
- },
10
- "keywords": [
11
- "coderook",
12
- "versioning",
13
- "backup",
14
- "cbx"
15
- ],
16
- "engines": {
17
- "node": ">=20.11.0"
18
- },
19
- "bin": {
20
- "coderook": "dist/cli/src/cli.js"
21
- },
22
- "files": [
23
- "dist"
24
- ],
25
- "scripts": {
26
- "build": "tsc -p tsconfig.json",
27
- "check": "tsc -p tsconfig.json --noEmit",
28
- "test": "tsc -p tsconfig.json && node --test --experimental-strip-types test/*.test.ts",
29
- "start": "node dist/cli/src/cli.js",
30
- "prepublishOnly": "npm run build",
31
- "test:e2e": "node test/e2e.mjs",
32
- "test:matrix": "node test/state-matrix.mjs",
33
- "test:attempt": "node test/attempt-identity.mjs",
34
- "test:get": "node test/get-safety.mjs",
35
- "test:live": "node test/e2e.mjs --allow-production && node test/state-matrix.mjs --allow-production && node test/get-safety.mjs --allow-production && node test/get-protects-edits.mjs --allow-production && node test/upgrade-migration.mjs --allow-production && node test/fault-get.mjs --allow-production && node test/version-floor.mjs --allow-production && node test/fault-submit.mjs --allow-production && node test/race-attempts.mjs --allow-production && node test/attempt-identity.mjs --allow-production",
36
- "test:getedits": "node test/get-protects-edits.mjs",
37
- "test:upgrade": "node test/upgrade-migration.mjs",
38
- "test:faultget": "node test/fault-get.mjs",
39
- "test:floor": "node test/version-floor.mjs",
40
- "test:faultsubmit": "node test/fault-submit.mjs",
41
- "test:race": "node test/race-attempts.mjs"
42
- },
43
- "devDependencies": {
44
- "@types/node": "24.10.1",
45
- "typescript": "5.9.3"
46
- }
47
- }
1
+ {
2
+ "name": "@coderook/cli",
3
+ "version": "0.10.1",
4
+ "description": "CodeRook from the command line, on any operating system",
5
+ "license": "SEE LICENSE IN LICENSE.txt",
6
+ "homepage": "https://coderook.com",
7
+ "bugs": {
8
+ "url": "https://coderook.com/contact"
9
+ },
10
+ "keywords": [
11
+ "coderook",
12
+ "versioning",
13
+ "backup",
14
+ "cbx"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20.11.0"
18
+ },
19
+ "bin": {
20
+ "coderook": "dist/cli/src/cli.js"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "check": "tsc -p tsconfig.json --noEmit",
28
+ "test": "tsc -p tsconfig.json && node --test --experimental-strip-types test/*.test.ts",
29
+ "start": "node dist/cli/src/cli.js",
30
+ "prepublishOnly": "npm run build",
31
+ "test:e2e": "node test/e2e.mjs",
32
+ "test:matrix": "node test/state-matrix.mjs",
33
+ "test:attempt": "node test/attempt-identity.mjs",
34
+ "test:get": "node test/get-safety.mjs",
35
+ "test:live": "node test/e2e.mjs --allow-production && node test/state-matrix.mjs --allow-production && node test/get-safety.mjs --allow-production && node test/get-protects-edits.mjs --allow-production && node test/upgrade-migration.mjs --allow-production && node test/fault-get.mjs --allow-production && node test/version-floor.mjs --allow-production && node test/fault-submit.mjs --allow-production && node test/race-attempts.mjs --allow-production && node test/attempt-identity.mjs --allow-production",
36
+ "test:getedits": "node test/get-protects-edits.mjs",
37
+ "test:upgrade": "node test/upgrade-migration.mjs",
38
+ "test:faultget": "node test/fault-get.mjs",
39
+ "test:floor": "node test/version-floor.mjs",
40
+ "test:faultsubmit": "node test/fault-submit.mjs",
41
+ "test:race": "node test/race-attempts.mjs",
42
+ "test:runner": "node test/runner-live.mjs"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "24.10.1",
46
+ "typescript": "5.9.3"
47
+ }
48
+ }