@knpkv/codecommit 0.5.5 → 0.7.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 +35 -9
- package/dist/package.json +15 -15
- package/dist/src/FilterService.d.ts +84 -0
- package/dist/src/FilterService.d.ts.map +1 -0
- package/dist/src/FilterService.js +85 -0
- package/dist/src/FilterService.js.map +1 -0
- package/dist/src/bin.js +135 -48
- package/dist/src/bin.js.map +1 -1
- package/dist/src/filterPresets.d.ts +24 -0
- package/dist/src/filterPresets.d.ts.map +1 -0
- package/dist/src/filterPresets.js +46 -0
- package/dist/src/filterPresets.js.map +1 -0
- package/dist/src/main.d.ts +3 -1
- package/dist/src/main.d.ts.map +1 -1
- package/dist/src/main.js +17 -5
- package/dist/src/main.js.map +1 -1
- package/dist/src/tui/App.d.ts.map +1 -1
- package/dist/src/tui/App.js +3 -2
- package/dist/src/tui/App.js.map +1 -1
- package/dist/src/tui/atoms/actions.d.ts +6 -6
- package/dist/src/tui/atoms/actions.d.ts.map +1 -1
- package/dist/src/tui/atoms/actions.js +30 -23
- package/dist/src/tui/atoms/actions.js.map +1 -1
- package/dist/src/tui/atoms/app.d.ts +7 -7
- package/dist/src/tui/atoms/app.d.ts.map +1 -1
- package/dist/src/tui/atoms/app.js +11 -12
- package/dist/src/tui/atoms/app.js.map +1 -1
- package/dist/src/tui/atoms/runtime.d.ts +3 -3
- package/dist/src/tui/atoms/runtime.d.ts.map +1 -1
- package/dist/src/tui/atoms/runtime.js +8 -8
- package/dist/src/tui/atoms/runtime.js.map +1 -1
- package/dist/src/tui/atoms/ui.d.ts +1 -1
- package/dist/src/tui/atoms/ui.d.ts.map +1 -1
- package/dist/src/tui/atoms/ui.js +1 -1
- package/dist/src/tui/atoms/ui.js.map +1 -1
- package/dist/src/tui/components/DetailsView.d.ts.map +1 -1
- package/dist/src/tui/components/DetailsView.js +8 -7
- package/dist/src/tui/components/DetailsView.js.map +1 -1
- package/dist/src/tui/components/Footer.js +1 -1
- package/dist/src/tui/components/Footer.js.map +1 -1
- package/dist/src/tui/components/Header.d.ts.map +1 -1
- package/dist/src/tui/components/Header.js +5 -4
- package/dist/src/tui/components/Header.js.map +1 -1
- package/dist/src/tui/components/ListItemRow.d.ts.map +1 -1
- package/dist/src/tui/components/ListItemRow.js +3 -3
- package/dist/src/tui/components/ListItemRow.js.map +1 -1
- package/dist/src/tui/components/MainList.d.ts.map +1 -1
- package/dist/src/tui/components/MainList.js +5 -4
- package/dist/src/tui/components/MainList.js.map +1 -1
- package/dist/src/tui/components/QuickFilters.d.ts.map +1 -1
- package/dist/src/tui/components/QuickFilters.js +3 -2
- package/dist/src/tui/components/QuickFilters.js.map +1 -1
- package/dist/src/tui/components/SettingsAccountsTab.js +1 -1
- package/dist/src/tui/components/SettingsAccountsTab.js.map +1 -1
- package/dist/src/tui/components/SettingsThemeTab.js +1 -1
- package/dist/src/tui/components/SettingsThemeTab.js.map +1 -1
- package/dist/src/tui/components/SettingsView.js +1 -1
- package/dist/src/tui/components/SettingsView.js.map +1 -1
- package/dist/src/tui/context/theme.js +1 -1
- package/dist/src/tui/context/theme.js.map +1 -1
- package/dist/src/tui/hooks/useKeyboardNav.d.ts.map +1 -1
- package/dist/src/tui/hooks/useKeyboardNav.js +5 -4
- package/dist/src/tui/hooks/useKeyboardNav.js.map +1 -1
- package/dist/src/tui/hooks/useListNavigation.js +1 -1
- package/dist/src/tui/hooks/useListNavigation.js.map +1 -1
- package/dist/src/tui/ui/DialogCommand.js +1 -1
- package/dist/src/tui/ui/DialogCommand.js.map +1 -1
- package/dist/src/tui/ui/DialogCreatePR.d.ts.map +1 -1
- package/dist/src/tui/ui/DialogCreatePR.js +5 -4
- package/dist/src/tui/ui/DialogCreatePR.js.map +1 -1
- package/dist/src/tui/utils/prTemplates.d.ts +2 -2
- package/dist/src/tui/utils/prTemplates.d.ts.map +1 -1
- package/dist/src/tui/utils/prTemplates.js +35 -36
- package/dist/src/tui/utils/prTemplates.js.map +1 -1
- package/dist/test/ConfigService.test.js +10 -7
- package/dist/test/ConfigService.test.js.map +1 -1
- package/dist/test/FilterService.test.d.ts +2 -0
- package/dist/test/FilterService.test.d.ts.map +1 -0
- package/dist/test/FilterService.test.js +209 -0
- package/dist/test/FilterService.test.js.map +1 -0
- package/dist/test/bin.test.d.ts +2 -0
- package/dist/test/bin.test.d.ts.map +1 -0
- package/dist/test/bin.test.js +36 -0
- package/dist/test/bin.test.js.map +1 -0
- package/dist/test/filterPresets.test.d.ts +2 -0
- package/dist/test/filterPresets.test.d.ts.map +1 -0
- package/dist/test/filterPresets.test.js +166 -0
- package/dist/test/filterPresets.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/skills/codecommit/SKILL.md +69 -0
- package/skills/codecommit/agents/openai.yaml +4 -0
package/README.md
CHANGED
|
@@ -57,15 +57,16 @@ codecommit web [--port 3000] [--hostname 127.0.0.1]
|
|
|
57
57
|
codecommit pr list [options]
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
| Option | Alias | Description
|
|
61
|
-
| ----------- | ----- |
|
|
62
|
-
| `--profile` | `-p` | AWS profile
|
|
63
|
-
| `--region` | `-r` | AWS region
|
|
64
|
-
| `--status` | `-s` | PR status
|
|
65
|
-
| `--all` | `-a` | Show all PRs
|
|
66
|
-
| `--repo` | | Filter by repository
|
|
67
|
-
| `--author` | | Filter by author
|
|
68
|
-
| `--
|
|
60
|
+
| Option | Alias | Description | Default |
|
|
61
|
+
| ----------- | ----- | ---------------------------------------------- | ----------- |
|
|
62
|
+
| `--profile` | `-p` | AWS profile (ignored with --filter) | `default` |
|
|
63
|
+
| `--region` | `-r` | AWS region (ignored with --filter) | `us-east-1` |
|
|
64
|
+
| `--status` | `-s` | PR status, OPEN/CLOSED (ignored with --filter) | `OPEN` |
|
|
65
|
+
| `--all` | `-a` | Show all PRs (ignored with --filter) | `false` |
|
|
66
|
+
| `--repo` | | Filter by repository | - |
|
|
67
|
+
| `--author` | | Filter by author | - |
|
|
68
|
+
| `--filter` | | Named preset, OPEN-only (see below) | - |
|
|
69
|
+
| `--json` | | Output as JSON | `false` |
|
|
69
70
|
|
|
70
71
|
```bash
|
|
71
72
|
codecommit pr list
|
|
@@ -76,6 +77,31 @@ codecommit pr list --author jane
|
|
|
76
77
|
codecommit pr list --json
|
|
77
78
|
```
|
|
78
79
|
|
|
80
|
+
#### Filter presets (`--filter`)
|
|
81
|
+
|
|
82
|
+
When `--filter` is set, the command fans out across **every enabled account**
|
|
83
|
+
in `~/.codecommit/config.json` (set up via `codecommit tui`) and returns the
|
|
84
|
+
merged list, sorted by last-modified-date. Presets operate on **OPEN PRs only**,
|
|
85
|
+
so `--profile`, `--region`, `--status`, and `--all` are all ignored when
|
|
86
|
+
`--filter` is set. Combine with `--json`, `--repo`, or `--author` for further
|
|
87
|
+
narrowing. If any account fails (e.g. an expired SSO session), a
|
|
88
|
+
`⚠ N account(s) failed` summary is printed to stderr and the PRs from the
|
|
89
|
+
accounts that succeeded are still returned.
|
|
90
|
+
|
|
91
|
+
| Preset | Matches |
|
|
92
|
+
| ----------------- | ------------------------------------------------------------------------ |
|
|
93
|
+
| `mine` | Open PRs you authored (matched against `getCallerIdentity` per profile) |
|
|
94
|
+
| `needs-my-review` | Open PRs awaiting your approval (you're in an unsatisfied approval pool) |
|
|
95
|
+
| `stale` | Open PRs with no activity for more than 7 days |
|
|
96
|
+
| `conflicting` | Open PRs with merge conflicts |
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
codecommit pr list --filter mine --json # all my open PRs everywhere
|
|
100
|
+
codecommit pr list --filter needs-my-review # what I need to review
|
|
101
|
+
codecommit pr list --filter stale --repo my-repo # stale PRs in one repo
|
|
102
|
+
codecommit pr list --filter conflicting --json
|
|
103
|
+
```
|
|
104
|
+
|
|
79
105
|
Output:
|
|
80
106
|
|
|
81
107
|
```
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knpkv/codecommit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "TUI for browsing AWS CodeCommit PRs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"dist"
|
|
15
|
+
"dist",
|
|
16
|
+
"skills"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"start": "bun src/bin.ts",
|
|
@@ -25,26 +26,25 @@
|
|
|
25
26
|
"docgen": "docgen"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
|
-
"@effect
|
|
29
|
-
"@effect/
|
|
30
|
-
"@effect/platform": "
|
|
31
|
-
"@
|
|
32
|
-
"@effect/platform-node": "latest",
|
|
29
|
+
"@effect/atom-react": "4.0.0-beta.87",
|
|
30
|
+
"@effect/platform-bun": "4.0.0-beta.87",
|
|
31
|
+
"@effect/platform-node": "4.0.0-beta.87",
|
|
32
|
+
"@knpkv/agent-skills": "workspace:^",
|
|
33
33
|
"@knpkv/codecommit-core": "workspace:^",
|
|
34
34
|
"@knpkv/codecommit-web": "workspace:^",
|
|
35
35
|
"@opentui/core": "https://pkg.pr.new/anomalyco/opentui/@opentui/core@367a94087821b3b5feedd35bbb57df43b10a286e",
|
|
36
36
|
"@opentui/react": "https://pkg.pr.new/anomalyco/opentui/@opentui/react@367a94087821b3b5feedd35bbb57df43b10a286e",
|
|
37
|
-
"effect": "
|
|
38
|
-
"
|
|
39
|
-
"react": "^19.1.0",
|
|
37
|
+
"effect": "4.0.0-beta.87",
|
|
38
|
+
"react": "^19.2.7",
|
|
40
39
|
"tslib": "^2.8.1"
|
|
41
40
|
},
|
|
42
41
|
"devDependencies": {
|
|
43
|
-
"@effect/vitest": "
|
|
44
|
-
"@storybook/react": "^
|
|
45
|
-
"@types/bun": "^1.3.
|
|
42
|
+
"@effect/vitest": "4.0.0-beta.87",
|
|
43
|
+
"@storybook/react": "^10.4.6",
|
|
44
|
+
"@types/bun": "^1.3.14",
|
|
46
45
|
"@types/node": "latest",
|
|
47
|
-
"@types/react": "^19.
|
|
48
|
-
"
|
|
46
|
+
"@types/react": "^19.2.17",
|
|
47
|
+
"storybook": "^10.4.6",
|
|
48
|
+
"vitest": "^4.1.9"
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-account `--filter` orchestration for `pr list`.
|
|
3
|
+
*
|
|
4
|
+
* Extracts the fan-out workflow that backs the named filter presets out of the
|
|
5
|
+
* CLI entrypoint (`bin.ts`) so the entrypoint keeps only presentation. The
|
|
6
|
+
* service depends on {@link AwsClient.AwsClient} and
|
|
7
|
+
* {@link ConfigService.ConfigService} and reuses the pure `matchesPreset` /
|
|
8
|
+
* `matchesRepoAuthor` predicates from `./filterPresets.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Behaviour mirrors the original inline block exactly:
|
|
11
|
+
*
|
|
12
|
+
* - {@link FilterService.Service.resolveTargets} loads config, keeps only
|
|
13
|
+
* enabled accounts, and flattens to `{ profile, region }` targets.
|
|
14
|
+
* - {@link FilterService.Service.collect} resolves caller identity once per
|
|
15
|
+
* profile (for the identity-comparing presets), fans out OPEN-only PR fetches
|
|
16
|
+
* across targets (concurrency 4), filters by preset + repo/author, collects
|
|
17
|
+
* per-account failures, and returns PRs sorted by `lastModifiedDate` desc.
|
|
18
|
+
*
|
|
19
|
+
* @category Service
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { AwsClient, ConfigService, type Domain, type Errors } from "@knpkv/codecommit-core";
|
|
23
|
+
import type { AwsProfileName, AwsRegion } from "@knpkv/codecommit-core/Domain.js";
|
|
24
|
+
import { Context, Effect, Layer, type Option } from "effect";
|
|
25
|
+
import { type FilterPreset } from "./filterPresets.js";
|
|
26
|
+
/** A single `{ profile, region }` account/region pair to scan. */
|
|
27
|
+
export interface FilterTarget {
|
|
28
|
+
readonly profile: AwsProfileName;
|
|
29
|
+
readonly region: AwsRegion;
|
|
30
|
+
}
|
|
31
|
+
/** Repo/author narrowing options applied on top of the preset. */
|
|
32
|
+
export interface FilterOptions {
|
|
33
|
+
readonly repo: Option.Option<string>;
|
|
34
|
+
readonly author: Option.Option<string>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Structured outcome of a cross-account scan.
|
|
38
|
+
*
|
|
39
|
+
* - `prs` — matching PRs, sorted by `lastModifiedDate` desc.
|
|
40
|
+
* - `failures` — `"<profile>/<region>: <message>"` per failed account.
|
|
41
|
+
* - `unresolvedProfiles` — profiles whose caller identity didn't resolve, so
|
|
42
|
+
* identity-comparing presets may have incomplete results for them.
|
|
43
|
+
*/
|
|
44
|
+
export interface FilterResult {
|
|
45
|
+
readonly prs: ReadonlyArray<Domain.PullRequest>;
|
|
46
|
+
readonly failures: ReadonlyArray<string>;
|
|
47
|
+
readonly unresolvedProfiles: ReadonlyArray<string>;
|
|
48
|
+
}
|
|
49
|
+
type FilterCollectError = Errors.AwsApiError | Errors.AwsCredentialError | Errors.AwsThrottleError;
|
|
50
|
+
/**
|
|
51
|
+
* Cross-account filter orchestration service.
|
|
52
|
+
*
|
|
53
|
+
* @category models
|
|
54
|
+
*/
|
|
55
|
+
export interface FilterServiceShape {
|
|
56
|
+
readonly resolveTargets: Effect.Effect<ReadonlyArray<FilterTarget>, unknown>;
|
|
57
|
+
readonly collect: (preset: FilterPreset, targets: ReadonlyArray<FilterTarget>, opts: FilterOptions, now?: Date) => Effect.Effect<FilterResult, FilterCollectError>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Cross-account filter orchestration service.
|
|
61
|
+
*
|
|
62
|
+
* @category models
|
|
63
|
+
*/
|
|
64
|
+
export declare namespace FilterService {
|
|
65
|
+
interface Service extends FilterServiceShape {
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
declare const FilterService_base: Context.ServiceClass<FilterService, "@knpkv/codecommit/FilterService", FilterServiceShape>;
|
|
69
|
+
/**
|
|
70
|
+
* Cross-account filter orchestration service.
|
|
71
|
+
*
|
|
72
|
+
* @category Service
|
|
73
|
+
*/
|
|
74
|
+
export declare class FilterService extends FilterService_base {
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Live layer. Requires {@link AwsClient.AwsClient} and
|
|
78
|
+
* {@link ConfigService.ConfigService} (wired by the caller).
|
|
79
|
+
*
|
|
80
|
+
* @category Layer
|
|
81
|
+
*/
|
|
82
|
+
export declare const FilterServiceLive: Layer.Layer<FilterService, Errors.AwsCredentialError | Errors.AwsThrottleError | Errors.AwsApiError, AwsClient.AwsClient | ConfigService.ConfigService>;
|
|
83
|
+
export {};
|
|
84
|
+
//# sourceMappingURL=FilterService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilterService.d.ts","sourceRoot":"","sources":["../../src/FilterService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAC3F,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAA;AACjF,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,MAAM,EAAU,MAAM,QAAQ,CAAA;AACpE,OAAO,EAAE,KAAK,YAAY,EAAoC,MAAM,oBAAoB,CAAA;AAExF,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;CAC3B;AAED,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;CACvC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IAC/C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACxC,QAAQ,CAAC,kBAAkB,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACnD;AAED,KAAK,kBAAkB,GAAG,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,kBAAkB,GAAG,MAAM,CAAC,gBAAgB,CAAA;AAElG;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAA;IAC5E,QAAQ,CAAC,OAAO,EAAE,CAChB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,EACpC,IAAI,EAAE,aAAa,EACnB,GAAG,CAAC,EAAE,IAAI,KACP,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAA;CACrD;AAsFD;;;;GAIG;AACH,MAAM,CAAC,OAAO,WAAW,aAAa,CAAC;IACrC,UAAiB,OAAQ,SAAQ,kBAAkB;KAAG;CACvD;;AAED;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,kBAGG;CAAG;AAEzC;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,yJAAoC,CAAA"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-account `--filter` orchestration for `pr list`.
|
|
3
|
+
*
|
|
4
|
+
* Extracts the fan-out workflow that backs the named filter presets out of the
|
|
5
|
+
* CLI entrypoint (`bin.ts`) so the entrypoint keeps only presentation. The
|
|
6
|
+
* service depends on {@link AwsClient.AwsClient} and
|
|
7
|
+
* {@link ConfigService.ConfigService} and reuses the pure `matchesPreset` /
|
|
8
|
+
* `matchesRepoAuthor` predicates from `./filterPresets.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Behaviour mirrors the original inline block exactly:
|
|
11
|
+
*
|
|
12
|
+
* - {@link FilterService.Service.resolveTargets} loads config, keeps only
|
|
13
|
+
* enabled accounts, and flattens to `{ profile, region }` targets.
|
|
14
|
+
* - {@link FilterService.Service.collect} resolves caller identity once per
|
|
15
|
+
* profile (for the identity-comparing presets), fans out OPEN-only PR fetches
|
|
16
|
+
* across targets (concurrency 4), filters by preset + repo/author, collects
|
|
17
|
+
* per-account failures, and returns PRs sorted by `lastModifiedDate` desc.
|
|
18
|
+
*
|
|
19
|
+
* @category Service
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { AwsClient, ConfigService } from "@knpkv/codecommit-core";
|
|
23
|
+
import { Context, Effect, Layer, Stream } from "effect";
|
|
24
|
+
import { matchesPreset, matchesRepoAuthor } from "./filterPresets.js";
|
|
25
|
+
const make = Effect.gen(function* () {
|
|
26
|
+
const aws = yield* AwsClient.AwsClient;
|
|
27
|
+
const cs = yield* ConfigService.ConfigService;
|
|
28
|
+
const resolveTargets = Effect.gen(function* () {
|
|
29
|
+
const config = yield* cs.load;
|
|
30
|
+
return config.accounts
|
|
31
|
+
.filter((a) => a.enabled)
|
|
32
|
+
.flatMap((a) => a.regions.map((r) => ({ profile: a.profile, region: r })));
|
|
33
|
+
});
|
|
34
|
+
const collect = (preset, targets, opts, now = new Date()) => Effect.gen(function* () {
|
|
35
|
+
// Resolve caller identity once per profile (deduped per profile within this
|
|
36
|
+
// run, not cached across runs) for presets that compare against "me".
|
|
37
|
+
const callerByProfile = new Map();
|
|
38
|
+
// Profiles whose caller-identity didn't resolve (lookup failed or returned
|
|
39
|
+
// no username). For the identity-comparing presets this means their PRs
|
|
40
|
+
// can't be matched, so we surface a warning rather than silently dropping them.
|
|
41
|
+
const unresolvedCallerProfiles = [];
|
|
42
|
+
if (preset === "mine" || preset === "needs-my-review") {
|
|
43
|
+
const uniqueProfiles = [...new Map(targets.map((t) => [t.profile, t])).values()];
|
|
44
|
+
const callers = yield* Effect.forEach(uniqueProfiles, (acct) => Effect.catchIf(Effect.map(aws.getCallerIdentity(acct), (id) => ({
|
|
45
|
+
profile: acct.profile,
|
|
46
|
+
username: id.username
|
|
47
|
+
})), () => true, () => Effect.succeed({ profile: acct.profile, username: null }), () => Effect.succeed({ profile: acct.profile, username: null })), { concurrency: 4 });
|
|
48
|
+
for (const { profile: p, username } of callers) {
|
|
49
|
+
if (username)
|
|
50
|
+
callerByProfile.set(p, username);
|
|
51
|
+
else
|
|
52
|
+
unresolvedCallerProfiles.push(p);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const collected = yield* Effect.forEach(targets, (acct) => Effect.catchIf(aws.getPullRequests(acct, { status: "OPEN" }).pipe(Stream.filter((pr) => matchesPreset(preset, pr, callerByProfile, now) && matchesRepoAuthor(pr, opts.repo, opts.author)), Stream.runCollect, Effect.map((chunk) => ({ ok: Array.from(chunk), failed: null }))), () => true,
|
|
56
|
+
// Don't silently coalesce auth/permission failures to "no matches" —
|
|
57
|
+
// collect the failure so it can be surfaced after the results.
|
|
58
|
+
(e) => Effect.succeed({
|
|
59
|
+
ok: [],
|
|
60
|
+
failed: `${acct.profile}/${acct.region}: ${e.message}`
|
|
61
|
+
}), (e) => Effect.succeed({
|
|
62
|
+
ok: [],
|
|
63
|
+
failed: `${acct.profile}/${acct.region}: ${e.message}`
|
|
64
|
+
})), { concurrency: 4 });
|
|
65
|
+
const prs = collected.flatMap((r) => r.ok).sort((a, b) => b.lastModifiedDate.getTime() - a.lastModifiedDate.getTime());
|
|
66
|
+
const failures = collected.flatMap((r) => (r.failed === null ? [] : [r.failed]));
|
|
67
|
+
return { prs, failures, unresolvedProfiles: unresolvedCallerProfiles };
|
|
68
|
+
});
|
|
69
|
+
return { resolveTargets, collect };
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* Cross-account filter orchestration service.
|
|
73
|
+
*
|
|
74
|
+
* @category Service
|
|
75
|
+
*/
|
|
76
|
+
export class FilterService extends Context.Service()("@knpkv/codecommit/FilterService") {
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Live layer. Requires {@link AwsClient.AwsClient} and
|
|
80
|
+
* {@link ConfigService.ConfigService} (wired by the caller).
|
|
81
|
+
*
|
|
82
|
+
* @category Layer
|
|
83
|
+
*/
|
|
84
|
+
export const FilterServiceLive = Layer.effect(FilterService, make);
|
|
85
|
+
//# sourceMappingURL=FilterService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilterService.js","sourceRoot":"","sources":["../../src/FilterService.ts"],"names":[],"mappings":"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"}
|
package/dist/src/bin.js
CHANGED
|
@@ -1,37 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
3
|
-
import { FileSystem } from "@effect/platform";
|
|
4
|
-
import { BunContext, BunRuntime } from "@effect/platform-bun";
|
|
2
|
+
import { BunRuntime, BunServices } from "@effect/platform-bun";
|
|
5
3
|
import { NodeHttpClient } from "@effect/platform-node";
|
|
6
|
-
import {
|
|
4
|
+
import { makeInstallCommand } from "@knpkv/agent-skills";
|
|
5
|
+
import { AwsClient, AwsClientConfig, CacheService, ConfigService } from "@knpkv/codecommit-core";
|
|
7
6
|
import { makeServer } from "@knpkv/codecommit-web";
|
|
8
7
|
import { Console, Effect, Layer, Stream } from "effect";
|
|
9
|
-
import
|
|
8
|
+
import * as FileSystem from "effect/FileSystem";
|
|
9
|
+
import * as Runtime from "effect/Runtime";
|
|
10
|
+
import * as Stdio from "effect/Stdio";
|
|
11
|
+
import { Argument as Args, Command, Flag as Options } from "effect/unstable/cli";
|
|
12
|
+
import * as ChildProcess from "effect/unstable/process/ChildProcess";
|
|
10
13
|
import pkg from "../package.json";
|
|
14
|
+
import { FILTER_PRESETS, matchesRepoAuthor } from "./filterPresets.js";
|
|
15
|
+
import { FilterService, FilterServiceLive } from "./FilterService.js";
|
|
11
16
|
// TUI Command
|
|
12
|
-
const
|
|
13
|
-
yield* Effect.promise(() => import("./main.js"));
|
|
14
|
-
|
|
17
|
+
const launchTui = Effect.gen(function* () {
|
|
18
|
+
const { default: program } = yield* Effect.promise(() => import("./main.js"));
|
|
19
|
+
yield* program;
|
|
20
|
+
});
|
|
21
|
+
const tui = Command.make("tui", {}, () => launchTui);
|
|
15
22
|
// Web Command
|
|
16
23
|
const web = Command.make("web", {
|
|
17
24
|
port: Options.integer("port").pipe(Options.withDefault(3000)),
|
|
18
|
-
hostname: Options.
|
|
25
|
+
hostname: Options.string("hostname").pipe(Options.withDefault("127.0.0.1"))
|
|
19
26
|
}, ({ hostname, port }) => Effect.gen(function* () {
|
|
20
27
|
yield* Effect.logInfo(`Starting web server at http://${hostname}:${port}`);
|
|
21
28
|
// Open browser
|
|
22
|
-
|
|
29
|
+
const url = `http://${hostname}:${port}`;
|
|
30
|
+
const exitCode = (command) => Effect.scoped(command.pipe(Effect.flatMap((handle) => handle.exitCode)));
|
|
31
|
+
yield* exitCode(ChildProcess.make("open", [url])).pipe(Effect.catchIf(() => true, () => exitCode(ChildProcess.make("xdg-open", [url]))), Effect.catchIf(() => true, () => exitCode(ChildProcess.make("rundll32.exe", ["url.dll,FileProtocolHandler", url]))), Effect.catchIf(() => true, () => Effect.void));
|
|
23
32
|
// Run server with configured port/hostname
|
|
24
33
|
return yield* Layer.launch(makeServer({ port, hostname }));
|
|
25
34
|
}));
|
|
26
35
|
// PR Create Command
|
|
27
36
|
const prCreate = Command.make("create", {
|
|
28
|
-
repo: Args.
|
|
29
|
-
title: Args.
|
|
30
|
-
source: Options.
|
|
31
|
-
destination: Options.
|
|
32
|
-
description: Options.
|
|
33
|
-
profile: Options.
|
|
34
|
-
region: Options.
|
|
37
|
+
repo: Args.string("repository").pipe(Args.withDescription("Repository name")),
|
|
38
|
+
title: Args.string("title").pipe(Args.withDescription("PR title")),
|
|
39
|
+
source: Options.string("source").pipe(Options.withAlias("s"), Options.withDescription("Source branch")),
|
|
40
|
+
destination: Options.string("destination").pipe(Options.withAlias("d"), Options.withDescription("Destination branch"), Options.withDefault("main")),
|
|
41
|
+
description: Options.string("description").pipe(Options.withDescription("PR description"), Options.optional),
|
|
42
|
+
profile: Options.string("profile").pipe(Options.withAlias("p"), Options.withDescription("AWS profile"), Options.withDefault("default")),
|
|
43
|
+
region: Options.string("region").pipe(Options.withAlias("r"), Options.withDescription("AWS region"), Options.withDefault("us-east-1"))
|
|
35
44
|
}, ({ description, destination, profile, region, repo, source, title }) => Effect.gen(function* () {
|
|
36
45
|
const aws = yield* AwsClient.AwsClient;
|
|
37
46
|
yield* Console.log(`Creating PR: ${source} -> ${destination} in ${repo}`);
|
|
@@ -46,28 +55,83 @@ const prCreate = Command.make("create", {
|
|
|
46
55
|
const link = `https://${region}.console.aws.amazon.com/codesuite/codecommit/repositories/${repo}/pull-requests/${prId}?region=${region}`;
|
|
47
56
|
yield* Console.log(`Created PR: ${prId}`);
|
|
48
57
|
yield* Console.log(link);
|
|
49
|
-
}).pipe(Effect.provide(Layer.merge(AwsClient.AwsClientLive, NodeHttpClient.
|
|
58
|
+
}).pipe(Effect.provide(Layer.merge(AwsClient.AwsClientLive, NodeHttpClient.layerFetch)))).pipe(Command.withDescription("Create a pull request"));
|
|
59
|
+
// Filter presets (FILTER_PRESETS, matchesPreset, matchesRepoAuthor) live in
|
|
60
|
+
// ./filterPresets.ts — a side-effect-free module so they can be unit-tested
|
|
61
|
+
// without importing this CLI entrypoint (which boots the TUI/Bun runtime).
|
|
50
62
|
// PR List Command
|
|
51
63
|
const prList = Command.make("list", {
|
|
52
|
-
profile: Options.
|
|
53
|
-
region: Options.
|
|
54
|
-
status: Options.choice("status", ["OPEN", "CLOSED"]).pipe(Options.withAlias("s"), Options.withDescription("Filter by PR status"), Options.withDefault("OPEN")),
|
|
55
|
-
all: Options.boolean("all").pipe(Options.withAlias("a"), Options.withDescription("Show all PRs (both OPEN and CLOSED)"), Options.withDefault(false)),
|
|
56
|
-
repo: Options.
|
|
57
|
-
author: Options.
|
|
64
|
+
profile: Options.string("profile").pipe(Options.withAlias("p"), Options.withDescription("AWS profile (ignored when --filter is set — presets fan out across all enabled accounts)"), Options.withDefault("default")),
|
|
65
|
+
region: Options.string("region").pipe(Options.withAlias("r"), Options.withDescription("AWS region (ignored when --filter is set — presets fan out across all enabled accounts)"), Options.withDefault("us-east-1")),
|
|
66
|
+
status: Options.choice("status", ["OPEN", "CLOSED"]).pipe(Options.withAlias("s"), Options.withDescription("Filter by PR status (ignored when --filter is set — presets are OPEN-only)"), Options.withDefault("OPEN")),
|
|
67
|
+
all: Options.boolean("all").pipe(Options.withAlias("a"), Options.withDescription("Show all PRs (both OPEN and CLOSED; ignored when --filter is set — presets are OPEN-only)"), Options.withDefault(false)),
|
|
68
|
+
repo: Options.string("repo").pipe(Options.withDescription("Filter by repository name"), Options.optional),
|
|
69
|
+
author: Options.string("author").pipe(Options.withDescription("Filter by author"), Options.optional),
|
|
70
|
+
filter: Options.choice("filter", FILTER_PRESETS).pipe(Options.withDescription("Named preset (fans out across all enabled accounts, OPEN PRs only — ignores --status/--all): " +
|
|
71
|
+
"mine | needs-my-review | stale | conflicting"), Options.optional),
|
|
58
72
|
json: Options.boolean("json").pipe(Options.withDescription("Output as JSON"), Options.withDefault(false))
|
|
59
|
-
}, ({ all, author, json, profile, region, repo, status }) => Effect.gen(function* () {
|
|
73
|
+
}, ({ all, author, filter, json, profile, region, repo, status }) => Effect.gen(function* () {
|
|
60
74
|
const aws = yield* AwsClient.AwsClient;
|
|
75
|
+
// ── Filter-preset path: fan out across all enabled accounts ──────────────
|
|
76
|
+
if (filter._tag === "Some") {
|
|
77
|
+
const preset = filter.value;
|
|
78
|
+
const fs = yield* FilterService;
|
|
79
|
+
const targets = yield* fs.resolveTargets;
|
|
80
|
+
if (targets.length === 0) {
|
|
81
|
+
yield* Console.log("No enabled accounts in ~/.codecommit/config.json. Enable some with `codecommit tui`.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Progress/status text goes to stderr so `--json` emits only the JSON document on stdout.
|
|
85
|
+
if (!json)
|
|
86
|
+
yield* Console.error(`Scanning ${targets.length} account(s) with filter '${preset}'...`);
|
|
87
|
+
const { failures, prs, unresolvedProfiles } = yield* fs.collect(preset, targets, { repo, author });
|
|
88
|
+
const unresolvedCallerProfiles = unresolvedProfiles;
|
|
89
|
+
// Warn (on stderr, so `--json` stdout stays clean) when caller identity
|
|
90
|
+
// couldn't be resolved for an identity-comparing preset — those accounts'
|
|
91
|
+
// results may be incomplete because no PR can match an unknown "me".
|
|
92
|
+
const reportWarnings = Effect.gen(function* () {
|
|
93
|
+
for (const p of unresolvedCallerProfiles) {
|
|
94
|
+
yield* Console.error(`⚠ could not resolve caller identity for profile ${p}; '${preset}' results for it may be incomplete`);
|
|
95
|
+
}
|
|
96
|
+
if (failures.length > 0) {
|
|
97
|
+
yield* Console.error(`\n⚠ ${failures.length} account(s) failed:`);
|
|
98
|
+
for (const f of failures)
|
|
99
|
+
yield* Console.error(` ${f}`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
if (prs.length === 0) {
|
|
103
|
+
if (json)
|
|
104
|
+
yield* Console.log("[]");
|
|
105
|
+
else
|
|
106
|
+
yield* Console.error(`No PRs match filter '${preset}'.`);
|
|
107
|
+
yield* reportWarnings;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (json) {
|
|
111
|
+
yield* Console.log(JSON.stringify(prs, null, 2));
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
yield* Console.log(`\nFound ${prs.length} PR(s) matching filter '${preset}':\n`);
|
|
115
|
+
for (const pr of prs) {
|
|
116
|
+
const flags = [
|
|
117
|
+
pr.isApproved ? "approved" : "",
|
|
118
|
+
pr.isMergeable ? "mergeable" : "conflicts"
|
|
119
|
+
].filter(Boolean).join(" ");
|
|
120
|
+
yield* Console.log(`${pr.id} ${pr.repositoryName} [${pr.account.profile}/${pr.account.region}]`);
|
|
121
|
+
yield* Console.log(` ${pr.title}`);
|
|
122
|
+
yield* Console.log(` ${pr.sourceBranch} -> ${pr.destinationBranch}`);
|
|
123
|
+
yield* Console.log(` by ${pr.author} ${flags}`);
|
|
124
|
+
yield* Console.log("");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
yield* reportWarnings;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// ── Single-account path (original behaviour) ─────────────────────────────
|
|
61
131
|
const account = { profile: profile, region: region };
|
|
62
132
|
const statusLabel = all ? "all" : status.toLowerCase();
|
|
63
133
|
yield* Console.log(`Fetching ${statusLabel} PRs...`);
|
|
64
|
-
const filterPRs = (prStream) => prStream.pipe(Stream.filter((pr) =>
|
|
65
|
-
if (repo._tag === "Some" && pr.repositoryName !== repo.value)
|
|
66
|
-
return false;
|
|
67
|
-
if (author._tag === "Some" && pr.author !== author.value)
|
|
68
|
-
return false;
|
|
69
|
-
return true;
|
|
70
|
-
}), Stream.runCollect, Effect.map((chunk) => Array.from(chunk)));
|
|
134
|
+
const filterPRs = (prStream) => prStream.pipe(Stream.filter((pr) => matchesRepoAuthor(pr, repo, author)), Stream.runCollect, Effect.map((chunk) => Array.from(chunk)));
|
|
71
135
|
let prs;
|
|
72
136
|
if (all) {
|
|
73
137
|
const [openPrs, closedPrs] = yield* Effect.all([
|
|
@@ -101,7 +165,10 @@ const prList = Command.make("list", {
|
|
|
101
165
|
yield* Console.log("");
|
|
102
166
|
}
|
|
103
167
|
}
|
|
104
|
-
}).pipe(Effect.provide(
|
|
168
|
+
}).pipe(Effect.provide(
|
|
169
|
+
// FilterService draws AwsClient/ConfigService from the base layers, which
|
|
170
|
+
// are also merged into the output so the single-account path keeps them.
|
|
171
|
+
FilterServiceLive.pipe(Layer.provideMerge(Layer.mergeAll(AwsClient.AwsClientLive, NodeHttpClient.layerFetch, ConfigService.ConfigServiceLive.pipe(Layer.provide(CacheService.EventsHub.Default)))))))).pipe(Command.withDescription("List pull requests (use --filter for cross-account presets)"));
|
|
105
172
|
// Helper to render comment threads as markdown
|
|
106
173
|
const renderThread = (thread, indent = 0) => {
|
|
107
174
|
const prefix = " ".repeat(indent);
|
|
@@ -120,11 +187,11 @@ const renderLocation = (loc) => {
|
|
|
120
187
|
};
|
|
121
188
|
// PR Export Command
|
|
122
189
|
const prExport = Command.make("export", {
|
|
123
|
-
prId: Args.
|
|
124
|
-
repo: Args.
|
|
190
|
+
prId: Args.string("pr-id").pipe(Args.withDescription("Pull request ID")),
|
|
191
|
+
repo: Args.string("repository").pipe(Args.withDescription("Repository name")),
|
|
125
192
|
output: Options.file("output").pipe(Options.withAlias("o"), Options.withDescription("Output file path"), Options.optional),
|
|
126
|
-
profile: Options.
|
|
127
|
-
region: Options.
|
|
193
|
+
profile: Options.string("profile").pipe(Options.withAlias("p"), Options.withDescription("AWS profile"), Options.withDefault("default")),
|
|
194
|
+
region: Options.string("region").pipe(Options.withAlias("r"), Options.withDescription("AWS region"), Options.withDefault("us-east-1"))
|
|
128
195
|
}, ({ output, prId, profile, region, repo }) => Effect.gen(function* () {
|
|
129
196
|
const aws = yield* AwsClient.AwsClient;
|
|
130
197
|
const fs = yield* FileSystem.FileSystem;
|
|
@@ -166,14 +233,14 @@ const prExport = Command.make("export", {
|
|
|
166
233
|
yield* Console.log("");
|
|
167
234
|
yield* Console.log(markdown);
|
|
168
235
|
}
|
|
169
|
-
}).pipe(Effect.provide(Layer.merge(AwsClient.AwsClientLive, NodeHttpClient.
|
|
236
|
+
}).pipe(Effect.provide(Layer.merge(AwsClient.AwsClientLive, NodeHttpClient.layerFetch)))).pipe(Command.withDescription("Export PR comments as markdown"));
|
|
170
237
|
// PR Update Command
|
|
171
238
|
const prUpdate = Command.make("update", {
|
|
172
|
-
prId: Args.
|
|
173
|
-
title: Options.
|
|
174
|
-
description: Options.
|
|
175
|
-
profile: Options.
|
|
176
|
-
region: Options.
|
|
239
|
+
prId: Args.string("pr-id").pipe(Args.withDescription("Pull request ID")),
|
|
240
|
+
title: Options.string("title").pipe(Options.withAlias("t"), Options.withDescription("New PR title"), Options.optional),
|
|
241
|
+
description: Options.string("description").pipe(Options.withAlias("d"), Options.withDescription("New PR description"), Options.optional),
|
|
242
|
+
profile: Options.string("profile").pipe(Options.withAlias("p"), Options.withDescription("AWS profile"), Options.withDefault("default")),
|
|
243
|
+
region: Options.string("region").pipe(Options.withAlias("r"), Options.withDescription("AWS region"), Options.withDefault("us-east-1"))
|
|
177
244
|
}, ({ description, prId, profile, region, title }) => Effect.gen(function* () {
|
|
178
245
|
const aws = yield* AwsClient.AwsClient;
|
|
179
246
|
const account = { profile: profile, region: region };
|
|
@@ -190,15 +257,35 @@ const prUpdate = Command.make("update", {
|
|
|
190
257
|
yield* aws.updatePullRequestDescription({ account, pullRequestId: prId, description: description.value });
|
|
191
258
|
}
|
|
192
259
|
yield* Console.log(`Updated PR ${prId}`);
|
|
193
|
-
}).pipe(Effect.provide(Layer.merge(AwsClient.AwsClientLive, NodeHttpClient.
|
|
260
|
+
}).pipe(Effect.provide(Layer.merge(AwsClient.AwsClientLive, NodeHttpClient.layerFetch)))).pipe(Command.withDescription("Update PR title or description"));
|
|
194
261
|
// PR Command (parent)
|
|
195
262
|
const pr = Command.make("pr", {}, () => Console.log("Usage: codecommit pr <command>")).pipe(Command.withSubcommands([prList, prCreate, prExport, prUpdate]), Command.withDescription("Pull request commands"));
|
|
263
|
+
const skillsInstall = makeInstallCommand({
|
|
264
|
+
description: "Install the CodeCommit agent skill",
|
|
265
|
+
name: "install",
|
|
266
|
+
skills: ["codecommit"]
|
|
267
|
+
});
|
|
268
|
+
const skills = Command.make("skills", {}, () => Console.log("Usage: codecommit skills install")).pipe(Command.withSubcommands([skillsInstall]), Command.withDescription("Agent skill commands"));
|
|
196
269
|
const command = Command.make("codecommit", {}, () =>
|
|
197
270
|
// Default to TUI if no subcommand
|
|
198
|
-
|
|
199
|
-
const cli = Command.
|
|
200
|
-
name: pkg.name,
|
|
271
|
+
launchTui).pipe(Command.withSubcommands([tui, web, pr, skills]));
|
|
272
|
+
const cli = Command.runWith(command, {
|
|
201
273
|
version: pkg.version
|
|
202
274
|
});
|
|
203
|
-
|
|
275
|
+
const AppRuntimeLayer = Layer.mergeAll(NodeHttpClient.layerFetch, AwsClientConfig.Default);
|
|
276
|
+
const needsAppRuntime = (args) => args[0] !== "skills";
|
|
277
|
+
const program = Effect.gen(function* () {
|
|
278
|
+
const stdio = yield* Stdio.Stdio;
|
|
279
|
+
const args = yield* stdio.args;
|
|
280
|
+
const runCli = needsAppRuntime(args) ? cli(args).pipe(Effect.provide(AppRuntimeLayer)) : cli(args);
|
|
281
|
+
return yield* runCli;
|
|
282
|
+
});
|
|
283
|
+
// The TUI keeps long-lived resources open through its atom runtime (SQLite
|
|
284
|
+
// repos, the HTTP client, the EventsHub PubSub). When the user quits in-app the
|
|
285
|
+
// main fiber exits cleanly (code 0) and — because OpenTUI holds stdin in raw
|
|
286
|
+
// mode, so Ctrl-C is delivered as a keypress, not a SIGINT — runMain's default
|
|
287
|
+
// teardown never reaches `process.exit`. The process would then hang on those
|
|
288
|
+
// open handles after the UI has already torn down. Always terminate explicitly.
|
|
289
|
+
const forceExitTeardown = (exit) => Runtime.defaultTeardown(exit, (code) => process.exit(code));
|
|
290
|
+
BunRuntime.runMain(Effect.provide(program, BunServices.layer), { teardown: forceExitTeardown });
|
|
204
291
|
//# sourceMappingURL=bin.js.map
|