@geneverse/workctl 0.2.5
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/LICENSE +21 -0
- package/README.md +340 -0
- package/dist/packages/capabilities/assistant/src/commands.d.ts +10 -0
- package/dist/packages/capabilities/assistant/src/commands.js +33 -0
- package/dist/packages/capabilities/assistant/src/commands.js.map +1 -0
- package/dist/packages/capabilities/assistant/src/index.d.ts +2 -0
- package/dist/packages/capabilities/assistant/src/index.js +3 -0
- package/dist/packages/capabilities/assistant/src/index.js.map +1 -0
- package/dist/packages/capabilities/assistant/src/status.d.ts +18 -0
- package/dist/packages/capabilities/assistant/src/status.js +48 -0
- package/dist/packages/capabilities/assistant/src/status.js.map +1 -0
- package/dist/packages/capabilities/jenkins/src/commands.d.ts +9 -0
- package/dist/packages/capabilities/jenkins/src/commands.js +43 -0
- package/dist/packages/capabilities/jenkins/src/commands.js.map +1 -0
- package/dist/packages/capabilities/jenkins/src/definition.d.ts +10 -0
- package/dist/packages/capabilities/jenkins/src/definition.js +59 -0
- package/dist/packages/capabilities/jenkins/src/definition.js.map +1 -0
- package/dist/packages/capabilities/jenkins/src/index.d.ts +2 -0
- package/dist/packages/capabilities/jenkins/src/index.js +3 -0
- package/dist/packages/capabilities/jenkins/src/index.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/filesystem/config.store.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/adapters/filesystem/config.store.js +43 -0
- package/dist/packages/capabilities/jira/src/adapters/filesystem/config.store.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/filesystem/local.paths.d.ts +16 -0
- package/dist/packages/capabilities/jira/src/adapters/filesystem/local.paths.js +46 -0
- package/dist/packages/capabilities/jira/src/adapters/filesystem/local.paths.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/filesystem/sync-lock.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/adapters/filesystem/sync-lock.js +120 -0
- package/dist/packages/capabilities/jira/src/adapters/filesystem/sync-lock.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/jira/jira.client.d.ts +31 -0
- package/dist/packages/capabilities/jira/src/adapters/jira/jira.client.js +344 -0
- package/dist/packages/capabilities/jira/src/adapters/jira/jira.client.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/catalog.d.ts +9 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/catalog.js +17 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/catalog.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/orchestrator.d.ts +14 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/orchestrator.js +116 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/orchestrator.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/scraper.d.ts +32 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/scraper.js +2 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/scraper.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/detect.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/detect.js +45 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/detect.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/extract.d.ts +3 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/extract.js +115 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/extract.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/index.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/index.js +27 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/index.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/navigation.d.ts +3 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/navigation.js +104 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/navigation.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/ready.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/ready.js +47 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/ready.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/shared.d.ts +4 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/shared.js +5 -0
- package/dist/packages/capabilities/jira/src/adapters/kawari/v1/shared.js.map +1 -0
- package/dist/packages/capabilities/jira/src/adapters/terminal/node-cli.io.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/adapters/terminal/node-cli.io.js +40 -0
- package/dist/packages/capabilities/jira/src/adapters/terminal/node-cli.io.js.map +1 -0
- package/dist/packages/capabilities/jira/src/batch-interaction.d.ts +53 -0
- package/dist/packages/capabilities/jira/src/batch-interaction.js +201 -0
- package/dist/packages/capabilities/jira/src/batch-interaction.js.map +1 -0
- package/dist/packages/capabilities/jira/src/capability.d.ts +16 -0
- package/dist/packages/capabilities/jira/src/capability.js +56 -0
- package/dist/packages/capabilities/jira/src/capability.js.map +1 -0
- package/dist/packages/capabilities/jira/src/cli/contracts.d.ts +42 -0
- package/dist/packages/capabilities/jira/src/cli/contracts.js +2 -0
- package/dist/packages/capabilities/jira/src/cli/contracts.js.map +1 -0
- package/dist/packages/capabilities/jira/src/cli/errors.d.ts +3 -0
- package/dist/packages/capabilities/jira/src/cli/errors.js +7 -0
- package/dist/packages/capabilities/jira/src/cli/errors.js.map +1 -0
- package/dist/packages/capabilities/jira/src/cli/program.d.ts +6 -0
- package/dist/packages/capabilities/jira/src/cli/program.js +48 -0
- package/dist/packages/capabilities/jira/src/cli/program.js.map +1 -0
- package/dist/packages/capabilities/jira/src/cli/run-cli.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/cli/run-cli.js +72 -0
- package/dist/packages/capabilities/jira/src/cli/run-cli.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.command.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.command.js +165 -0
- package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.command.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.service.d.ts +30 -0
- package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.service.js +174 -0
- package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.service.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/command-context.d.ts +10 -0
- package/dist/packages/capabilities/jira/src/commands/command-context.js +2 -0
- package/dist/packages/capabilities/jira/src/commands/command-context.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/config-show/config-show.command.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/commands/config-show/config-show.command.js +26 -0
- package/dist/packages/capabilities/jira/src/commands/config-show/config-show.command.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/configure/configure.command.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/commands/configure/configure.command.js +26 -0
- package/dist/packages/capabilities/jira/src/commands/configure/configure.command.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/configure/configure.service.d.ts +42 -0
- package/dist/packages/capabilities/jira/src/commands/configure/configure.service.js +205 -0
- package/dist/packages/capabilities/jira/src/commands/configure/configure.service.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/doctor/doctor.command.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/commands/doctor/doctor.command.js +17 -0
- package/dist/packages/capabilities/jira/src/commands/doctor/doctor.command.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/doctor/doctor.service.d.ts +18 -0
- package/dist/packages/capabilities/jira/src/commands/doctor/doctor.service.js +84 -0
- package/dist/packages/capabilities/jira/src/commands/doctor/doctor.service.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/rollback/rollback.command.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/commands/rollback/rollback.command.js +198 -0
- package/dist/packages/capabilities/jira/src/commands/rollback/rollback.command.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/rollback/rollback.service.d.ts +37 -0
- package/dist/packages/capabilities/jira/src/commands/rollback/rollback.service.js +112 -0
- package/dist/packages/capabilities/jira/src/commands/rollback/rollback.service.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/sync/audit-export.service.d.ts +18 -0
- package/dist/packages/capabilities/jira/src/commands/sync/audit-export.service.js +96 -0
- package/dist/packages/capabilities/jira/src/commands/sync/audit-export.service.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/sync/preview.service.d.ts +23 -0
- package/dist/packages/capabilities/jira/src/commands/sync/preview.service.js +201 -0
- package/dist/packages/capabilities/jira/src/commands/sync/preview.service.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/sync/sync.command.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/commands/sync/sync.command.js +537 -0
- package/dist/packages/capabilities/jira/src/commands/sync/sync.command.js.map +1 -0
- package/dist/packages/capabilities/jira/src/commands/sync/verification.service.d.ts +34 -0
- package/dist/packages/capabilities/jira/src/commands/sync/verification.service.js +164 -0
- package/dist/packages/capabilities/jira/src/commands/sync/verification.service.js.map +1 -0
- package/dist/packages/capabilities/jira/src/config.d.ts +37 -0
- package/dist/packages/capabilities/jira/src/config.js +217 -0
- package/dist/packages/capabilities/jira/src/config.js.map +1 -0
- package/dist/packages/capabilities/jira/src/dependencies.d.ts +2 -0
- package/dist/packages/capabilities/jira/src/dependencies.js +92 -0
- package/dist/packages/capabilities/jira/src/dependencies.js.map +1 -0
- package/dist/packages/capabilities/jira/src/domain/date-range.d.ts +10 -0
- package/dist/packages/capabilities/jira/src/domain/date-range.js +63 -0
- package/dist/packages/capabilities/jira/src/domain/date-range.js.map +1 -0
- package/dist/packages/capabilities/jira/src/domain/marker.d.ts +20 -0
- package/dist/packages/capabilities/jira/src/domain/marker.js +100 -0
- package/dist/packages/capabilities/jira/src/domain/marker.js.map +1 -0
- package/dist/packages/capabilities/jira/src/domain/timesheet-entry.d.ts +45 -0
- package/dist/packages/capabilities/jira/src/domain/timesheet-entry.js +117 -0
- package/dist/packages/capabilities/jira/src/domain/timesheet-entry.js.map +1 -0
- package/dist/packages/capabilities/jira/src/index.d.ts +5 -0
- package/dist/packages/capabilities/jira/src/index.js +4 -0
- package/dist/packages/capabilities/jira/src/index.js.map +1 -0
- package/dist/packages/capabilities/jira/src/ports/jira.d.ts +49 -0
- package/dist/packages/capabilities/jira/src/ports/jira.js +11 -0
- package/dist/packages/capabilities/jira/src/ports/jira.js.map +1 -0
- package/dist/packages/capabilities/jira/src/ports/sync-lock.d.ts +6 -0
- package/dist/packages/capabilities/jira/src/ports/sync-lock.js +7 -0
- package/dist/packages/capabilities/jira/src/ports/sync-lock.js.map +1 -0
- package/dist/packages/capabilities/jira/src/ports/timesheet.d.ts +11 -0
- package/dist/packages/capabilities/jira/src/ports/timesheet.js +7 -0
- package/dist/packages/capabilities/jira/src/ports/timesheet.js.map +1 -0
- package/dist/packages/capabilities/jira/src/presentation.d.ts +37 -0
- package/dist/packages/capabilities/jira/src/presentation.js +111 -0
- package/dist/packages/capabilities/jira/src/presentation.js.map +1 -0
- package/dist/packages/cli/src/bin.d.ts +2 -0
- package/dist/packages/cli/src/bin.js +4 -0
- package/dist/packages/cli/src/bin.js.map +1 -0
- package/dist/packages/cli/src/commands/browsers/browsers.command.d.ts +7 -0
- package/dist/packages/cli/src/commands/browsers/browsers.command.js +22 -0
- package/dist/packages/cli/src/commands/browsers/browsers.command.js.map +1 -0
- package/dist/packages/cli/src/commands/browsers/browsers.service.d.ts +16 -0
- package/dist/packages/cli/src/commands/browsers/browsers.service.js +37 -0
- package/dist/packages/cli/src/commands/browsers/browsers.service.js.map +1 -0
- package/dist/packages/cli/src/commands/setup/setup.command.d.ts +4 -0
- package/dist/packages/cli/src/commands/setup/setup.command.js +41 -0
- package/dist/packages/cli/src/commands/setup/setup.command.js.map +1 -0
- package/dist/packages/cli/src/commands/setup/setup.service.d.ts +23 -0
- package/dist/packages/cli/src/commands/setup/setup.service.js +32 -0
- package/dist/packages/cli/src/commands/setup/setup.service.js.map +1 -0
- package/dist/packages/cli/src/commands/update/update.command.d.ts +7 -0
- package/dist/packages/cli/src/commands/update/update.command.js +34 -0
- package/dist/packages/cli/src/commands/update/update.command.js.map +1 -0
- package/dist/packages/cli/src/commands/update/update.service.d.ts +35 -0
- package/dist/packages/cli/src/commands/update/update.service.js +134 -0
- package/dist/packages/cli/src/commands/update/update.service.js.map +1 -0
- package/dist/packages/cli/src/index.d.ts +1 -0
- package/dist/packages/cli/src/index.js +2 -0
- package/dist/packages/cli/src/index.js.map +1 -0
- package/dist/packages/cli/src/program.d.ts +21 -0
- package/dist/packages/cli/src/program.js +74 -0
- package/dist/packages/cli/src/program.js.map +1 -0
- package/dist/packages/cli/src/release-version.d.ts +1 -0
- package/dist/packages/cli/src/release-version.js +2 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Geneverse
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# workctl
|
|
2
|
+
|
|
3
|
+
Deterministic internal developer workflows in one CLI. The Time Sync capability
|
|
4
|
+
is available through `workctl jira`.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
workctl jira sync
|
|
8
|
+
workctl update
|
|
9
|
+
workctl setup
|
|
10
|
+
workctl jenkins job create --help
|
|
11
|
+
workctl assistant status
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The npm package contains the compiled CLI, this README, and the MIT license.
|
|
15
|
+
Project planning and architecture notes live in the source repository and are
|
|
16
|
+
not included in the published package.
|
|
17
|
+
|
|
18
|
+
## jira capability
|
|
19
|
+
|
|
20
|
+
Deterministic local CLI for synchronizing Kawari timesheets to Jira Cloud.
|
|
21
|
+
No double-entry — scrape your timesheet, review what will be created, and
|
|
22
|
+
approve once.
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
**Prerequisites:** Node.js ≥22, a Jira Cloud API token, and access to your
|
|
27
|
+
Kawari timesheet.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g @geneverse/workctl
|
|
31
|
+
workctl browsers install
|
|
32
|
+
workctl setup
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The onboarding hub always lets you select setup-capable capabilities and
|
|
36
|
+
requires at least one selection. jira is selected by default; compact
|
|
37
|
+
onboarding asks only for Jira email and API token, supplies the company
|
|
38
|
+
defaults, verifies the result, and writes a private user-global configuration
|
|
39
|
+
file. Re-running setup skips configuration that is already valid and verifies
|
|
40
|
+
it again.
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
Run `workctl jira configure` to review the current configuration and select
|
|
45
|
+
only the sections you want to edit: Jira credentials, service defaults, or
|
|
46
|
+
Link Status Audit. On macOS and Linux, configuration is stored at
|
|
47
|
+
`~/.config/workctl/config.json`; on Windows, it is stored under
|
|
48
|
+
`%APPDATA%\workctl\config.json`. POSIX directories and files use private
|
|
49
|
+
permissions. The Jira token is stored as plain text in that file, so use
|
|
50
|
+
workctl only on a trusted personal workstation.
|
|
51
|
+
|
|
52
|
+
| Config field | Environment override | Description |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `jiraBaseUrl` | `JIRA_BASE_URL` | Jira Cloud HTTPS origin |
|
|
55
|
+
| `jiraEmail` | `JIRA_EMAIL` | Your Jira account email |
|
|
56
|
+
| `jiraApiToken` | `JIRA_API_TOKEN` | Jira Cloud API token ([create one here](https://id.atlassian.com/manage-profile/security/api-tokens)) |
|
|
57
|
+
| `timesheetUrl` | `TIMESHEET_URL` | Full URL to the Kawari timesheet |
|
|
58
|
+
| `timezone` | `TZ` | Must be `Asia/Bangkok` |
|
|
59
|
+
| `linkAudit.projects` | — | Optional Jira project map with `doneStatuses` for `audit-links` |
|
|
60
|
+
|
|
61
|
+
Real process environment variables override the config file field by field.
|
|
62
|
+
workctl does not load `.env` files. `NO_COLOR=1` disables colored output.
|
|
63
|
+
|
|
64
|
+
Use `workctl jira config show` to inspect effective values and their source. The token
|
|
65
|
+
is always redacted. Use `workctl jira doctor` to check permissions, Jira credentials,
|
|
66
|
+
and the Playwright Chromium installation.
|
|
67
|
+
|
|
68
|
+
## Commands
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
workctl jira sync [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--export csv|json]
|
|
72
|
+
workctl jira preview [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--export csv|json]
|
|
73
|
+
workctl jira verify [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--export csv|json]
|
|
74
|
+
workctl jira rollback [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--issue KEY] [--dry-run]
|
|
75
|
+
workctl jira audit-links [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--export csv|json]
|
|
76
|
+
workctl jira configure
|
|
77
|
+
workctl jira config show
|
|
78
|
+
workctl jira doctor
|
|
79
|
+
workctl browsers install
|
|
80
|
+
workctl update
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`workctl setup` selects, compactly configures, and verifies capabilities.
|
|
84
|
+
`workctl jira configure` is the full editor for Jira-specific settings.
|
|
85
|
+
|
|
86
|
+
| Command | Writes to Jira? | Purpose |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `sync` | Yes (with approval) | Full pipeline: scrape, preview, approve, create, verify |
|
|
89
|
+
| `preview` | No | Read-only: scrape and show what would happen |
|
|
90
|
+
| `verify` | No | Compare eligible entries against existing tool-managed Jira worklogs |
|
|
91
|
+
| `rollback` | Yes (typed approval) | Delete matching tool-managed worklogs and restore their time to Remaining Estimate |
|
|
92
|
+
| `audit-links` | No | Audit linked issue statuses for Jira issues found in Kawari |
|
|
93
|
+
| `browsers install` | No | Install the Playwright Chromium browser required by Kawari scraping |
|
|
94
|
+
| `update` | No | Install the latest stable `@geneverse/workctl` release from npm after approval |
|
|
95
|
+
|
|
96
|
+
### Rolling back a sync
|
|
97
|
+
|
|
98
|
+
`workctl jira rollback` reads the selected Kawari entries and previews only Jira
|
|
99
|
+
worklogs carrying matching `jlog:v1` Source Identities. The worklog must belong
|
|
100
|
+
to the current Jira account. Type the displayed `delete N` phrase to proceed,
|
|
101
|
+
or use `--dry-run` for a read-only check.
|
|
102
|
+
|
|
103
|
+
Each deletion sends `adjustEstimate=auto`, so deleting a 2h worklog from an
|
|
104
|
+
issue with 3h remaining restores Remaining Estimate to 5h. The Jira capability does not set
|
|
105
|
+
Original Estimate or an absolute remaining value. Use repeatable
|
|
106
|
+
`--issue OLD-1` options if a source entry's Jira key changed before rollback.
|
|
107
|
+
Once deletion begins, the Jira capability writes a private JSON receipt under
|
|
108
|
+
the local workctl data directory.
|
|
109
|
+
|
|
110
|
+
**Date range:** interactive `workctl jira sync`, `workctl jira rollback`, and `workctl jira audit-links`
|
|
111
|
+
ask whether to use
|
|
112
|
+
the current calendar month in `Asia/Bangkok` (showing its inclusive dates) or
|
|
113
|
+
enter custom `From` and `To` dates. The current month is selected by default.
|
|
114
|
+
`preview`, `verify`, and non-interactive commands continue to use the current
|
|
115
|
+
month when dates are omitted. Explicit ranges bypass the prompt and must fall
|
|
116
|
+
within one calendar month.
|
|
117
|
+
|
|
118
|
+
### Auditing linked issue statuses
|
|
119
|
+
|
|
120
|
+
`workctl jira audit-links` reads Jira keys from Kawari for the selected date range. It
|
|
121
|
+
checks links only for source issues whose current status is configured as done
|
|
122
|
+
for that Jira project. Every unique linked pair is reported as `Mismatch` or
|
|
123
|
+
`Aligned`; completed source issues without links are reported as `No Links`.
|
|
124
|
+
Link type and direction are displayed exactly as Jira returns them.
|
|
125
|
+
|
|
126
|
+
Configure the Link Audit Scope with `workctl jira configure`. The wizard reads
|
|
127
|
+
accessible projects and their statuses from Jira, then lets you select the
|
|
128
|
+
Done Statuses for each project:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"version": 1,
|
|
133
|
+
"capabilities": {
|
|
134
|
+
"jira": {
|
|
135
|
+
"linkAudit": {
|
|
136
|
+
"projects": {
|
|
137
|
+
"ABC": {
|
|
138
|
+
"doneStatuses": ["DONE", "READY TO DEPLOY"]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Status comparison ignores surrounding whitespace and letter case. This scope
|
|
148
|
+
does not restrict worklog sync, preview, or verification. Source issues outside
|
|
149
|
+
the scope are skipped and summarized. Cross-project targets use their own
|
|
150
|
+
configured Done Statuses; targets outside the scope are reported as
|
|
151
|
+
`Not Configured`. Invalid Kawari entries and incomplete Jira responses still
|
|
152
|
+
block the audit. Mismatches do not produce a failing exit code, and the command
|
|
153
|
+
never changes Jira. Use `--export csv|json` to retain the complete report,
|
|
154
|
+
including skipped sources and unconfigured targets.
|
|
155
|
+
|
|
156
|
+
Interactive command output uses one guided terminal presentation for prompts,
|
|
157
|
+
progress, results, and errors. `--help`, `--version`, and non-interactive output
|
|
158
|
+
remain plain text for piping and automation.
|
|
159
|
+
|
|
160
|
+
**Audit export** (`--export`): writes a timestamped CSV or JSON file to the
|
|
161
|
+
local workctl data directory for your records.
|
|
162
|
+
|
|
163
|
+
### Updating workctl
|
|
164
|
+
|
|
165
|
+
Run `workctl update` to check the npm registry for `@geneverse/workctl`. If a
|
|
166
|
+
newer stable version exists, workctl shows the target package, requires you to
|
|
167
|
+
type `yes`, and runs `npm install --global @geneverse/workctl@<version>` without
|
|
168
|
+
`sudo`. This command does not require Jira or Kawari configuration.
|
|
169
|
+
|
|
170
|
+
You can also update directly with npm:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
npm update -g @geneverse/workctl
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## How It Works
|
|
177
|
+
|
|
178
|
+
### Pipeline
|
|
179
|
+
|
|
180
|
+
Each `sync` runs these steps:
|
|
181
|
+
|
|
182
|
+
1. **Resolve date range** — interactive `sync` offers the displayed current
|
|
183
|
+
month or a custom range before opening Kawari
|
|
184
|
+
2. **Scrape Kawari** — launches a browser; prompts for manual login if the
|
|
185
|
+
session expired
|
|
186
|
+
3. **Classify** — each hours cell becomes a Timesheet Entry and is classified as:
|
|
187
|
+
- **Eligible** — exactly one Jira issue key, ready to sync
|
|
188
|
+
- **Unmapped** — no Jira key (e.g. internal meetings), skipped with a warning
|
|
189
|
+
- **Invalid** — bad data, multiple keys, or ambiguous identity
|
|
190
|
+
4. **Read Jira and repair keys** — fetches existing worklogs to check for prior
|
|
191
|
+
syncs. If a Jira issue does not exist, an interactive run can replace that
|
|
192
|
+
key for the current batch and validate the replacement immediately.
|
|
193
|
+
5. **Preview** — shows what will be created, skipped, or flagged as a conflict
|
|
194
|
+
6. **Approve** — interactive confirmation before any writes
|
|
195
|
+
7. **Create worklogs** — each new worklog gets a `jlog:v1:` marker in its
|
|
196
|
+
comment
|
|
197
|
+
8. **Verify** — re-reads Jira to confirm every expected worklog exists
|
|
198
|
+
|
|
199
|
+
`audit-links` shares date selection, Kawari classification, and temporary
|
|
200
|
+
missing-key repair, but reads Jira link metadata through a separate read-only
|
|
201
|
+
pipeline and never creates or transitions issues.
|
|
202
|
+
|
|
203
|
+
### Safety Gates
|
|
204
|
+
|
|
205
|
+
- **Invalid entries** and **sync conflicts** (a previously-synced entry whose
|
|
206
|
+
values changed) block the entire batch. Nothing is written until all issues
|
|
207
|
+
are resolved.
|
|
208
|
+
- A Jira key correction is a **Batch Override**. It applies to every affected
|
|
209
|
+
entry in the current command only; it does not edit Kawari or persist a local
|
|
210
|
+
mapping. Non-interactive runs fail with repair guidance instead.
|
|
211
|
+
- The approval prompt shows exact counts and hours before you commit.
|
|
212
|
+
|
|
213
|
+
### Idempotency
|
|
214
|
+
|
|
215
|
+
Every worklog created by the Jira capability contains a legacy `jlog:v1:` marker — a base64url-encoded
|
|
216
|
+
snapshot of the source entry. On re-runs, entries with a matching marker are
|
|
217
|
+
skipped automatically. The marker survives local state loss and prevents
|
|
218
|
+
duplicates across machines.
|
|
219
|
+
|
|
220
|
+
## Example
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
$ workctl jira sync --from 2026-01-05 --to 2026-01-09
|
|
224
|
+
|
|
225
|
+
──────────────────────────────────────────────────
|
|
226
|
+
Sync Batch: 2026-01-05 → 2026-01-09
|
|
227
|
+
──────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
╔═══════════════════════════════════════════════════════╗
|
|
230
|
+
║ PREVIEW ║
|
|
231
|
+
╠═══════════════════════════════════════════════════════╣
|
|
232
|
+
║ Create 3 worklog(s) 12.0h ║
|
|
233
|
+
║ Skip 1 worklog(s) 2.0h ║
|
|
234
|
+
╠═══════════════════════════════════════════════════════╣
|
|
235
|
+
║ Total eligible 14.0h ║
|
|
236
|
+
╚═══════════════════════════════════════════════════════╝
|
|
237
|
+
|
|
238
|
+
Create 3 Jira worklog(s) totaling 12h? Yes
|
|
239
|
+
|
|
240
|
+
◇ Ownership rechecked
|
|
241
|
+
│ ╭───────────────────────────────────────────────────╮
|
|
242
|
+
│ │ PROGRESS DATE ISSUE TIME STATUS │
|
|
243
|
+
│ ├───────────────────────────────────────────────────┤
|
|
244
|
+
✓ │ 1/3 2026-01-07 PROJ-123 4h Created │
|
|
245
|
+
✓ │ 2/3 2026-01-08 PROJ-456 4h Created │
|
|
246
|
+
✓ │ 3/3 2026-01-09 PROJ-789 4h Created │
|
|
247
|
+
│ ╰───────────────────────────────────────────────────╯
|
|
248
|
+
● Created 3 (12h) · Failed 0 (0h)
|
|
249
|
+
|
|
250
|
+
Verified: 3 of 3 expected worklogs found on Jira.
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Development
|
|
254
|
+
|
|
255
|
+
**Prerequisites:** Node.js ≥22, npm, a Jira Cloud API token, and access to the
|
|
256
|
+
Kawari timesheet.
|
|
257
|
+
|
|
258
|
+
For a fresh clone, bootstrap the project once. This installs npm dependencies
|
|
259
|
+
and Playwright Chromium, then opens the onboarding hub:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
npm run bootstrap
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Run the CLI directly from TypeScript without building or linking it:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
npm run dev -- jira sync
|
|
269
|
+
npm run dev -- jira preview --from 2026-07-01 --to 2026-07-04
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The compiled workflow remains available when testing the packaged executable:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
npm ci
|
|
276
|
+
npm run build
|
|
277
|
+
npm link
|
|
278
|
+
workctl jira sync
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Development checks:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
npm test # run tests (vitest)
|
|
285
|
+
npm run typecheck # type-check only, no emit
|
|
286
|
+
npm run build # compile TypeScript → dist/
|
|
287
|
+
npm run test:package # pack and install the CLI in a temporary npm prefix
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Releasing
|
|
291
|
+
|
|
292
|
+
Start from a clean `main` branch, then run the development checks before
|
|
293
|
+
creating a patch release:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
npm test
|
|
297
|
+
npm run typecheck
|
|
298
|
+
npm run build
|
|
299
|
+
npm run test:package
|
|
300
|
+
npm version patch -m "chore(release): %s"
|
|
301
|
+
git push origin main --follow-tags
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`npm version` updates `package.json` and `package-lock.json`, creates the
|
|
305
|
+
release commit, and tags it as `v<version>`. The tag workflow publishes
|
|
306
|
+
`@geneverse/workctl` to the npm registry with public access.
|
|
307
|
+
|
|
308
|
+
### Project Structure
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
packages/
|
|
312
|
+
├── cli/ workctl composition root and binary
|
|
313
|
+
└── capabilities/
|
|
314
|
+
├── jira/ Kawari-to-Jira time synchronization
|
|
315
|
+
├── jenkins/ Git-backed Jenkins definitions
|
|
316
|
+
└── assistant/ vendor CLI installation status
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Each capability owns its commands, dependencies, implementation, tests, and
|
|
320
|
+
domain glossary. Capabilities are built into one release and cannot import one
|
|
321
|
+
another. The CLI imports only their public entrypoints.
|
|
322
|
+
|
|
323
|
+
### Local State
|
|
324
|
+
|
|
325
|
+
Runtime data is user-global. On macOS and Linux it lives under
|
|
326
|
+
`~/.local/share/workctl/jira/`; on Windows it lives under
|
|
327
|
+
`%LOCALAPPDATA%\workctl\jira\`:
|
|
328
|
+
|
|
329
|
+
- `browser-profile/` — persistent Playwright Chromium session
|
|
330
|
+
- `audit-exports/` — timestamped CSV/JSON exports
|
|
331
|
+
- `state/` — sync state
|
|
332
|
+
|
|
333
|
+
Run `workctl setup` after installation; the first sync will require Kawari
|
|
334
|
+
login.
|
|
335
|
+
|
|
336
|
+
Uninstall the CLI with npm:
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
npm uninstall -g @geneverse/workctl
|
|
340
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type AssistantStatus } from "./status.js";
|
|
3
|
+
export interface AssistantCommandIO {
|
|
4
|
+
stdout(message: string): void;
|
|
5
|
+
}
|
|
6
|
+
export interface AssistantDependencies {
|
|
7
|
+
statuses(): Promise<readonly AssistantStatus[]>;
|
|
8
|
+
}
|
|
9
|
+
export declare function createAssistantDependencies(): AssistantDependencies;
|
|
10
|
+
export declare function registerAssistantCommands(program: Command, io: AssistantCommandIO, dependencies?: AssistantDependencies): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { assistantStatuses } from "./status.js";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
export function createAssistantDependencies() {
|
|
6
|
+
return {
|
|
7
|
+
statuses: () => assistantStatuses(async (command) => {
|
|
8
|
+
try {
|
|
9
|
+
await execFileAsync("which", [command]);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function registerAssistantCommands(program, io, dependencies = createAssistantDependencies()) {
|
|
19
|
+
program.command("assistant").description("inspect supported assistant installations")
|
|
20
|
+
.command("status")
|
|
21
|
+
.action(async () => {
|
|
22
|
+
for (const status of await dependencies.statuses()) {
|
|
23
|
+
io.stdout(`${status.displayName} (${status.command}): ` +
|
|
24
|
+
`${status.installed ? "installed" : "not installed"}\n`);
|
|
25
|
+
if (status.installed) {
|
|
26
|
+
io.stdout(` Sign in directly with the vendor CLI: ${status.loginCommand}\n`);
|
|
27
|
+
}
|
|
28
|
+
io.stdout(` Vendor documentation: ${status.documentationUrl}\n`);
|
|
29
|
+
}
|
|
30
|
+
io.stdout("workctl does not run login commands or read, store, configure, or receipt provider credentials.\n");
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../../../packages/capabilities/assistant/src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,iBAAiB,EAAwB,MAAM,aAAa,CAAC;AAUtE,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,UAAU,2BAA2B;IACzC,OAAO;QACL,QAAQ,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YAClD,IAAI,CAAC;gBACH,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;gBACxC,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC,CAAC;KACH,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,OAAgB,EAChB,EAAsB,EACtB,eAAsC,2BAA2B,EAAE;IAEnE,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,2CAA2C,CAAC;SAClF,OAAO,CAAC,QAAQ,CAAC;SACjB,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,KAAK,MAAM,MAAM,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC;YACnD,EAAE,CAAC,MAAM,CACP,GAAG,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,OAAO,KAAK;gBAC7C,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,IAAI,CACxD,CAAC;YACF,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBACrB,EAAE,CAAC,MAAM,CAAC,2CAA2C,MAAM,CAAC,YAAY,IAAI,CAAC,CAAC;YAChF,CAAC;YACD,EAAE,CAAC,MAAM,CAAC,2BAA2B,MAAM,CAAC,gBAAgB,IAAI,CAAC,CAAC;QACpE,CAAC;QACD,EAAE,CAAC,MAAM,CACP,mGAAmG,CACpG,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/capabilities/assistant/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type AssistantName = "cursor" | "codex" | "claude";
|
|
2
|
+
export interface AssistantDefinition {
|
|
3
|
+
name: AssistantName;
|
|
4
|
+
displayName: string;
|
|
5
|
+
commands: readonly string[];
|
|
6
|
+
loginCommand: string;
|
|
7
|
+
documentationUrl: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AssistantStatus {
|
|
10
|
+
name: AssistantName;
|
|
11
|
+
displayName: string;
|
|
12
|
+
command: string;
|
|
13
|
+
installed: boolean;
|
|
14
|
+
loginCommand: string;
|
|
15
|
+
documentationUrl: string;
|
|
16
|
+
}
|
|
17
|
+
export declare const supportedAssistants: readonly AssistantDefinition[];
|
|
18
|
+
export declare function assistantStatuses(exists?: (command: string) => Promise<boolean>): Promise<readonly AssistantStatus[]>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
export const supportedAssistants = [
|
|
5
|
+
{
|
|
6
|
+
name: "cursor",
|
|
7
|
+
displayName: "Cursor Agent",
|
|
8
|
+
commands: ["cursor-agent"],
|
|
9
|
+
loginCommand: "cursor-agent login",
|
|
10
|
+
documentationUrl: "https://docs.cursor.com/en/cli/reference/authentication",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "codex",
|
|
14
|
+
displayName: "OpenAI Codex",
|
|
15
|
+
commands: ["codex"],
|
|
16
|
+
loginCommand: "codex login",
|
|
17
|
+
documentationUrl: "https://developers.openai.com/codex/auth/",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "claude",
|
|
21
|
+
displayName: "Claude Code",
|
|
22
|
+
commands: ["claude"],
|
|
23
|
+
loginCommand: "claude",
|
|
24
|
+
documentationUrl: "https://docs.anthropic.com/en/docs/claude-code/getting-started",
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
export async function assistantStatuses(exists = async (command) => {
|
|
28
|
+
try {
|
|
29
|
+
await execFileAsync("which", [command]);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}) {
|
|
36
|
+
return Promise.all(supportedAssistants.map(async (assistant) => {
|
|
37
|
+
const command = assistant.commands[0];
|
|
38
|
+
return {
|
|
39
|
+
name: assistant.name,
|
|
40
|
+
displayName: assistant.displayName,
|
|
41
|
+
command,
|
|
42
|
+
installed: await exists(command),
|
|
43
|
+
loginCommand: assistant.loginCommand,
|
|
44
|
+
documentationUrl: assistant.documentationUrl,
|
|
45
|
+
};
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../../../packages/capabilities/assistant/src/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAqB1C,MAAM,CAAC,MAAM,mBAAmB,GAAmC;IACjE;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,cAAc;QAC3B,QAAQ,EAAE,CAAC,cAAc,CAAC;QAC1B,YAAY,EAAE,oBAAoB;QAClC,gBAAgB,EAAE,yDAAyD;KAC5E;IACD;QACE,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,cAAc;QAC3B,QAAQ,EAAE,CAAC,OAAO,CAAC;QACnB,YAAY,EAAE,aAAa;QAC3B,gBAAgB,EAAE,2CAA2C;KAC9D;IACD;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,aAAa;QAC1B,QAAQ,EAAE,CAAC,QAAQ,CAAC;QACpB,YAAY,EAAE,QAAQ;QACtB,gBAAgB,EAAE,gEAAgE;KACnF;CACF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAgD,KAAK,EAAE,OAAO,EAAE,EAAE;IAChE,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE;QAC7D,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC;QACvC,OAAO;YACL,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,WAAW,EAAE,SAAS,CAAC,WAAW;YAClC,OAAO;YACP,SAAS,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC;YAChC,YAAY,EAAE,SAAS,CAAC,YAAY;YACpC,gBAAgB,EAAE,SAAS,CAAC,gBAAgB;SAC7C,CAAC;IACJ,CAAC,CAAC,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
export interface JenkinsCommandIO {
|
|
3
|
+
stdout(message: string): void;
|
|
4
|
+
}
|
|
5
|
+
export interface JenkinsDependencies {
|
|
6
|
+
writeFile(file: string, content: string): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare function createJenkinsDependencies(): JenkinsDependencies;
|
|
9
|
+
export declare function registerJenkinsCommands(program: Command, io: JenkinsCommandIO, dependencies?: JenkinsDependencies): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createJenkinsJobDefinition, jenkinsDefinitionPath, } from "./definition.js";
|
|
4
|
+
export function createJenkinsDependencies() {
|
|
5
|
+
return {
|
|
6
|
+
writeFile: async (file, content) => {
|
|
7
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
8
|
+
await writeFile(file, content, { flag: "wx", mode: 0o644 });
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function registerJenkinsCommands(program, io, dependencies = createJenkinsDependencies()) {
|
|
13
|
+
const job = program.command("jenkins").description("manage Git-backed Jenkins definitions")
|
|
14
|
+
.command("job").description("manage Jenkins jobs");
|
|
15
|
+
job.command("create")
|
|
16
|
+
.requiredOption("--name <name>")
|
|
17
|
+
.requiredOption("--repo <url>")
|
|
18
|
+
.option("--branch <branch>", "SCM branch", "main")
|
|
19
|
+
.requiredOption("--target-host <host>")
|
|
20
|
+
.requiredOption("--credential-id <id>")
|
|
21
|
+
.option("--output <directory>", "definition directory", "jenkins/jobs")
|
|
22
|
+
.option("--write", "write the reviewed definition; otherwise preview only")
|
|
23
|
+
.action(async (options) => {
|
|
24
|
+
const input = {
|
|
25
|
+
name: options.name,
|
|
26
|
+
repositoryUrl: options.repo,
|
|
27
|
+
branch: options.branch,
|
|
28
|
+
targetHost: options.targetHost,
|
|
29
|
+
credentialId: options.credentialId,
|
|
30
|
+
};
|
|
31
|
+
const definition = createJenkinsJobDefinition(input);
|
|
32
|
+
const destination = jenkinsDefinitionPath(options.output, options.name);
|
|
33
|
+
io.stdout(`${definition}\nDestination: ${destination}\n`);
|
|
34
|
+
if (options.write) {
|
|
35
|
+
await dependencies.writeFile(destination, definition);
|
|
36
|
+
io.stdout("Definition written. Review and commit it through the normal Git workflow.\n");
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
io.stdout("Preview only; pass --write to create the file.\n");
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../../../packages/capabilities/jenkins/src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,GAEtB,MAAM,iBAAiB,CAAC;AAUzB,MAAM,UAAU,yBAAyB;IACvC,OAAO;QACL,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;YACjC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACrD,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9D,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,OAAgB,EAChB,EAAoB,EACpB,eAAoC,yBAAyB,EAAE;IAE/D,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,uCAAuC,CAAC;SACxF,OAAO,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;IACrD,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC;SAClB,cAAc,CAAC,eAAe,CAAC;SAC/B,cAAc,CAAC,cAAc,CAAC;SAC9B,MAAM,CAAC,mBAAmB,EAAE,YAAY,EAAE,MAAM,CAAC;SACjD,cAAc,CAAC,sBAAsB,CAAC;SACtC,cAAc,CAAC,sBAAsB,CAAC;SACtC,MAAM,CAAC,sBAAsB,EAAE,sBAAsB,EAAE,cAAc,CAAC;SACtE,MAAM,CAAC,SAAS,EAAE,uDAAuD,CAAC;SAC1E,MAAM,CAAC,KAAK,EAAE,OAQd,EAAE,EAAE;QACH,MAAM,KAAK,GAAoB;YAC7B,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,aAAa,EAAE,OAAO,CAAC,IAAI;YAC3B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,YAAY,EAAE,OAAO,CAAC,YAAY;SACnC,CAAC;QACF,MAAM,UAAU,GAAG,0BAA0B,CAAC,KAAK,CAAC,CAAC;QACrD,MAAM,WAAW,GAAG,qBAAqB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACxE,EAAE,CAAC,MAAM,CAAC,GAAG,UAAU,kBAAkB,WAAW,IAAI,CAAC,CAAC;QAC1D,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,YAAY,CAAC,SAAS,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;YACtD,EAAE,CAAC,MAAM,CAAC,6EAA6E,CAAC,CAAC;QAC3F,CAAC;aAAM,CAAC;YACN,EAAE,CAAC,MAAM,CAAC,kDAAkD,CAAC,CAAC;QAChE,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface JenkinsJobInput {
|
|
2
|
+
name: string;
|
|
3
|
+
repositoryUrl: string;
|
|
4
|
+
branch: string;
|
|
5
|
+
targetHost: string;
|
|
6
|
+
credentialId: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function validateJenkinsJobInput(input: JenkinsJobInput): void;
|
|
9
|
+
export declare function createJenkinsJobDefinition(input: JenkinsJobInput): string;
|
|
10
|
+
export declare function jenkinsDefinitionPath(outputDirectory: string, name: string): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const SAFE_NAME = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
3
|
+
const SAFE_CREDENTIAL_ID = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
4
|
+
function xml(value) {
|
|
5
|
+
return value
|
|
6
|
+
.replaceAll("&", "&")
|
|
7
|
+
.replaceAll("<", "<")
|
|
8
|
+
.replaceAll(">", ">")
|
|
9
|
+
.replaceAll('"', """)
|
|
10
|
+
.replaceAll("'", "'");
|
|
11
|
+
}
|
|
12
|
+
export function validateJenkinsJobInput(input) {
|
|
13
|
+
if (!SAFE_NAME.test(input.name))
|
|
14
|
+
throw new Error("Job name contains unsupported characters.");
|
|
15
|
+
if (!/^https:\/\/[^/\s]+\/.+/.test(input.repositoryUrl) && !/^git@[^:\s]+:.+/.test(input.repositoryUrl)) {
|
|
16
|
+
throw new Error("Repository URL must use HTTPS or Git SSH syntax.");
|
|
17
|
+
}
|
|
18
|
+
if (!input.branch.trim())
|
|
19
|
+
throw new Error("Branch is required.");
|
|
20
|
+
if (!/^[a-z0-9][a-z0-9.-]*$/i.test(input.targetHost)) {
|
|
21
|
+
throw new Error("Target host must be a hostname or IPv4 address without credentials.");
|
|
22
|
+
}
|
|
23
|
+
if (!SAFE_CREDENTIAL_ID.test(input.credentialId)) {
|
|
24
|
+
throw new Error("Credential ID contains unsupported characters.");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function createJenkinsJobDefinition(input) {
|
|
28
|
+
validateJenkinsJobInput(input);
|
|
29
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
30
|
+
<flow-definition plugin="workflow-job">
|
|
31
|
+
<description>Managed by workctl. Do not edit in Jenkins.</description>
|
|
32
|
+
<keepDependencies>false</keepDependencies>
|
|
33
|
+
<properties/>
|
|
34
|
+
<definition class="org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition" plugin="workflow-cps">
|
|
35
|
+
<scm class="hudson.plugins.git.GitSCM" plugin="git">
|
|
36
|
+
<configVersion>2</configVersion>
|
|
37
|
+
<userRemoteConfigs>
|
|
38
|
+
<hudson.plugins.git.UserRemoteConfig>
|
|
39
|
+
<url>${xml(input.repositoryUrl)}</url>
|
|
40
|
+
<credentialsId>${xml(input.credentialId)}</credentialsId>
|
|
41
|
+
</hudson.plugins.git.UserRemoteConfig>
|
|
42
|
+
</userRemoteConfigs>
|
|
43
|
+
<branches><hudson.plugins.git.BranchSpec><name>*/${xml(input.branch)}</name></hudson.plugins.git.BranchSpec></branches>
|
|
44
|
+
</scm>
|
|
45
|
+
<scriptPath>Jenkinsfile</scriptPath>
|
|
46
|
+
<lightweight>true</lightweight>
|
|
47
|
+
</definition>
|
|
48
|
+
<disabled>false</disabled>
|
|
49
|
+
<actions/>
|
|
50
|
+
<workctlTargetHost>${xml(input.targetHost)}</workctlTargetHost>
|
|
51
|
+
</flow-definition>
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
export function jenkinsDefinitionPath(outputDirectory, name) {
|
|
55
|
+
if (!SAFE_NAME.test(name))
|
|
56
|
+
throw new Error("Job name contains unsupported characters.");
|
|
57
|
+
return path.join(outputDirectory, `${name}.xml`);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=definition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definition.js","sourceRoot":"","sources":["../../../../../packages/capabilities/jenkins/src/definition.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAU7B,MAAM,SAAS,GAAG,yBAAyB,CAAC;AAC5C,MAAM,kBAAkB,GAAG,yBAAyB,CAAC;AAErD,SAAS,GAAG,CAAC,KAAa;IACxB,OAAO,KAAK;SACT,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAsB;IAC5D,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC9F,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;QACxG,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACjE,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;IACzF,CAAC;IACD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,KAAsB;IAC/D,uBAAuB,CAAC,KAAK,CAAC,CAAC;IAC/B,OAAO;;;;;;;;;;iBAUQ,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC;2BACd,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC;;;yDAGO,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC;;;;;;;uBAOnD,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC;;CAE3C,CAAC;AACF,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,eAAuB,EAAE,IAAY;IACzE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IACxF,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/capabilities/jenkins/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
|