@capraconsulting/cals-cli 3.14.0 → 3.15.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.
- package/README.md +42 -33
- package/lib/cals-cli.mjs +356 -277
- package/lib/cals-cli.mjs.map +1 -1
- package/lib/cli/commands/{github/generate-clone-commands.d.ts → repos.d.ts} +1 -1
- package/lib/cli/commands/{github/sync.d.ts → sync.d.ts} +1 -1
- package/lib/cli/commands/topics.d.ts +3 -0
- package/lib/index.es.js +1 -1
- package/lib/index.js +1 -1
- package/package.json +1 -1
- /package/lib/cli/commands/{github/list-repos.d.ts → auth.d.ts} +0 -0
- /package/lib/cli/commands/{github.d.ts → clone.d.ts} +0 -0
- /package/lib/cli/commands/{github/set-token.d.ts → groups.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -13,52 +13,61 @@ It is recommended to use `npx` over global install to ensure you
|
|
|
13
13
|
always run the latest version. If you install it globally remember
|
|
14
14
|
to update it before running.
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Commands
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
### Authentication
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
Set your GitHub token (will be stored in the OS keychain):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cals auth
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
### List repositories
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
```bash
|
|
29
|
+
cals repos --org capralifecycle
|
|
30
|
+
cals repos --org capralifecycle --compact
|
|
31
|
+
cals repos --org capralifecycle --csv
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### List repository groups
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cals groups --org capralifecycle
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Generate clone commands
|
|
30
41
|
|
|
31
|
-
|
|
42
|
+
Generate clone commands (pipe to bash to execute):
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
```bash
|
|
45
|
+
cals clone --org capralifecycle --all | bash
|
|
46
|
+
cals clone --org capralifecycle mygroup | bash
|
|
47
|
+
```
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
- Provide simple guidelines to improve the experience for developers
|
|
37
|
-
- A tool that everybody uses and gets ownership of
|
|
38
|
-
- Automate repeatable CALS tasks as we go
|
|
49
|
+
### Sync repositories
|
|
39
50
|
|
|
40
|
-
|
|
51
|
+
Pull latest changes for all repositories in a directory managed by a `.cals.yaml` manifest:
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- Creating GitHub repos, giving permissions etc
|
|
47
|
-
- Slack channels
|
|
48
|
-
- AWS account and structure
|
|
49
|
-
- Checklist for manual processes
|
|
50
|
-
- AWS infrastructure management, e.g. scripts such as https://github.com/capralifecycle/rvr-aws-infrastructure/blob/master/rvr/create-stack.sh
|
|
51
|
-
- `cals aws ...`
|
|
53
|
+
```bash
|
|
54
|
+
cals sync
|
|
55
|
+
cals sync --clone # Prompt to clone missing repos
|
|
56
|
+
```
|
|
52
57
|
|
|
53
|
-
|
|
58
|
+
## Build
|
|
54
59
|
|
|
55
|
-
|
|
60
|
+
Build and verify:
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- [x] Report issues in Snyk grouped by our projects
|
|
62
|
+
```sh
|
|
63
|
+
$ make # or "make build"
|
|
64
|
+
```
|
|
61
65
|
|
|
62
66
|
## Contributing
|
|
63
67
|
|
|
64
|
-
This project
|
|
68
|
+
This project uses [semantic release](https://semantic-release.gitbook.io/semantic-release/)
|
|
69
|
+
to automate releases and follows
|
|
70
|
+
[Git commit guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit)
|
|
71
|
+
from the Angular project.
|
|
72
|
+
|
|
73
|
+
For inquiries, please contact the maintainers at [Slack](https://liflig.slack.com/archives/C02T4KTPYS2).
|
package/lib/cals-cli.mjs
CHANGED
|
@@ -3,23 +3,23 @@ import * as process from 'node:process';
|
|
|
3
3
|
import process__default from 'node:process';
|
|
4
4
|
import yargs from 'yargs';
|
|
5
5
|
import { hideBin } from 'yargs/helpers';
|
|
6
|
+
import keytar from 'keytar';
|
|
7
|
+
import readline from 'node:readline';
|
|
8
|
+
import chalk from 'chalk';
|
|
6
9
|
import fs from 'node:fs';
|
|
7
10
|
import path from 'node:path';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import cachedir from 'cachedir';
|
|
8
13
|
import { Buffer } from 'node:buffer';
|
|
9
14
|
import { performance } from 'node:perf_hooks';
|
|
10
15
|
import { Octokit } from '@octokit/rest';
|
|
11
16
|
import pLimit from 'p-limit';
|
|
12
|
-
import keytar from 'keytar';
|
|
13
|
-
import os from 'node:os';
|
|
14
|
-
import cachedir from 'cachedir';
|
|
15
|
-
import readline from 'node:readline';
|
|
16
|
-
import chalk from 'chalk';
|
|
17
17
|
import { findUp } from 'find-up';
|
|
18
18
|
import yaml from 'js-yaml';
|
|
19
19
|
import AJV from 'ajv';
|
|
20
20
|
import { execa } from 'execa';
|
|
21
21
|
|
|
22
|
-
var version = "3.
|
|
22
|
+
var version = "3.15.0";
|
|
23
23
|
var engines = {
|
|
24
24
|
node: ">=22.14.0"
|
|
25
25
|
};
|
|
@@ -46,6 +46,239 @@ class GitHubTokenCliProvider {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
const CLEAR_WHOLE_LINE = 0;
|
|
50
|
+
function clearLine(stdout) {
|
|
51
|
+
readline.clearLine(stdout, CLEAR_WHOLE_LINE);
|
|
52
|
+
readline.cursorTo(stdout, 0);
|
|
53
|
+
}
|
|
54
|
+
async function readInput(options) {
|
|
55
|
+
process__default.stdout.write(options.prompt);
|
|
56
|
+
// For silent mode, read character by character with raw mode to hide input
|
|
57
|
+
if (options.silent && process__default.stdin.isTTY) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
let input = "";
|
|
60
|
+
process__default.stdin.setRawMode(true);
|
|
61
|
+
process__default.stdin.resume();
|
|
62
|
+
process__default.stdin.setEncoding("utf8");
|
|
63
|
+
const timer = options.timeout
|
|
64
|
+
? setTimeout(() => {
|
|
65
|
+
cleanup();
|
|
66
|
+
reject(new Error("Input timed out"));
|
|
67
|
+
}, options.timeout)
|
|
68
|
+
: null;
|
|
69
|
+
const cleanup = () => {
|
|
70
|
+
process__default.stdin.setRawMode(false);
|
|
71
|
+
process__default.stdin.pause();
|
|
72
|
+
process__default.stdin.removeListener("data", onData);
|
|
73
|
+
if (timer)
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
};
|
|
76
|
+
const onData = (char) => {
|
|
77
|
+
if (char === "\r" || char === "\n") {
|
|
78
|
+
cleanup();
|
|
79
|
+
process__default.stdout.write("\n");
|
|
80
|
+
resolve(input);
|
|
81
|
+
}
|
|
82
|
+
else if (char === "\u0003") {
|
|
83
|
+
// Ctrl+C
|
|
84
|
+
cleanup();
|
|
85
|
+
process__default.exit(1);
|
|
86
|
+
}
|
|
87
|
+
else if (char === "\u007F" || char === "\b") {
|
|
88
|
+
// Backspace
|
|
89
|
+
input = input.slice(0, -1);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
input += char;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
process__default.stdin.on("data", onData);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// Normal (non-silent) mode
|
|
99
|
+
const rl = readline.createInterface({
|
|
100
|
+
input: process__default.stdin,
|
|
101
|
+
output: process__default.stdout,
|
|
102
|
+
});
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const timer = options.timeout
|
|
105
|
+
? setTimeout(() => {
|
|
106
|
+
rl.close();
|
|
107
|
+
reject(new Error("Input timed out"));
|
|
108
|
+
}, options.timeout)
|
|
109
|
+
: null;
|
|
110
|
+
rl.question("", (answer) => {
|
|
111
|
+
if (timer)
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
rl.close();
|
|
114
|
+
resolve(answer);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
class Reporter {
|
|
119
|
+
stdout = process__default.stdout;
|
|
120
|
+
stderr = process__default.stderr;
|
|
121
|
+
format = chalk;
|
|
122
|
+
error(msg) {
|
|
123
|
+
clearLine(this.stderr);
|
|
124
|
+
this.stderr.write(`${this.format.red("error")} ${msg}\n`);
|
|
125
|
+
}
|
|
126
|
+
log(msg) {
|
|
127
|
+
clearLine(this.stdout);
|
|
128
|
+
this.stdout.write(`${msg}\n`);
|
|
129
|
+
}
|
|
130
|
+
warn(msg) {
|
|
131
|
+
clearLine(this.stderr);
|
|
132
|
+
this.stderr.write(`${this.format.yellow("warning")} ${msg}\n`);
|
|
133
|
+
}
|
|
134
|
+
info(msg) {
|
|
135
|
+
clearLine(this.stdout);
|
|
136
|
+
this.stdout.write(`${this.format.blue("info")} ${msg}\n`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
class CacheProvider {
|
|
141
|
+
constructor(config) {
|
|
142
|
+
this.config = config;
|
|
143
|
+
}
|
|
144
|
+
mustValidate = false;
|
|
145
|
+
config;
|
|
146
|
+
defaultCacheTime = 1800;
|
|
147
|
+
/**
|
|
148
|
+
* Retrieve cache if existent, ignoring the time.
|
|
149
|
+
*
|
|
150
|
+
* The caller is responsible for handling proper validation,
|
|
151
|
+
*/
|
|
152
|
+
retrieveJson(cachekey) {
|
|
153
|
+
const cachefile = path.join(this.config.cacheDir, `${cachekey}.json`);
|
|
154
|
+
if (!fs.existsSync(cachefile)) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
const data = fs.readFileSync(cachefile, "utf-8");
|
|
158
|
+
return {
|
|
159
|
+
cacheTime: fs.statSync(cachefile).mtime.getTime(),
|
|
160
|
+
data: (data === "undefined" ? undefined : JSON.parse(data)),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Save data to cache.
|
|
165
|
+
*/
|
|
166
|
+
storeJson(cachekey, data) {
|
|
167
|
+
const cachefile = path.join(this.config.cacheDir, `${cachekey}.json`);
|
|
168
|
+
if (!fs.existsSync(this.config.cacheDir)) {
|
|
169
|
+
fs.mkdirSync(this.config.cacheDir, { recursive: true });
|
|
170
|
+
}
|
|
171
|
+
fs.writeFileSync(cachefile, data === undefined ? "undefined" : JSON.stringify(data));
|
|
172
|
+
}
|
|
173
|
+
async json(cachekey, block, cachetime = this.defaultCacheTime) {
|
|
174
|
+
const cacheItem = this.mustValidate
|
|
175
|
+
? undefined
|
|
176
|
+
: this.retrieveJson(cachekey);
|
|
177
|
+
const expire = new Date(Date.now() - cachetime * 1000).getTime();
|
|
178
|
+
if (cacheItem !== undefined && cacheItem.cacheTime > expire) {
|
|
179
|
+
return cacheItem.data;
|
|
180
|
+
}
|
|
181
|
+
const result = await block();
|
|
182
|
+
this.storeJson(cachekey, result);
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Delete all cached data.
|
|
187
|
+
*/
|
|
188
|
+
cleanup() {
|
|
189
|
+
fs.rmSync(this.config.cacheDir, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
class Config {
|
|
194
|
+
cwd = path.resolve(process__default.cwd());
|
|
195
|
+
configFile = path.join(os.homedir(), ".cals-config.json");
|
|
196
|
+
cacheDir = cachedir("cals-cli");
|
|
197
|
+
configCached = undefined;
|
|
198
|
+
get config() {
|
|
199
|
+
const existingConfig = this.configCached;
|
|
200
|
+
if (existingConfig !== undefined) {
|
|
201
|
+
return existingConfig;
|
|
202
|
+
}
|
|
203
|
+
const config = this.readConfig();
|
|
204
|
+
this.configCached = config;
|
|
205
|
+
return config;
|
|
206
|
+
}
|
|
207
|
+
readConfig() {
|
|
208
|
+
if (!fs.existsSync(this.configFile)) {
|
|
209
|
+
return {};
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
return JSON.parse(fs.readFileSync(this.configFile, "utf-8"));
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
console.error("Failed", e);
|
|
216
|
+
throw new Error("Failed to read config");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
getConfig(key) {
|
|
220
|
+
return this.config[key];
|
|
221
|
+
}
|
|
222
|
+
requireConfig(key) {
|
|
223
|
+
const result = this.config[key];
|
|
224
|
+
if (result === undefined) {
|
|
225
|
+
throw Error(`Configuration for ${key} missing. Add manually to ${this.configFile}`);
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
updateConfig(key, value) {
|
|
230
|
+
const updatedConfig = {
|
|
231
|
+
...this.readConfig(),
|
|
232
|
+
[key]: value, // undefined will remove
|
|
233
|
+
};
|
|
234
|
+
fs.writeFileSync(this.configFile, JSON.stringify(updatedConfig, null, " "));
|
|
235
|
+
this.configCached = updatedConfig;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function createReporter() {
|
|
240
|
+
return new Reporter();
|
|
241
|
+
}
|
|
242
|
+
function createCacheProvider(config, argv) {
|
|
243
|
+
const cache = new CacheProvider(config);
|
|
244
|
+
if (argv.noCache === true) {
|
|
245
|
+
cache.mustValidate = true;
|
|
246
|
+
}
|
|
247
|
+
return cache;
|
|
248
|
+
}
|
|
249
|
+
function createConfig() {
|
|
250
|
+
return new Config();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function authenticate({ reporter, token, tokenProvider, }) {
|
|
254
|
+
if (token === undefined) {
|
|
255
|
+
reporter.info("Need API token to talk to GitHub");
|
|
256
|
+
reporter.info("https://github.com/settings/tokens/new?scopes=repo:status,read:repo_hook");
|
|
257
|
+
const inputToken = await readInput({
|
|
258
|
+
prompt: "Enter GitHub token: ",
|
|
259
|
+
silent: true,
|
|
260
|
+
});
|
|
261
|
+
token = inputToken;
|
|
262
|
+
}
|
|
263
|
+
await tokenProvider.setToken(token);
|
|
264
|
+
reporter.info("Token saved to keychain");
|
|
265
|
+
}
|
|
266
|
+
const command$5 = {
|
|
267
|
+
command: "auth [token]",
|
|
268
|
+
describe: "Authenticate with GitHub",
|
|
269
|
+
builder: (yargs) => yargs.positional("token", {
|
|
270
|
+
describe: "GitHub token (prompted if not provided)",
|
|
271
|
+
type: "string",
|
|
272
|
+
}),
|
|
273
|
+
handler: async (argv) => {
|
|
274
|
+
await authenticate({
|
|
275
|
+
reporter: createReporter(),
|
|
276
|
+
token: argv.token,
|
|
277
|
+
tokenProvider: new GitHubTokenCliProvider(),
|
|
278
|
+
});
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
|
|
49
282
|
class GitHubService {
|
|
50
283
|
octokit;
|
|
51
284
|
cache;
|
|
@@ -274,221 +507,45 @@ function includesTopic(repo, topic) {
|
|
|
274
507
|
return repo.repositoryTopics.edges.some((it) => it.node.topic.name === topic);
|
|
275
508
|
}
|
|
276
509
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.config = config;
|
|
280
|
-
}
|
|
281
|
-
mustValidate = false;
|
|
282
|
-
config;
|
|
283
|
-
defaultCacheTime = 1800;
|
|
284
|
-
/**
|
|
285
|
-
* Retrieve cache if existent, ignoring the time.
|
|
286
|
-
*
|
|
287
|
-
* The caller is responsible for handling proper validation,
|
|
288
|
-
*/
|
|
289
|
-
retrieveJson(cachekey) {
|
|
290
|
-
const cachefile = path.join(this.config.cacheDir, `${cachekey}.json`);
|
|
291
|
-
if (!fs.existsSync(cachefile)) {
|
|
292
|
-
return undefined;
|
|
293
|
-
}
|
|
294
|
-
const data = fs.readFileSync(cachefile, "utf-8");
|
|
295
|
-
return {
|
|
296
|
-
cacheTime: fs.statSync(cachefile).mtime.getTime(),
|
|
297
|
-
data: (data === "undefined" ? undefined : JSON.parse(data)),
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Save data to cache.
|
|
302
|
-
*/
|
|
303
|
-
storeJson(cachekey, data) {
|
|
304
|
-
const cachefile = path.join(this.config.cacheDir, `${cachekey}.json`);
|
|
305
|
-
if (!fs.existsSync(this.config.cacheDir)) {
|
|
306
|
-
fs.mkdirSync(this.config.cacheDir, { recursive: true });
|
|
307
|
-
}
|
|
308
|
-
fs.writeFileSync(cachefile, data === undefined ? "undefined" : JSON.stringify(data));
|
|
309
|
-
}
|
|
310
|
-
async json(cachekey, block, cachetime = this.defaultCacheTime) {
|
|
311
|
-
const cacheItem = this.mustValidate
|
|
312
|
-
? undefined
|
|
313
|
-
: this.retrieveJson(cachekey);
|
|
314
|
-
const expire = new Date(Date.now() - cachetime * 1000).getTime();
|
|
315
|
-
if (cacheItem !== undefined && cacheItem.cacheTime > expire) {
|
|
316
|
-
return cacheItem.data;
|
|
317
|
-
}
|
|
318
|
-
const result = await block();
|
|
319
|
-
this.storeJson(cachekey, result);
|
|
320
|
-
return result;
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Delete all cached data.
|
|
324
|
-
*/
|
|
325
|
-
cleanup() {
|
|
326
|
-
fs.rmSync(this.config.cacheDir, { recursive: true, force: true });
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
class Config {
|
|
331
|
-
cwd = path.resolve(process__default.cwd());
|
|
332
|
-
configFile = path.join(os.homedir(), ".cals-config.json");
|
|
333
|
-
cacheDir = cachedir("cals-cli");
|
|
334
|
-
configCached = undefined;
|
|
335
|
-
get config() {
|
|
336
|
-
const existingConfig = this.configCached;
|
|
337
|
-
if (existingConfig !== undefined) {
|
|
338
|
-
return existingConfig;
|
|
339
|
-
}
|
|
340
|
-
const config = this.readConfig();
|
|
341
|
-
this.configCached = config;
|
|
342
|
-
return config;
|
|
343
|
-
}
|
|
344
|
-
readConfig() {
|
|
345
|
-
if (!fs.existsSync(this.configFile)) {
|
|
346
|
-
return {};
|
|
347
|
-
}
|
|
348
|
-
try {
|
|
349
|
-
return JSON.parse(fs.readFileSync(this.configFile, "utf-8"));
|
|
350
|
-
}
|
|
351
|
-
catch (e) {
|
|
352
|
-
console.error("Failed", e);
|
|
353
|
-
throw new Error("Failed to read config");
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
getConfig(key) {
|
|
357
|
-
return this.config[key];
|
|
358
|
-
}
|
|
359
|
-
requireConfig(key) {
|
|
360
|
-
const result = this.config[key];
|
|
361
|
-
if (result === undefined) {
|
|
362
|
-
throw Error(`Configuration for ${key} missing. Add manually to ${this.configFile}`);
|
|
363
|
-
}
|
|
364
|
-
return result;
|
|
365
|
-
}
|
|
366
|
-
updateConfig(key, value) {
|
|
367
|
-
const updatedConfig = {
|
|
368
|
-
...this.readConfig(),
|
|
369
|
-
[key]: value, // undefined will remove
|
|
370
|
-
};
|
|
371
|
-
fs.writeFileSync(this.configFile, JSON.stringify(updatedConfig, null, " "));
|
|
372
|
-
this.configCached = updatedConfig;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const CLEAR_WHOLE_LINE = 0;
|
|
377
|
-
function clearLine(stdout) {
|
|
378
|
-
readline.clearLine(stdout, CLEAR_WHOLE_LINE);
|
|
379
|
-
readline.cursorTo(stdout, 0);
|
|
380
|
-
}
|
|
381
|
-
async function readInput(options) {
|
|
382
|
-
const rl = readline.createInterface({
|
|
383
|
-
input: process__default.stdin,
|
|
384
|
-
output: process__default.stdout,
|
|
385
|
-
});
|
|
386
|
-
if (options.silent) {
|
|
387
|
-
rl._writeToOutput = () => { };
|
|
388
|
-
}
|
|
389
|
-
return new Promise((resolve, reject) => {
|
|
390
|
-
const timer = options.timeout
|
|
391
|
-
? setTimeout(() => {
|
|
392
|
-
rl.close();
|
|
393
|
-
reject(new Error("Input timed out"));
|
|
394
|
-
}, options.timeout)
|
|
395
|
-
: null;
|
|
396
|
-
rl.question(options.prompt, (answer) => {
|
|
397
|
-
if (timer)
|
|
398
|
-
clearTimeout(timer);
|
|
399
|
-
rl.close();
|
|
400
|
-
if (options.silent) {
|
|
401
|
-
process__default.stdout.write("\n");
|
|
402
|
-
}
|
|
403
|
-
resolve(answer);
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
class Reporter {
|
|
408
|
-
stdout = process__default.stdout;
|
|
409
|
-
stderr = process__default.stderr;
|
|
410
|
-
format = chalk;
|
|
411
|
-
error(msg) {
|
|
412
|
-
clearLine(this.stderr);
|
|
413
|
-
this.stderr.write(`${this.format.red("error")} ${msg}\n`);
|
|
414
|
-
}
|
|
415
|
-
log(msg) {
|
|
416
|
-
clearLine(this.stdout);
|
|
417
|
-
this.stdout.write(`${msg}\n`);
|
|
418
|
-
}
|
|
419
|
-
warn(msg) {
|
|
420
|
-
clearLine(this.stderr);
|
|
421
|
-
this.stderr.write(`${this.format.yellow("warning")} ${msg}\n`);
|
|
422
|
-
}
|
|
423
|
-
info(msg) {
|
|
424
|
-
clearLine(this.stdout);
|
|
425
|
-
this.stdout.write(`${this.format.blue("info")} ${msg}\n`);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function createReporter() {
|
|
430
|
-
return new Reporter();
|
|
431
|
-
}
|
|
432
|
-
function createCacheProvider(config, argv) {
|
|
433
|
-
const cache = new CacheProvider(config);
|
|
434
|
-
if (argv.validateCache === true) {
|
|
435
|
-
cache.mustValidate = true;
|
|
436
|
-
}
|
|
437
|
-
return cache;
|
|
438
|
-
}
|
|
439
|
-
function createConfig() {
|
|
440
|
-
return new Config();
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
async function generateCloneCommands({ reporter, config, github, org, ...opt }) {
|
|
444
|
-
if (!opt.listGroups && !opt.all && opt.group === undefined) {
|
|
510
|
+
async function generateCloneCommands({ config, github, org, ...opt }) {
|
|
511
|
+
if (!opt.all && opt.group === undefined) {
|
|
445
512
|
yargs(hideBin(process__default.argv)).showHelp();
|
|
446
513
|
return;
|
|
447
514
|
}
|
|
448
515
|
const repos = await github.getOrgRepoList({ org });
|
|
449
516
|
const groups = getGroupedRepos(repos);
|
|
450
|
-
|
|
451
|
-
groups.forEach((it) => {
|
|
452
|
-
reporter.log(it.name);
|
|
453
|
-
});
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
groups.forEach((group) => {
|
|
517
|
+
for (const group of groups) {
|
|
457
518
|
if (opt.group !== undefined && opt.group !== group.name) {
|
|
458
|
-
|
|
519
|
+
continue;
|
|
459
520
|
}
|
|
460
521
|
group.items
|
|
461
522
|
.filter((it) => opt.includeArchived || !it.isArchived)
|
|
462
523
|
.filter((it) => opt.name === undefined || it.name.includes(opt.name))
|
|
463
524
|
.filter((it) => opt.topic === undefined || includesTopic(it, opt.topic))
|
|
464
|
-
.filter((it) => !opt.
|
|
465
|
-
!fs.existsSync(path.resolve(config.cwd, it.name)))
|
|
525
|
+
.filter((it) => !opt.skipCloned || !fs.existsSync(path.resolve(config.cwd, it.name)))
|
|
466
526
|
.forEach((repo) => {
|
|
467
527
|
// The output of this is used to pipe into e.g. bash.
|
|
468
|
-
// We cannot use reporter.log as it adds additional characters.
|
|
469
528
|
process__default.stdout.write(`[ ! -e "${repo.name}" ] && git clone ${repo.sshUrl}\n`);
|
|
470
529
|
});
|
|
471
|
-
}
|
|
530
|
+
}
|
|
472
531
|
}
|
|
473
532
|
const command$4 = {
|
|
474
|
-
command: "
|
|
475
|
-
describe: "Generate
|
|
533
|
+
command: "clone [group]",
|
|
534
|
+
describe: "Generate git clone commands (pipe to bash to execute)",
|
|
476
535
|
builder: (yargs) => yargs
|
|
477
536
|
.positional("group", {
|
|
478
|
-
describe: "
|
|
537
|
+
describe: "Clone only repos in this group",
|
|
538
|
+
type: "string",
|
|
479
539
|
})
|
|
480
540
|
.options("org", {
|
|
481
|
-
|
|
482
|
-
|
|
541
|
+
alias: "o",
|
|
542
|
+
default: "capralifecycle",
|
|
543
|
+
requiresArg: true,
|
|
544
|
+
describe: "GitHub organization",
|
|
483
545
|
type: "string",
|
|
484
546
|
})
|
|
485
547
|
.option("all", {
|
|
486
|
-
describe: "
|
|
487
|
-
type: "boolean",
|
|
488
|
-
})
|
|
489
|
-
.option("list-groups", {
|
|
490
|
-
alias: "l",
|
|
491
|
-
describe: "List available groups",
|
|
548
|
+
describe: "Clone all repos",
|
|
492
549
|
type: "boolean",
|
|
493
550
|
})
|
|
494
551
|
.option("include-archived", {
|
|
@@ -499,37 +556,61 @@ const command$4 = {
|
|
|
499
556
|
.option("name", {
|
|
500
557
|
describe: "Filter to include the specified name",
|
|
501
558
|
type: "string",
|
|
559
|
+
requiresArg: true,
|
|
502
560
|
})
|
|
503
561
|
.option("topic", {
|
|
504
562
|
alias: "t",
|
|
505
563
|
describe: "Filter by specific topic",
|
|
506
564
|
type: "string",
|
|
565
|
+
requiresArg: true,
|
|
507
566
|
})
|
|
508
|
-
.option("
|
|
509
|
-
alias: "
|
|
510
|
-
describe: "
|
|
567
|
+
.option("skip-cloned", {
|
|
568
|
+
alias: "s",
|
|
569
|
+
describe: "Skip repos already cloned in working directory",
|
|
511
570
|
type: "boolean",
|
|
512
571
|
}),
|
|
513
572
|
handler: async (argv) => {
|
|
514
573
|
const config = createConfig();
|
|
515
574
|
return generateCloneCommands({
|
|
516
|
-
reporter: createReporter(),
|
|
517
575
|
config,
|
|
518
576
|
github: await createGitHubService({
|
|
519
577
|
cache: createCacheProvider(config, argv),
|
|
520
578
|
}),
|
|
521
579
|
all: !!argv.all,
|
|
522
|
-
listGroups: !!argv["list-groups"],
|
|
523
580
|
includeArchived: !!argv["include-archived"],
|
|
524
581
|
name: argv.name,
|
|
525
582
|
topic: argv.topic,
|
|
526
|
-
|
|
583
|
+
skipCloned: !!argv["skip-cloned"],
|
|
527
584
|
group: argv.group,
|
|
528
585
|
org: argv.org,
|
|
529
586
|
});
|
|
530
587
|
},
|
|
531
588
|
};
|
|
532
589
|
|
|
590
|
+
const command$3 = {
|
|
591
|
+
command: "groups",
|
|
592
|
+
describe: "List available repository groups in a GitHub organization",
|
|
593
|
+
builder: (yargs) => yargs.options("org", {
|
|
594
|
+
alias: "o",
|
|
595
|
+
default: "capralifecycle",
|
|
596
|
+
requiresArg: true,
|
|
597
|
+
describe: "GitHub organization",
|
|
598
|
+
type: "string",
|
|
599
|
+
}),
|
|
600
|
+
handler: async (argv) => {
|
|
601
|
+
const config = createConfig();
|
|
602
|
+
const reporter = createReporter();
|
|
603
|
+
const github = await createGitHubService({
|
|
604
|
+
cache: createCacheProvider(config, argv),
|
|
605
|
+
});
|
|
606
|
+
const repos = await github.getOrgRepoList({ org: argv.org });
|
|
607
|
+
const groups = getGroupedRepos(repos);
|
|
608
|
+
for (const group of groups) {
|
|
609
|
+
reporter.log(group.name);
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
|
|
533
614
|
function getReposMissingGroup(repos) {
|
|
534
615
|
return repos.filter((it) => getGroup(it) === null);
|
|
535
616
|
}
|
|
@@ -612,13 +693,15 @@ async function listRepos({ reporter, github, includeArchived, name = undefined,
|
|
|
612
693
|
});
|
|
613
694
|
}
|
|
614
695
|
}
|
|
615
|
-
const command$
|
|
616
|
-
command: "
|
|
617
|
-
describe: "List
|
|
696
|
+
const command$2 = {
|
|
697
|
+
command: "repos",
|
|
698
|
+
describe: "List repositories in a GitHub organization",
|
|
618
699
|
builder: (yargs) => yargs
|
|
619
700
|
.options("org", {
|
|
620
|
-
|
|
621
|
-
|
|
701
|
+
alias: "o",
|
|
702
|
+
default: "capralifecycle",
|
|
703
|
+
requiresArg: true,
|
|
704
|
+
describe: "GitHub organization",
|
|
622
705
|
type: "string",
|
|
623
706
|
})
|
|
624
707
|
.option("include-archived", {
|
|
@@ -638,11 +721,13 @@ const command$3 = {
|
|
|
638
721
|
.option("name", {
|
|
639
722
|
describe: "Filter to include the specified name",
|
|
640
723
|
type: "string",
|
|
724
|
+
requiresArg: true,
|
|
641
725
|
})
|
|
642
726
|
.option("topic", {
|
|
643
727
|
alias: "t",
|
|
644
728
|
describe: "Filter by specific topic",
|
|
645
729
|
type: "string",
|
|
730
|
+
requiresArg: true,
|
|
646
731
|
}),
|
|
647
732
|
handler: async (argv) => {
|
|
648
733
|
const config = createConfig();
|
|
@@ -661,34 +746,6 @@ const command$3 = {
|
|
|
661
746
|
},
|
|
662
747
|
};
|
|
663
748
|
|
|
664
|
-
async function setToken({ reporter, token, tokenProvider, }) {
|
|
665
|
-
if (token === undefined) {
|
|
666
|
-
reporter.info("Need API token to talk to GitHub");
|
|
667
|
-
reporter.info("https://github.com/settings/tokens/new?scopes=repo:status,read:repo_hook");
|
|
668
|
-
const inputToken = await readInput({
|
|
669
|
-
prompt: "Enter new GitHub API token: ",
|
|
670
|
-
silent: true,
|
|
671
|
-
});
|
|
672
|
-
token = inputToken;
|
|
673
|
-
}
|
|
674
|
-
await tokenProvider.setToken(token);
|
|
675
|
-
reporter.info("Token saved");
|
|
676
|
-
}
|
|
677
|
-
const command$2 = {
|
|
678
|
-
command: "set-token",
|
|
679
|
-
describe: "Set GitHub token for API calls",
|
|
680
|
-
builder: (yargs) => yargs.positional("token", {
|
|
681
|
-
describe: "Token. If not provided it will be requested as input. Can be generated at https://github.com/settings/tokens/new?scopes=repo:status,read:repo_hook",
|
|
682
|
-
}),
|
|
683
|
-
handler: async (argv) => {
|
|
684
|
-
await setToken({
|
|
685
|
-
reporter: createReporter(),
|
|
686
|
-
token: argv.token,
|
|
687
|
-
tokenProvider: new GitHubTokenCliProvider(),
|
|
688
|
-
});
|
|
689
|
-
},
|
|
690
|
-
};
|
|
691
|
-
|
|
692
749
|
var type = "object";
|
|
693
750
|
var properties = {
|
|
694
751
|
projects: {
|
|
@@ -1200,7 +1257,7 @@ async function askMoveConfirm() {
|
|
|
1200
1257
|
return false;
|
|
1201
1258
|
}
|
|
1202
1259
|
}
|
|
1203
|
-
async function sync({ reporter, github, cals, rootdir,
|
|
1260
|
+
async function sync({ reporter, github, cals, rootdir, clone, move, }) {
|
|
1204
1261
|
const { expectedRepos, definitionRepo } = await getExpectedRepos(reporter, github, cals, rootdir);
|
|
1205
1262
|
const unknownDirs = [];
|
|
1206
1263
|
const foundRepos = [];
|
|
@@ -1260,8 +1317,8 @@ async function sync({ reporter, github, cals, rootdir, askClone, askMove, }) {
|
|
|
1260
1317
|
for (const it of movedRepos) {
|
|
1261
1318
|
reporter.info(` ${it.actualRelpath} -> ${getRelpath(it)}`);
|
|
1262
1319
|
}
|
|
1263
|
-
if (!
|
|
1264
|
-
reporter.info("To move these repos on disk add --
|
|
1320
|
+
if (!move) {
|
|
1321
|
+
reporter.info("To move these repos on disk add --move option");
|
|
1265
1322
|
}
|
|
1266
1323
|
else {
|
|
1267
1324
|
const shouldMove = await askMoveConfirm();
|
|
@@ -1293,8 +1350,8 @@ async function sync({ reporter, github, cals, rootdir, askClone, askMove, }) {
|
|
|
1293
1350
|
for (const it of missingRepos) {
|
|
1294
1351
|
reporter.info(` ${it.id}`);
|
|
1295
1352
|
}
|
|
1296
|
-
if (!
|
|
1297
|
-
reporter.info("To clone these repos add --
|
|
1353
|
+
if (!clone) {
|
|
1354
|
+
reporter.info("To clone these repos add --clone option");
|
|
1298
1355
|
}
|
|
1299
1356
|
else {
|
|
1300
1357
|
reporter.info("You must already have working credentials for GitHub set up for clone to work");
|
|
@@ -1342,16 +1399,17 @@ const command$1 = {
|
|
|
1342
1399
|
command: "sync",
|
|
1343
1400
|
describe: "Sync repositories for working directory",
|
|
1344
1401
|
builder: (yargs) => yargs
|
|
1345
|
-
.option("
|
|
1402
|
+
.option("clone", {
|
|
1346
1403
|
alias: "c",
|
|
1347
|
-
describe: "
|
|
1404
|
+
describe: "Prompt to clone missing repos",
|
|
1348
1405
|
type: "boolean",
|
|
1349
1406
|
})
|
|
1350
|
-
.option("
|
|
1351
|
-
|
|
1407
|
+
.option("move", {
|
|
1408
|
+
alias: "m",
|
|
1409
|
+
describe: "Prompt to move renamed repos",
|
|
1352
1410
|
type: "boolean",
|
|
1353
1411
|
})
|
|
1354
|
-
.usage(`cals
|
|
1412
|
+
.usage(`cals sync
|
|
1355
1413
|
|
|
1356
1414
|
Synchronize all checked out GitHub repositories within the working directory
|
|
1357
1415
|
grouped by the project in the resource definition file. The command can also
|
|
@@ -1400,39 +1458,42 @@ will be stored there.`),
|
|
|
1400
1458
|
github,
|
|
1401
1459
|
cals,
|
|
1402
1460
|
rootdir: dir,
|
|
1403
|
-
|
|
1404
|
-
|
|
1461
|
+
clone: !!argv.clone,
|
|
1462
|
+
move: !!argv.move,
|
|
1405
1463
|
});
|
|
1406
1464
|
},
|
|
1407
1465
|
};
|
|
1408
1466
|
|
|
1409
1467
|
const command = {
|
|
1410
|
-
command: "
|
|
1411
|
-
describe: "
|
|
1412
|
-
builder: (yargs) => yargs
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1468
|
+
command: "topics",
|
|
1469
|
+
describe: "List customer topics in a GitHub organization",
|
|
1470
|
+
builder: (yargs) => yargs.options("org", {
|
|
1471
|
+
alias: "o",
|
|
1472
|
+
default: "capralifecycle",
|
|
1473
|
+
requiresArg: true,
|
|
1474
|
+
describe: "GitHub organization",
|
|
1475
|
+
type: "string",
|
|
1476
|
+
}),
|
|
1477
|
+
handler: async (argv) => {
|
|
1478
|
+
const config = createConfig();
|
|
1479
|
+
const reporter = createReporter();
|
|
1480
|
+
const github = await createGitHubService({
|
|
1481
|
+
cache: createCacheProvider(config, argv),
|
|
1482
|
+
});
|
|
1483
|
+
const repos = await github.getOrgRepoList({ org: argv.org });
|
|
1484
|
+
const topics = new Set();
|
|
1485
|
+
for (const repo of repos) {
|
|
1486
|
+
for (const edge of repo.repositoryTopics.edges) {
|
|
1487
|
+
const name = edge.node.topic.name;
|
|
1488
|
+
if (name.startsWith("customer-")) {
|
|
1489
|
+
topics.add(name);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
const sorted = [...topics].sort((a, b) => a.localeCompare(b));
|
|
1494
|
+
for (const topic of sorted) {
|
|
1495
|
+
reporter.log(topic);
|
|
1496
|
+
}
|
|
1436
1497
|
},
|
|
1437
1498
|
};
|
|
1438
1499
|
|
|
@@ -1459,17 +1520,35 @@ async function main() {
|
|
|
1459
1520
|
process__default.exit(1);
|
|
1460
1521
|
}
|
|
1461
1522
|
await yargs(hideBin(process__default.argv))
|
|
1462
|
-
.usage(`cals
|
|
1523
|
+
.usage(`cals v${version} (build: ${"2026-01-30T15:20:39.822Z"})
|
|
1524
|
+
|
|
1525
|
+
A CLI for managing GitHub repositories.
|
|
1526
|
+
|
|
1527
|
+
Before using, authenticate with: cals auth`)
|
|
1463
1528
|
.scriptName("cals")
|
|
1464
1529
|
.locale("en")
|
|
1530
|
+
.strict()
|
|
1531
|
+
.strictCommands()
|
|
1532
|
+
.strictOptions()
|
|
1465
1533
|
.help("help")
|
|
1534
|
+
.command(command$5)
|
|
1535
|
+
.command(command$4)
|
|
1536
|
+
.command(command$3)
|
|
1537
|
+
.command(command$2)
|
|
1538
|
+
.command(command$1)
|
|
1466
1539
|
.command(command)
|
|
1467
1540
|
.version(version)
|
|
1468
1541
|
.demandCommand()
|
|
1469
|
-
.option("
|
|
1470
|
-
describe: "
|
|
1542
|
+
.option("no-cache", {
|
|
1543
|
+
describe: "Bypass cache and fetch fresh data",
|
|
1471
1544
|
type: "boolean",
|
|
1472
1545
|
})
|
|
1546
|
+
.example("cals auth", "Set GitHub token")
|
|
1547
|
+
.example("cals repos", "List repositories")
|
|
1548
|
+
.example("cals groups", "List repository groups")
|
|
1549
|
+
.example("cals topics", "List customer topics")
|
|
1550
|
+
.example("cals clone --all | bash", "Clone all repos")
|
|
1551
|
+
.example("cals sync", "Pull latest changes")
|
|
1473
1552
|
.parse();
|
|
1474
1553
|
}
|
|
1475
1554
|
|
package/lib/cals-cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cals-cli.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cals-cli.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/lib/index.es.js
CHANGED
package/lib/index.js
CHANGED
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|