@jonbito/jira-markdown 0.1.2
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 +396 -0
- package/dist/cli.js +25482 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# jira-markdown
|
|
2
|
+
|
|
3
|
+
`jira-markdown` is a Bun-based CLI that syncs markdown files with Jira Cloud issues. You can start from local markdown and push it into Jira, start from Jira and pull issues onto disk, or run both directions with `sync`.
|
|
4
|
+
|
|
5
|
+
This project is Cloud-first. It works best with Jira Cloud authentication and field metadata. Bun `1.3.9` or newer is required at runtime, including when the CLI is installed from npm.
|
|
6
|
+
|
|
7
|
+
Examples below use the installed `jira-markdown` binary. If you are working from this repository, use the matching Bun scripts instead:
|
|
8
|
+
|
|
9
|
+
- `jira-markdown auth login` -> `bun run auth login`
|
|
10
|
+
- `jira-markdown push` -> `bun run push`
|
|
11
|
+
- `jira-markdown pull --project ENG` -> `bun run pull --project ENG`
|
|
12
|
+
- `jira-markdown sync --project ENG` -> `bun run sync --project ENG`
|
|
13
|
+
|
|
14
|
+
Replace `ENG` below with your Jira project key.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Install the published CLI globally:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install --global @jonbito/jira-markdown
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The npm package name is scoped, but the installed command remains `jira-markdown`.
|
|
25
|
+
|
|
26
|
+
For local development in this repository:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
bun install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
Before you authenticate, gather:
|
|
35
|
+
|
|
36
|
+
- your Jira Cloud site root, for example `https://your-domain.atlassian.net`
|
|
37
|
+
- your Atlassian account email if you use basic auth
|
|
38
|
+
- an Atlassian API token for basic auth, or a bearer token if your Jira setup uses bearer auth
|
|
39
|
+
|
|
40
|
+
Authenticate once:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
jira-markdown auth login
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Optional: create or edit a config file. `config init` prompts for the issue storage directory and defaults to `issues`.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
jira-markdown config init
|
|
50
|
+
jira-markdown config edit
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`push`, `pull`, and `sync` will auto-create a starter config if it is missing. Relative `dir` values are evaluated from the current working directory for sync runs, so the default `dir: "issues"` writes under `./issues` in the workspace where you run the CLI.
|
|
54
|
+
|
|
55
|
+
Choose one starting point:
|
|
56
|
+
|
|
57
|
+
### Start from Jira
|
|
58
|
+
|
|
59
|
+
Use this when Jira already has the issues and you want local markdown copies first.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
jira-markdown pull --project ENG --dry-run
|
|
63
|
+
jira-markdown pull --project ENG
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
When you do not already have local files under `<dir>/<PROJECT>/`, `pull` needs `--project`.
|
|
67
|
+
|
|
68
|
+
### Start from local markdown
|
|
69
|
+
|
|
70
|
+
Use this when you want to draft a new Jira issue locally first.
|
|
71
|
+
|
|
72
|
+
Create a markdown file under `<dir>/<PROJECT>/`, for example `issues/ENG/draft-task.md`:
|
|
73
|
+
|
|
74
|
+
```md
|
|
75
|
+
---
|
|
76
|
+
issueType: Task
|
|
77
|
+
summary: Tighten sync error handling
|
|
78
|
+
labels:
|
|
79
|
+
- docs
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
# Tighten sync error handling
|
|
83
|
+
|
|
84
|
+
- Improve API error logging
|
|
85
|
+
- Keep dry-run output readable
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Minimum frontmatter for a new local issue:
|
|
89
|
+
|
|
90
|
+
- `issueType` is required.
|
|
91
|
+
- The Jira project comes from the folder path `<dir>/<PROJECT>/...`, or from `project:` if the file is elsewhere.
|
|
92
|
+
- `parent` is required when `issueType` is a Jira sub-task type.
|
|
93
|
+
- `summary` is optional if the first `# Heading` or filename is good enough.
|
|
94
|
+
- Omit `issue:` for new issues. Set `issue:` only when the file already maps to an existing Jira issue.
|
|
95
|
+
|
|
96
|
+
Preview and then create the issue:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
jira-markdown push --dry-run
|
|
100
|
+
jira-markdown push
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`push` writes the created Jira key back into frontmatter by default and renames the file into the canonical path `<dir>/<PROJECT>/<KEY> - <Summary>.md`.
|
|
104
|
+
|
|
105
|
+
### Ongoing sync
|
|
106
|
+
|
|
107
|
+
After you have local files and Jira auth configured, use `sync` for the normal workflow:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
jira-markdown sync --project ENG --dry-run
|
|
111
|
+
jira-markdown sync --project ENG
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`sync --dry-run` previews both the outgoing Jira writes and the local file writes without changing Jira, local files, or sync history.
|
|
115
|
+
|
|
116
|
+
The first non-dry-run `push`, `pull`, or `sync` automatically discovers missing issue-type field maps and learns user labels, then writes them into `<dir>/.jira-markdown.field-map.json` and `<dir>/.jira-markdown.user-map.json`.
|
|
117
|
+
|
|
118
|
+
## Authentication
|
|
119
|
+
|
|
120
|
+
Run the interactive login flow once:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
jira-markdown auth login
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Useful auth commands:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
jira-markdown auth status
|
|
130
|
+
jira-markdown auth logout
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
You can also script login non-interactively:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
jira-markdown auth login \
|
|
137
|
+
--base-url https://your-domain.atlassian.net \
|
|
138
|
+
--auth-mode basic \
|
|
139
|
+
--email you@example.com \
|
|
140
|
+
--token your-api-token
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Authentication storage:
|
|
144
|
+
|
|
145
|
+
- On macOS, the CLI uses Keychain for the token by default and stores metadata in `~/Library/Application Support/jira-markdown/auth.json`.
|
|
146
|
+
- If keychain storage is unavailable, or you force it with `--storage file`, the token is stored in the same auth file with local file permissions.
|
|
147
|
+
- The base URL must be the Jira site root. Do not include a path.
|
|
148
|
+
|
|
149
|
+
## Config
|
|
150
|
+
|
|
151
|
+
Example `jira-markdown.config.json`:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"dir": "issues",
|
|
156
|
+
"sync": {
|
|
157
|
+
"createMissing": true,
|
|
158
|
+
"updateExisting": true
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
By default, the config file lives beside `auth.json` in the local user config directory. `jira-markdown.config.json` holds only hand-edited settings.
|
|
164
|
+
|
|
165
|
+
`dir` controls where project folders live. Generated field mappings live in `<dir>/.jira-markdown.field-map.json`, generated user labels live in `<dir>/.jira-markdown.user-map.json`, and sync metadata lives in `<dir>/.sync-history`.
|
|
166
|
+
|
|
167
|
+
Path resolution rules:
|
|
168
|
+
|
|
169
|
+
- Run the CLI from the workspace you want to sync. Relative `dir` values are evaluated from the current working directory during push, pull, and sync runs.
|
|
170
|
+
- The default `dir: "issues"` therefore targets `./issues` in that workspace.
|
|
171
|
+
|
|
172
|
+
Pull scope comes from `--project` and any local files already present under `<dir>/<PROJECT>/...`.
|
|
173
|
+
|
|
174
|
+
If `EDITOR` is set, you can open the config file directly with:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
jira-markdown config edit
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Example generated field-map dotfile:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"ENG": {
|
|
185
|
+
"Task": {
|
|
186
|
+
"sprint": {
|
|
187
|
+
"fieldId": "customfield_10020",
|
|
188
|
+
"resolver": "sprintByName",
|
|
189
|
+
"boardId": 12
|
|
190
|
+
},
|
|
191
|
+
"storyPoints": {
|
|
192
|
+
"fieldId": "customfield_10016",
|
|
193
|
+
"resolver": "number"
|
|
194
|
+
},
|
|
195
|
+
"audience": {
|
|
196
|
+
"fieldId": "customfield_10010",
|
|
197
|
+
"resolver": "optionArrayByName"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Example generated user-map dotfile:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"Alice Example": {
|
|
209
|
+
"accountId": "557058:abcd-1234",
|
|
210
|
+
"aliases": ["alice@example.com"]
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
If you want sprint-by-name mapping instead of sprint ids, inspect the board sprints and then edit `<dir>/.jira-markdown.field-map.json` to add a `boardId` on the generated sprint field entry.
|
|
216
|
+
|
|
217
|
+
You can inspect board sprints with:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
jira-markdown sprints --board 12 --state active,future
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Conflict resolution
|
|
224
|
+
|
|
225
|
+
`jira-markdown` does not auto-merge concurrent local and Jira edits. When both the local markdown issue and the Jira issue changed since the last successful sync baseline, the CLI requires a resolution choice:
|
|
226
|
+
|
|
227
|
+
- `--on-conflict prompt` asks which side to keep for each conflicted issue when the command is running in a TTY.
|
|
228
|
+
- `--on-conflict keep-local` keeps the local markdown version and immediately updates Jira to match it.
|
|
229
|
+
- `--on-conflict keep-jira` keeps the Jira version and immediately rewrites the local markdown file to match it.
|
|
230
|
+
- `--on-conflict fail` aborts on the first conflict.
|
|
231
|
+
|
|
232
|
+
If you do not pass `--on-conflict`, the CLI defaults to `prompt` in an interactive terminal and `fail` otherwise.
|
|
233
|
+
|
|
234
|
+
The choice applies to the whole issue state, including attachments. `sync --dry-run`, `push --dry-run`, and `pull --dry-run` follow the same conflict mode but only report the action they would take.
|
|
235
|
+
|
|
236
|
+
## Attachments
|
|
237
|
+
|
|
238
|
+
Issue attachments live beside the markdown under the project folder. With the default `dir: "issues"` that looks like:
|
|
239
|
+
|
|
240
|
+
```text
|
|
241
|
+
issues/ENG/ENG-123 - Improve sync behavior.md
|
|
242
|
+
issues/ENG/.attachments/ENG-123/diagram.png
|
|
243
|
+
issues/ENG/.attachments/ENG-123/spec.pdf
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
For new local issues that do not have a Jira key yet, stage attachments under:
|
|
247
|
+
|
|
248
|
+
```text
|
|
249
|
+
issues/ENG/.attachments/_drafts/<markdown-file-name>/
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
When `push` creates the Jira issue, the CLI moves that draft attachment folder into the stable issue-key directory and uploads the files.
|
|
253
|
+
|
|
254
|
+
Attachment behavior:
|
|
255
|
+
|
|
256
|
+
- `push` uploads new attachments and replaces attachments that were previously synced by this CLI when the local file content changes.
|
|
257
|
+
- `push` rewrites local attachment markdown links like `[spec.pdf](.attachments/ENG-123/spec.pdf)` and image references like `` into Jira attachment URLs in the issue description.
|
|
258
|
+
- `pull` downloads Jira attachments into the canonical issue attachment folder.
|
|
259
|
+
- `pull` rewrites Jira description attachment references into local markdown links like `[spec.pdf](.attachments/ENG-123/spec.pdf)` or image embeds like ``.
|
|
260
|
+
- The tool does not prune deleted attachments on either side.
|
|
261
|
+
- If Jira already has an attachment with the same filename that is not tracked by `jira-markdown`, `push` stops and tells you to `pull` first or rename the local file.
|
|
262
|
+
|
|
263
|
+
## Minimum Jira permissions
|
|
264
|
+
|
|
265
|
+
The list below uses the Jira Cloud REST permission names Atlassian uses in its API docs. For the current `jira-markdown` feature set, the minimum access for `push`, `pull`, and `sync` is:
|
|
266
|
+
|
|
267
|
+
- Jira product access on the site.
|
|
268
|
+
- `Browse Projects` for every project the CLI will read or write.
|
|
269
|
+
- `Create Issues` if you want local markdown without an `issue:` key to create new Jira issues.
|
|
270
|
+
- `Edit Issues` if you want to update existing Jira issues from markdown.
|
|
271
|
+
|
|
272
|
+
Add these only when you use the matching feature:
|
|
273
|
+
|
|
274
|
+
- `Create attachments` to upload attachments during `push`.
|
|
275
|
+
- `Delete own attachments` or `Delete all attachments` if `push` needs to replace an attachment that was previously synced by this CLI.
|
|
276
|
+
- `Assign Issues` if you set `assignee:` in frontmatter. The target user must also be assignable in that project.
|
|
277
|
+
- `Transition Issues` if you set `status:` in frontmatter and want `push` to move the issue through the workflow.
|
|
278
|
+
- `Schedule Issues` if you write the sprint field. If you use `sprintByName` mappings or `jira-markdown sprints --board <id>`, the account also needs access to that Jira board.
|
|
279
|
+
- `Browse users and groups` if you want site-wide user discovery by display name or email for assignees or `@mentions`. Issue-scoped assignable-user checks can work with `Assign Issues` alone.
|
|
280
|
+
|
|
281
|
+
Notes:
|
|
282
|
+
|
|
283
|
+
- `pull` and `sync` can only read issues and attachments that the account can already see, including any Jira issue-level security restrictions.
|
|
284
|
+
- `push` and `pull` call Jira field metadata endpoints to validate fields and generate mappings, but they do not require Jira admin or project admin permissions.
|
|
285
|
+
|
|
286
|
+
## Markdown shape
|
|
287
|
+
|
|
288
|
+
Pulled issues land in canonical paths like `<dir>/ENG/ENG-123 - Tighten sync error handling.md`.
|
|
289
|
+
|
|
290
|
+
Example issue file:
|
|
291
|
+
|
|
292
|
+
```md
|
|
293
|
+
---
|
|
294
|
+
issue: ENG-123
|
|
295
|
+
issueType: Task
|
|
296
|
+
summary: Tighten sync error handling
|
|
297
|
+
status: In Progress
|
|
298
|
+
labels:
|
|
299
|
+
- automation
|
|
300
|
+
- docs
|
|
301
|
+
sprint: Sprint 42
|
|
302
|
+
storyPoints: 3
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
# Tighten sync error handling
|
|
306
|
+
|
|
307
|
+
- Improve API error logging
|
|
308
|
+
- Keep dry-run output readable
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Reserved top-level frontmatter keys:
|
|
312
|
+
|
|
313
|
+
- `issue` or `issueKey`
|
|
314
|
+
- `project`
|
|
315
|
+
- `issueType` or `issuetype`
|
|
316
|
+
- `summary`
|
|
317
|
+
- `description`
|
|
318
|
+
- `labels`
|
|
319
|
+
- `assignee`
|
|
320
|
+
- `parent`
|
|
321
|
+
- `status`
|
|
322
|
+
- `fields`
|
|
323
|
+
|
|
324
|
+
Everything else is treated as candidate Jira field input and resolved through `<dir>/.jira-markdown.field-map.json`, direct field ids, or exact Jira field-name matches.
|
|
325
|
+
|
|
326
|
+
Common Jira field shapes supported at the top level:
|
|
327
|
+
|
|
328
|
+
- `status: In Progress`
|
|
329
|
+
- `priority: High`
|
|
330
|
+
- `components: ["API", "UI"]`
|
|
331
|
+
- `versions: ["2026.03"]`
|
|
332
|
+
- `fixVersions: ["2026.03"]`
|
|
333
|
+
- mapped single-option custom fields as a string, for example `customerTier: Gold`
|
|
334
|
+
- mapped multi-select custom fields as a string array, for example `audience: ["Customer", "Internal"]`
|
|
335
|
+
|
|
336
|
+
For metadata-backed option, component, version, and priority fields, `push` resolves the human-friendly names through Jira create/edit metadata and sends the corresponding Jira ids. `pull` writes those values back into top-level frontmatter using the mapped key for custom fields and the canonical field name for common system fields.
|
|
337
|
+
|
|
338
|
+
When a file uses `sprint: Sprint 42`, the `sprintByName` resolver looks up the sprint on the configured board and sends the numeric sprint id Jira expects.
|
|
339
|
+
|
|
340
|
+
`parent` follows Jira Cloud's modern hierarchy model:
|
|
341
|
+
|
|
342
|
+
- For sub-task issue types, `parent` is required on create and must resolve to an issue in the same project.
|
|
343
|
+
- For non-subtask issue types, `parent` is passed through when Jira exposes the modern parent field for that create or edit operation.
|
|
344
|
+
- `jira-markdown` does not support legacy `Epic Link` or `Parent Link` fields.
|
|
345
|
+
|
|
346
|
+
`fields` is the escape hatch for raw Jira payloads:
|
|
347
|
+
|
|
348
|
+
```yaml
|
|
349
|
+
---
|
|
350
|
+
summary: Create from raw fields
|
|
351
|
+
fields:
|
|
352
|
+
customfield_12345:
|
|
353
|
+
value: Important
|
|
354
|
+
---
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Commands
|
|
358
|
+
|
|
359
|
+
- `jira-markdown auth login`
|
|
360
|
+
- `jira-markdown auth status`
|
|
361
|
+
- `jira-markdown auth logout`
|
|
362
|
+
- `jira-markdown config init`
|
|
363
|
+
- `jira-markdown config edit`
|
|
364
|
+
- `jira-markdown inspect adf GRIP-2`
|
|
365
|
+
- `jira-markdown push`
|
|
366
|
+
- `jira-markdown push --dry-run`
|
|
367
|
+
- `jira-markdown pull --project ENG`
|
|
368
|
+
- `jira-markdown pull --project ENG --dry-run`
|
|
369
|
+
- `jira-markdown sync --project ENG`
|
|
370
|
+
- `jira-markdown sync --project ENG --dry-run`
|
|
371
|
+
- `jira-markdown sprints --board 12 --state active,future`
|
|
372
|
+
|
|
373
|
+
## Release
|
|
374
|
+
|
|
375
|
+
GitHub Actions publishes to npm when a tag matching `v<package.json version>` is pushed. Before using the publish workflow, add an `NPM_TOKEN` repository secret with publish access to the target package.
|
|
376
|
+
|
|
377
|
+
Example release flow:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
npm version patch
|
|
381
|
+
git push origin --follow-tags
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Notes
|
|
385
|
+
|
|
386
|
+
- The CLI stores per-file, per-issue, and per-attachment sync metadata in `<dir>/.sync-history` so unchanged markdown, Jira issues, and attachments can be skipped on later runs.
|
|
387
|
+
- `pull` writes Jira issues into `<dir>/<PROJECT>/<KEY> - <Summary>.md`.
|
|
388
|
+
- `push` renames local files into that same canonical shape after create or update.
|
|
389
|
+
- `sync` is a convenience wrapper that runs `push` and then `pull`.
|
|
390
|
+
- The markdown-to-ADF adapter handles headings, paragraphs, lists, task lists, tables, blockquotes, links, mentions, bold, italic, and inline code. If your content needs panels or richer Atlassian-specific nodes, extend [src/adf.ts](/Users/arch/src/jira-markdown/src/adf.ts).
|
|
391
|
+
- Mention syntax:
|
|
392
|
+
`@[alice@example.com]` does a Jira user lookup during `push`.
|
|
393
|
+
`@[Alice Example]` uses `<dir>/.jira-markdown.user-map.json` first and then Jira search during `push`, and is what `pull` writes back when a display name is available.
|
|
394
|
+
`@[Alice Example](557058:abcd-1234)` remains supported as the explicit stable form.
|
|
395
|
+
- `<dir>/.jira-markdown.user-map.json` is generated automatically during successful `push`, `pull`, and `sync` runs so pulled `assignee` values and mentions can use real names instead of raw account IDs.
|
|
396
|
+
- Newly created issues are written back into frontmatter by default through the `issue` field.
|