@repokit/core 0.0.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.
- package/.vscode/settings.json +11 -0
- package/Cargo.lock +517 -0
- package/Cargo.toml +26 -0
- package/README.md +204 -0
- package/dist/cjs/CommandParser.js +68 -0
- package/dist/cjs/ConfigurationParser.js +46 -0
- package/dist/cjs/RepoKitCommand.js +16 -0
- package/dist/cjs/RepoKitConfig.js +10 -0
- package/dist/cjs/TaskPooler.js +42 -0
- package/dist/cjs/commands/parse_commands.js +15 -0
- package/dist/cjs/commands/parse_configuration.js +15 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/types.js +2 -0
- package/dist/mjs/CommandParser.js +51 -0
- package/dist/mjs/ConfigurationParser.js +31 -0
- package/dist/mjs/RepoKitCommand.js +16 -0
- package/dist/mjs/RepoKitConfig.js +8 -0
- package/dist/mjs/TaskPooler.js +28 -0
- package/dist/mjs/commands/parse_commands.js +4 -0
- package/dist/mjs/commands/parse_configuration.js +4 -0
- package/dist/mjs/index.js +3 -0
- package/dist/mjs/package.json +4 -0
- package/dist/mjs/types.js +1 -0
- package/dist/types/CommandParser.d.ts +5 -0
- package/dist/types/ConfigurationParser.d.ts +4 -0
- package/dist/types/RepoKitCommand.d.ts +14 -0
- package/dist/types/RepoKitConfig.d.ts +6 -0
- package/dist/types/TaskPooler.d.ts +10 -0
- package/dist/types/commands/parse_commands.d.ts +1 -0
- package/dist/types/commands/parse_configuration.d.ts +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/types.d.ts +18 -0
- package/install.sh +43 -0
- package/package.json +40 -0
- package/repokit.ts +36 -0
- package/src/CommandParser.ts +59 -0
- package/src/ConfigurationParser.ts +34 -0
- package/src/RepoKitCommand.ts +24 -0
- package/src/RepoKitConfig.ts +10 -0
- package/src/TaskPooler.ts +31 -0
- package/src/commands/parse_commands.ts +5 -0
- package/src/commands/parse_configuration.ts +5 -0
- package/src/index.ts +3 -0
- package/src/types.ts +22 -0
- package/tsconfig.json +24 -0
- package/workspaces/concurrency/mod.rs +1 -0
- package/workspaces/concurrency/thread_pool.rs +32 -0
- package/workspaces/configuration/configuration.rs +47 -0
- package/workspaces/configuration/configuration_template.ts +23 -0
- package/workspaces/configuration/mod.rs +1 -0
- package/workspaces/executables/external_executable.rs +4 -0
- package/workspaces/executables/intenal_executable.rs +9 -0
- package/workspaces/executables/internal_executable_definition.rs +8 -0
- package/workspaces/executables/mod.rs +3 -0
- package/workspaces/executor/executor.rs +62 -0
- package/workspaces/executor/mod.rs +1 -0
- package/workspaces/external_commands/external_commands.rs +125 -0
- package/workspaces/external_commands/mod.rs +1 -0
- package/workspaces/internal_commands/command_template.ts +24 -0
- package/workspaces/internal_commands/help.rs +146 -0
- package/workspaces/internal_commands/internal_registry.rs +59 -0
- package/workspaces/internal_commands/list_commands.rs +106 -0
- package/workspaces/internal_commands/list_owners.rs +74 -0
- package/workspaces/internal_commands/locate_command.rs +78 -0
- package/workspaces/internal_commands/mod.rs +10 -0
- package/workspaces/internal_commands/onboarder.rs +66 -0
- package/workspaces/internal_commands/register_command.rs +117 -0
- package/workspaces/internal_commands/search_commands.rs +189 -0
- package/workspaces/internal_commands/typescript_command.rs +63 -0
- package/workspaces/internal_commands/upgrade_repokit.rs +76 -0
- package/workspaces/logger/logger.rs +98 -0
- package/workspaces/logger/mod.rs +1 -0
- package/workspaces/main.rs +21 -0
- package/workspaces/repokit/interfaces.rs +41 -0
- package/workspaces/repokit/mod.rs +2 -0
- package/workspaces/repokit/repokit.rs +141 -0
- package/workspaces/validations/command_validations.rs +140 -0
- package/workspaces/validations/mod.rs +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<img src="media/repokit.webp" alt="Alt text" width="150px" />
|
|
2
|
+
|
|
3
|
+
# repokit
|
|
4
|
+
|
|
5
|
+
A knowledgebase for your repository - wrapped in a CLI.
|
|
6
|
+
|
|
7
|
+
Repokit is designed for large teams in complex codebases to publish self-documenting commands, API's, and workflows to a central CLI.
|
|
8
|
+
|
|
9
|
+
The Repokit CLI exists as a living source of documentation and knowledge - growing alongside your team.
|
|
10
|
+
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
### Installation
|
|
14
|
+
|
|
15
|
+
If you do not have node.js setup in your repository, you'll first want to install node.js.
|
|
16
|
+
|
|
17
|
+
[NVM is a populat posix compliant installer](https://github.com/nvm-sh/nvm)
|
|
18
|
+
|
|
19
|
+
Once installed, you can run the following in the root of your repository
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm init
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
If you don't have `typescript` already setup in your repository, you can run:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm i -D typescript && tsc --init
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Next, install repokit:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm i -D @repokit/core
|
|
35
|
+
# or
|
|
36
|
+
yarn add -D @repokit/core
|
|
37
|
+
# or
|
|
38
|
+
pnpm add -D @repokit/core
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Repokit will automatically create a config file named `repokit.ts` for you upon installing. Fill out this file with your desired settings.
|
|
42
|
+
|
|
43
|
+
Here's an example of what Repokit's internal config looks like:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { RepoKitConfig } from "@repokit/core";
|
|
47
|
+
|
|
48
|
+
export const RepoKit = new RepoKitConfig({
|
|
49
|
+
project: "Repokit",
|
|
50
|
+
commands: {
|
|
51
|
+
"build:rust": {
|
|
52
|
+
command: "cargo build --release",
|
|
53
|
+
description: "Build CLI in production mode",
|
|
54
|
+
},
|
|
55
|
+
"install:rust": {
|
|
56
|
+
command: "cargo install --path .",
|
|
57
|
+
description: "Installs the production CLI and adds it to your path",
|
|
58
|
+
},
|
|
59
|
+
"lint:ts": {
|
|
60
|
+
command:
|
|
61
|
+
"yarn oxlint --type-aware --type-check --report-unused-disable-directives --fix && yarn oxfmt",
|
|
62
|
+
description: "Lints typescript files using oxc",
|
|
63
|
+
},
|
|
64
|
+
"build:ts": {
|
|
65
|
+
command: "yarn ts-packager -e src",
|
|
66
|
+
description: "Builds the typescript package",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
To verify your configuration, run
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
repokit
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The CLI will list out its internal commands as well as any commands you registered in your config file.
|
|
79
|
+
|
|
80
|
+
Next run:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
repokit onboard
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Building Your CLI
|
|
87
|
+
|
|
88
|
+
To begin building your CLI, run:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
repokit register ./path/to/your/feature
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This command generates a tool definition for your feature that you can fill out using your tool's API's. When complete, save the file and run:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
repokit <your-tool-name>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The CLI will list out your new tool's API's. To invoke any of them, run:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
repokit <your-tool-name> <your-command-name>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Reasoning about your toolchain
|
|
107
|
+
|
|
108
|
+
As your toolchain grows it's possible to find yourself with hundreds, if not thousands of registered commands.
|
|
109
|
+
|
|
110
|
+
To make reasoning about your commands easier, there are a few internal commands worth getting to know
|
|
111
|
+
|
|
112
|
+
#### `repokit search`
|
|
113
|
+
|
|
114
|
+
`repokit search` is a blanket search over all command definitions. Using it you can search for commands by name, owner, definition, location, or even the tools that it invokes.
|
|
115
|
+
|
|
116
|
+
For example, let's say you wanted to list all commands that invoke `cargo`, you could run
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
repokit search cargo
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
If you wanted to search for all commands owned by an individual or team you could run
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
repokit search <person or team name>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If you wanted to search for commands under a given path you could run
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
repokit search path/within/your/codebase
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
You can query for just about anything you can imagine
|
|
135
|
+
|
|
136
|
+
#### `repokit locate`
|
|
137
|
+
|
|
138
|
+
Code changes can sometimes require updating command definitions. Repokit can easily locate any command's definition by name:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
repokit locate <your-tool-name>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### `repokit owners`
|
|
145
|
+
|
|
146
|
+
If your team makes use of the `owners` attribute when defining your commands, you can easily list all commands owned by an individual or team
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
repokit list <owner>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`repokit list` can also accept `internal | registered | root` as an argument.
|
|
153
|
+
|
|
154
|
+
`internal` will cause repokit to list out all of its internal commands
|
|
155
|
+
|
|
156
|
+
`registered` will cause repokit to list out all of the commands your team has defined around your codebase
|
|
157
|
+
|
|
158
|
+
`root` will cause repokit to list out all commands in your `repokit.ts` config
|
|
159
|
+
|
|
160
|
+
### Best Practices for Registering Commands
|
|
161
|
+
|
|
162
|
+
First and most simply - use verbose descriptions. Document flags, positionals, and environment variables required to invoke your tool.
|
|
163
|
+
|
|
164
|
+
If your tool requires arguments, abstract common combinations of arguments into their own sub-commands. For example, instead of a single `build` command requiring flags to configure it, create sub commands that abstract commonly used combinations of parameters:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { RepoKitCommand } from "@repokit/core";
|
|
168
|
+
|
|
169
|
+
export const Commands = new RepoKitCommand({
|
|
170
|
+
// ... command definition
|
|
171
|
+
commands: {
|
|
172
|
+
"build:local": {
|
|
173
|
+
command: "bazel build --env development",
|
|
174
|
+
description: "Builds in development mode",
|
|
175
|
+
},
|
|
176
|
+
"build:production": {
|
|
177
|
+
command: "bazel build --progress --stats --env production",
|
|
178
|
+
description: "Builds in production mode",
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
When possible, prefer flags and positionals over environment variables. Often times your argv parsers will provide some out-of-the-box validations for free that environment variables simply don't get.
|
|
185
|
+
|
|
186
|
+
#### Working Directories
|
|
187
|
+
|
|
188
|
+
The commands you register onto the repokit toolchain will always be invoked using the working directory of the command's definition.
|
|
189
|
+
|
|
190
|
+
If your command needs to reason about the file system, keep this in mind.
|
|
191
|
+
|
|
192
|
+
## Motivation
|
|
193
|
+
|
|
194
|
+
I worked in a codebase at Google that used just about every programming language in existence. Each team had their own methodology for exposing commands, scripts, and API's for their team's day-to-day development needs.
|
|
195
|
+
|
|
196
|
+
Some teams used shell scripts, some used a tool called `bazel`, and some relied on good old `python ./path/to/my-script.py` or something similar.
|
|
197
|
+
|
|
198
|
+
For engineers new and old to onboard to new features, they were often left stuck combing through these undocumented scripts and tools - tracking down environment variables, positionals, and flags to get necessary commands to succeed.
|
|
199
|
+
|
|
200
|
+
Most of the time landing them in GChat asking for help.
|
|
201
|
+
|
|
202
|
+
During my time there, I never met an engineer with a fully functioning local environment.
|
|
203
|
+
|
|
204
|
+
It was there that I designed an early version **repokit.**
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CommandParser = void 0;
|
|
13
|
+
const node_util_1 = require("node:util");
|
|
14
|
+
const node_path_1 = require("node:path");
|
|
15
|
+
const promises_1 = require("node:fs/promises");
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
17
|
+
const TaskPooler_1 = require("./TaskPooler");
|
|
18
|
+
const RepoKitCommand_1 = require("./RepoKitCommand");
|
|
19
|
+
class CommandParser {
|
|
20
|
+
static parse() {
|
|
21
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
const { paths, root } = this.parsePaths();
|
|
23
|
+
if (!root || !(0, node_fs_1.existsSync)(root) || !(yield (0, promises_1.stat)(root)).isDirectory()) {
|
|
24
|
+
return console.log(JSON.stringify([]));
|
|
25
|
+
}
|
|
26
|
+
const pathList = paths.split(",").filter(Boolean);
|
|
27
|
+
const pool = new TaskPooler_1.TaskPooler();
|
|
28
|
+
const results = yield Promise.all(pathList.map(path => pool.enqueue(() => this.parseCommand((0, node_path_1.join)(root, path)))));
|
|
29
|
+
console.log(JSON.stringify(results.flat()));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
static parseCommand(path) {
|
|
33
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
34
|
+
const commands = [];
|
|
35
|
+
const declaredExports = yield Promise.resolve(`${path}`).then(s => require(s));
|
|
36
|
+
for (const key in declaredExports) {
|
|
37
|
+
if (declaredExports[key] instanceof RepoKitCommand_1.RepoKitCommand) {
|
|
38
|
+
commands.push(Object.assign(Object.assign({}, declaredExports[key].toJSON()), { location: path }));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return commands;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
static parsePaths() {
|
|
45
|
+
try {
|
|
46
|
+
return (0, node_util_1.parseArgs)({
|
|
47
|
+
options: {
|
|
48
|
+
paths: {
|
|
49
|
+
default: "",
|
|
50
|
+
multiple: false,
|
|
51
|
+
short: "p",
|
|
52
|
+
type: "string",
|
|
53
|
+
},
|
|
54
|
+
root: {
|
|
55
|
+
default: "",
|
|
56
|
+
multiple: false,
|
|
57
|
+
short: "r",
|
|
58
|
+
type: "string",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}).values;
|
|
62
|
+
}
|
|
63
|
+
catch (_a) {
|
|
64
|
+
return { paths: "", root: "" };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.CommandParser = CommandParser;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ConfigurationParser = void 0;
|
|
13
|
+
const node_util_1 = require("node:util");
|
|
14
|
+
const node_path_1 = require("node:path");
|
|
15
|
+
const node_fs_1 = require("node:fs");
|
|
16
|
+
const RepoKitConfig_1 = require("./RepoKitConfig");
|
|
17
|
+
class ConfigurationParser {
|
|
18
|
+
static parse() {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
const root = this.parseRoot();
|
|
21
|
+
const path = (0, node_path_1.join)(root, "repokit.ts");
|
|
22
|
+
if (!(0, node_fs_1.existsSync)(path)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const config = yield Promise.resolve(`${path}`).then(s => require(s));
|
|
26
|
+
for (const key in config) {
|
|
27
|
+
if (config[key] instanceof RepoKitConfig_1.RepoKitConfig) {
|
|
28
|
+
return console.log(JSON.stringify(config[key]));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
static parseRoot() {
|
|
34
|
+
return (0, node_util_1.parseArgs)({
|
|
35
|
+
options: {
|
|
36
|
+
root: {
|
|
37
|
+
default: "",
|
|
38
|
+
multiple: false,
|
|
39
|
+
short: "r",
|
|
40
|
+
type: "string",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}).values.root;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.ConfigurationParser = ConfigurationParser;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RepoKitCommand = void 0;
|
|
4
|
+
class RepoKitCommand {
|
|
5
|
+
constructor({ name, description, owner = "", commands = {}, }) {
|
|
6
|
+
this.name = name;
|
|
7
|
+
this.owner = owner;
|
|
8
|
+
this.commands = commands;
|
|
9
|
+
this.description = description;
|
|
10
|
+
}
|
|
11
|
+
toJSON() {
|
|
12
|
+
const { name, owner, commands, description } = this;
|
|
13
|
+
return { name, owner, commands, description };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.RepoKitCommand = RepoKitCommand;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RepoKitConfig = void 0;
|
|
4
|
+
class RepoKitConfig {
|
|
5
|
+
constructor({ project, commands = {} }) {
|
|
6
|
+
this.project = project;
|
|
7
|
+
this.commands = commands;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.RepoKitConfig = RepoKitConfig;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.TaskPooler = void 0;
|
|
13
|
+
const event_emitter_1 = require("@figliolia/event-emitter");
|
|
14
|
+
class TaskPooler {
|
|
15
|
+
constructor(maxSize = 10) {
|
|
16
|
+
this.maxSize = maxSize;
|
|
17
|
+
this.IDs = new event_emitter_1.AutoIncrementingID();
|
|
18
|
+
this.runningTasks = new Map();
|
|
19
|
+
}
|
|
20
|
+
enqueue(task) {
|
|
21
|
+
return new Promise(resolve => {
|
|
22
|
+
if (this.runningTasks.size < 10) {
|
|
23
|
+
return resolve(this.indexRunningTask(task));
|
|
24
|
+
}
|
|
25
|
+
resolve(this.indexBehindNextOpening(task));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
indexRunningTask(task) {
|
|
29
|
+
const ID = this.IDs.get();
|
|
30
|
+
const promise = task();
|
|
31
|
+
this.runningTasks.set(ID, promise);
|
|
32
|
+
void promise.finally(() => this.runningTasks.delete(ID));
|
|
33
|
+
return promise;
|
|
34
|
+
}
|
|
35
|
+
indexBehindNextOpening(task) {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
yield Promise.race(this.runningTasks.values());
|
|
38
|
+
return this.indexRunningTask(task);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.TaskPooler = TaskPooler;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const CommandParser_1 = require("../CommandParser");
|
|
13
|
+
void (() => __awaiter(void 0, void 0, void 0, function* () {
|
|
14
|
+
yield CommandParser_1.CommandParser.parse();
|
|
15
|
+
}))();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const ConfigurationParser_1 = require("../ConfigurationParser");
|
|
13
|
+
void (() => __awaiter(void 0, void 0, void 0, function* () {
|
|
14
|
+
yield ConfigurationParser_1.ConfigurationParser.parse();
|
|
15
|
+
}))();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./RepoKitConfig"), exports);
|
|
18
|
+
__exportStar(require("./RepoKitCommand"), exports);
|
|
19
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { stat } from "node:fs/promises";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { TaskPooler } from "./TaskPooler.js";
|
|
6
|
+
import { RepoKitCommand } from "./RepoKitCommand.js";
|
|
7
|
+
export class CommandParser {
|
|
8
|
+
static async parse() {
|
|
9
|
+
const { paths, root } = this.parsePaths();
|
|
10
|
+
if (!root || !existsSync(root) || !(await stat(root)).isDirectory()) {
|
|
11
|
+
return console.log(JSON.stringify([]));
|
|
12
|
+
}
|
|
13
|
+
const pathList = paths.split(",").filter(Boolean);
|
|
14
|
+
const pool = new TaskPooler();
|
|
15
|
+
const results = await Promise.all(pathList.map(path => pool.enqueue(() => this.parseCommand(join(root, path)))));
|
|
16
|
+
console.log(JSON.stringify(results.flat()));
|
|
17
|
+
}
|
|
18
|
+
static async parseCommand(path) {
|
|
19
|
+
const commands = [];
|
|
20
|
+
const declaredExports = await import(path);
|
|
21
|
+
for (const key in declaredExports) {
|
|
22
|
+
if (declaredExports[key] instanceof RepoKitCommand) {
|
|
23
|
+
commands.push({ ...declaredExports[key].toJSON(), location: path });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return commands;
|
|
27
|
+
}
|
|
28
|
+
static parsePaths() {
|
|
29
|
+
try {
|
|
30
|
+
return parseArgs({
|
|
31
|
+
options: {
|
|
32
|
+
paths: {
|
|
33
|
+
default: "",
|
|
34
|
+
multiple: false,
|
|
35
|
+
short: "p",
|
|
36
|
+
type: "string",
|
|
37
|
+
},
|
|
38
|
+
root: {
|
|
39
|
+
default: "",
|
|
40
|
+
multiple: false,
|
|
41
|
+
short: "r",
|
|
42
|
+
type: "string",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}).values;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { paths: "", root: "" };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { RepoKitConfig } from "./RepoKitConfig.js";
|
|
5
|
+
export class ConfigurationParser {
|
|
6
|
+
static async parse() {
|
|
7
|
+
const root = this.parseRoot();
|
|
8
|
+
const path = join(root, "repokit.ts");
|
|
9
|
+
if (!existsSync(path)) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const config = await import(path);
|
|
13
|
+
for (const key in config) {
|
|
14
|
+
if (config[key] instanceof RepoKitConfig) {
|
|
15
|
+
return console.log(JSON.stringify(config[key]));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
static parseRoot() {
|
|
20
|
+
return parseArgs({
|
|
21
|
+
options: {
|
|
22
|
+
root: {
|
|
23
|
+
default: "",
|
|
24
|
+
multiple: false,
|
|
25
|
+
short: "r",
|
|
26
|
+
type: "string",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
}).values.root;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class RepoKitCommand {
|
|
2
|
+
name;
|
|
3
|
+
owner;
|
|
4
|
+
description;
|
|
5
|
+
commands;
|
|
6
|
+
constructor({ name, description, owner = "", commands = {}, }) {
|
|
7
|
+
this.name = name;
|
|
8
|
+
this.owner = owner;
|
|
9
|
+
this.commands = commands;
|
|
10
|
+
this.description = description;
|
|
11
|
+
}
|
|
12
|
+
toJSON() {
|
|
13
|
+
const { name, owner, commands, description } = this;
|
|
14
|
+
return { name, owner, commands, description };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AutoIncrementingID } from "@figliolia/event-emitter";
|
|
2
|
+
export class TaskPooler {
|
|
3
|
+
maxSize;
|
|
4
|
+
IDs = new AutoIncrementingID();
|
|
5
|
+
runningTasks = new Map();
|
|
6
|
+
constructor(maxSize = 10) {
|
|
7
|
+
this.maxSize = maxSize;
|
|
8
|
+
}
|
|
9
|
+
enqueue(task) {
|
|
10
|
+
return new Promise(resolve => {
|
|
11
|
+
if (this.runningTasks.size < 10) {
|
|
12
|
+
return resolve(this.indexRunningTask(task));
|
|
13
|
+
}
|
|
14
|
+
resolve(this.indexBehindNextOpening(task));
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
indexRunningTask(task) {
|
|
18
|
+
const ID = this.IDs.get();
|
|
19
|
+
const promise = task();
|
|
20
|
+
this.runningTasks.set(ID, promise);
|
|
21
|
+
void promise.finally(() => this.runningTasks.delete(ID));
|
|
22
|
+
return promise;
|
|
23
|
+
}
|
|
24
|
+
async indexBehindNextOpening(task) {
|
|
25
|
+
await Promise.race(this.runningTasks.values());
|
|
26
|
+
return this.indexRunningTask(task);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ICommand, IRepoKitCommand } from "./types";
|
|
2
|
+
export declare class RepoKitCommand {
|
|
3
|
+
name: string;
|
|
4
|
+
owner: string;
|
|
5
|
+
description: string;
|
|
6
|
+
commands: Record<string, ICommand>;
|
|
7
|
+
constructor({ name, description, owner, commands, }: IRepoKitCommand);
|
|
8
|
+
toJSON(): {
|
|
9
|
+
name: string;
|
|
10
|
+
owner: string;
|
|
11
|
+
commands: Record<string, ICommand>;
|
|
12
|
+
description: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AsyncTask } from "./types";
|
|
2
|
+
export declare class TaskPooler<T> {
|
|
3
|
+
maxSize: number;
|
|
4
|
+
private readonly IDs;
|
|
5
|
+
private readonly runningTasks;
|
|
6
|
+
constructor(maxSize?: number);
|
|
7
|
+
enqueue(task: AsyncTask<T>): Promise<T>;
|
|
8
|
+
private indexRunningTask;
|
|
9
|
+
private indexBehindNextOpening;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|