jira-subtask-cli 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sayali
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,152 @@
1
+ # Jira Subtask CLI
2
+
3
+ A lightweight **Node.js CLI utility** to automatically create standard **DEV / QA / UX Jira sub-tasks** for a parent Jira issue — without requiring Jira Automation or admin access.
4
+
5
+ This tool is designed to save time for **developers and testers** by eliminating repetitive manual sub-task creation.
6
+
7
+ ---
8
+
9
+ ## ✨ Features
10
+
11
+ - Create multiple Jira sub-tasks using **one command**
12
+ - Works without **Jira Automation** or Jira admin permissions
13
+ - Globally installable via `npm`
14
+ - User-editable configuration and task templates
15
+ - Cross-platform support (Windows / macOS / Linux)
16
+ - Uses Jira **Cloud REST APIs**
17
+
18
+ ---
19
+
20
+ ## 📋 Default Sub-tasks Created
21
+
22
+ By default, the utility creates the following sub-tasks:
23
+
24
+ - DEV – Story Analysis
25
+ - DEV – Code Changes
26
+ - DEV – Unit Testing
27
+ - DEV – Dev Testing
28
+ - QA – Functional Testing
29
+ - DEV – Buddy Testing *(unassigned)*
30
+ - UX Review *(unassigned)*
31
+
32
+ > Sub-tasks can be freely customized by editing `tasks.json`.
33
+
34
+ ---
35
+
36
+ ## 🧰 Prerequisites
37
+
38
+ - **Node.js 18+**
39
+ - **npm**
40
+ - Jira **Cloud** account (`*.atlassian.net`)
41
+ - Jira **API Token**
42
+
43
+ Check versions:
44
+ ```bash
45
+ node -v
46
+ npm -v
47
+
48
+ 🚀 Installation (Global)
49
+
50
+ Install directly fusing below command:
51
+ npm install -g https://github.com/sayaligp/Jira_Subtask_Utility.git
52
+
53
+
54
+ Verify installation:
55
+ jira-subtask
56
+
57
+
58
+ Expected output:
59
+ ❌ Usage: jira-subtask <ISSUE-KEY> [--dev-only | --qa-only | --skip-ux]
60
+
61
+ ⚙️ First-time Setup
62
+ On first execution, the utility automatically creates user-specific files:
63
+
64
+ jira-subtask ABC-1
65
+
66
+ This creates:
67
+
68
+ ~/.jira-subtask/
69
+ ├── config.json
70
+ └── tasks.json
71
+
72
+ 🔐 Configure Jira Credentials
73
+
74
+ Open the config file:
75
+
76
+ jira-subtask config
77
+
78
+ Update it with your Jira details:
79
+
80
+ {
81
+ "baseUrl": "https://yourcompany.atlassian.net",
82
+ "email": "your.email@company.com",
83
+ "apiToken": "YOUR_JIRA_API_TOKEN",
84
+ "projectId": "your project id"
85
+ }
86
+
87
+ Configuration notes
88
+
89
+ email → Jira login email
90
+ apiToken → Jira API token (NOT your password)
91
+ projectId → Jira project ID (not project key)
92
+
93
+ Create an API token here:
94
+ https://id.atlassian.com/manage-profile/security/api-tokens
95
+
96
+ 📝 Customize Sub-tasks (Optional)
97
+ Open task template file:
98
+ jira-subtask tasks
99
+
100
+ Each task entry looks like:
101
+
102
+ {
103
+ "type": "DEV",
104
+ "summary": "[DEV] Code Changes",
105
+ "description": "Implement required code changes",
106
+ "assignee": "SELF"
107
+ }
108
+
109
+ Assignee behavior
110
+ "SELF" → assigned to the current user
111
+ "NONE" → created unassigned
112
+
113
+ ▶️ Usage
114
+ Create all sub-tasks (DEV + QA + UX)
115
+ jira-subtask ABC-123
116
+
117
+ Create DEV + UX sub-tasks only
118
+ jira-subtask ABC-123 --dev-only
119
+
120
+ Create QA sub-tasks only
121
+ jira-subtask ABC-123 --qa-only
122
+
123
+ Create DEV sub-tasks only (without UX)
124
+ jira-subtask ABC-123 --skip-ux
125
+
126
+
127
+ ⚠️ Only one flag at a time is supported.
128
+
129
+ 🧪 Sample Output
130
+ Fetching Jira account...
131
+ 🚀 Creating 6 sub-tasks for ABC-123
132
+ ✅ ABC-201 → [DEV] Code Changes
133
+ ✅ ABC-202 → [QA] Functional Testing
134
+ 🎉 Done!
135
+
136
+
137
+ 🧯 Troubleshooting
138
+ ❌ 401 Unauthorized
139
+ Ensure API token is correct
140
+ Email must match Jira login email
141
+ Verify credentials using:
142
+ curl -u email:token https://yourcompany.atlassian.net/rest/api/3/myself
143
+
144
+ 🔐 Security
145
+ No credentials are committed to the repository
146
+ Each user uses their own Jira API token
147
+ Tokens are stored locally on the user’s machine
148
+
149
+ 📌 Notes
150
+ Works only with Jira Cloud
151
+ No Jira admin permissions required
152
+ Intended for internal team productivity
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { exec } from "child_process";
4
+ import { ensureUserFiles, loadConfig, loadTasks, CONFIG_PATH, TASKS_PATH } from "../src/config.js";
5
+ import { getMyAccountId, createSubtask } from "../src/jira.js";
6
+
7
+ // Ensure user files exist
8
+ ensureUserFiles();
9
+
10
+ const args = process.argv.slice(2);
11
+
12
+ /**
13
+ * ------------------------------------
14
+ * HELPER COMMANDS (NEW)
15
+ * ------------------------------------
16
+ */
17
+ if (args[0] === "config") {
18
+ const editor = process.env.EDITOR || "notepad";
19
+ exec(`"${editor}" "${CONFIG_PATH}"`);
20
+ process.exit(0);
21
+ }
22
+
23
+ if (args[0] === "tasks") {
24
+ const editor = process.env.EDITOR || "notepad";
25
+ exec(`"${editor}" "${TASKS_PATH}"`);
26
+ process.exit(0);
27
+ }
28
+
29
+
30
+ const parentKey = args[0];
31
+
32
+ const devOnly = args.includes("--dev-only");
33
+ const qaOnly = args.includes("--qa-only");
34
+ const skipUx = args.includes("--skip-ux");
35
+
36
+ // Validate input
37
+ if (!parentKey) {
38
+ console.error(
39
+ "Usage: jira-subtask <ISSUE-KEY> [--dev-only | --qa-only | --skip-ux]"
40
+ );
41
+ process.exit(1);
42
+ }
43
+
44
+ // Only one flag allowed
45
+ const flagCount = [devOnly, qaOnly, skipUx].filter(Boolean).length;
46
+ if (flagCount > 1) {
47
+ console.error(
48
+ "Only one flag is allowed: --dev-only OR --qa-only OR --skip-ux"
49
+ );
50
+ process.exit(1);
51
+ }
52
+
53
+ const config = loadConfig();
54
+ const tasks = loadTasks();
55
+
56
+ // --------------------
57
+ // FINAL FILTER LOGIC
58
+ // --------------------
59
+ let filteredTasks;
60
+
61
+ if (devOnly) {
62
+ // DEV + UX
63
+ filteredTasks = tasks.filter(
64
+ t => t.type === "DEV" || t.type === "UX"
65
+ );
66
+ } else if (qaOnly) {
67
+ // QA only
68
+ filteredTasks = tasks.filter(t => t.type === "QA");
69
+ } else if (skipUx) {
70
+ // DEV only
71
+ filteredTasks = tasks.filter(t => t.type === "DEV");
72
+ } else {
73
+ // DEV + QA + UX
74
+ filteredTasks = tasks;
75
+ }
76
+
77
+ if (filteredTasks.length === 0) {
78
+ console.log("No tasks to create.");
79
+ process.exit(0);
80
+ }
81
+
82
+ // --------------------
83
+ // Execute
84
+ // --------------------
85
+ (async () => {
86
+ try {
87
+ console.log("Fetching Jira account...");
88
+ const myAccountId = await getMyAccountId();
89
+
90
+ console.log(
91
+ `Creating ${filteredTasks.length} sub-tasks for ${parentKey}`
92
+ );
93
+
94
+ for (const task of filteredTasks) {
95
+ let assigneeId = null;
96
+ if(task.assignee === "SELF"){
97
+ assigneeId = myAccountId;
98
+ }
99
+
100
+
101
+ const key = await createSubtask(parentKey, task, assigneeId);
102
+ console.log(`${key} → ${task.summary}`);
103
+ }
104
+
105
+ console.log("Done!");
106
+ } catch (err) {
107
+ console.error(
108
+ "Error:",
109
+ err.response?.data?.errorMessages || err.message
110
+ );
111
+ process.exit(1);
112
+ }
113
+ })();
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "jira-subtask-cli",
3
+ "version": "1.0.1",
4
+ "description": "CLI to auto-create Jira sub-tasks",
5
+ "bin": {
6
+ "jira-subtask": "./bin/jira-subtask.js"
7
+ },
8
+ "type": "module",
9
+ "dependencies": {
10
+ "axios": "^1.6.0"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "src",
15
+ "templates"
16
+ ],
17
+ "keywords": [
18
+ "jira",
19
+ "cli",
20
+ "automation"
21
+ ],
22
+ "author": "Sayali Pokharkar",
23
+ "license": "MIT",
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/sayaligp/Jira_Subtask_Utility.git"
30
+ }
31
+ }
package/src/config.js ADDED
@@ -0,0 +1,39 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+
8
+ const BASE_DIR = path.join(os.homedir(), ".jira-subtask");
9
+ const CONFIG_PATH = path.join(BASE_DIR, "config.json");
10
+ const TASKS_PATH = path.join(BASE_DIR, "tasks.json");
11
+
12
+ const DEFAULT_CONFIG = path.join(__dirname, "../templates/config.default.json");
13
+ const DEFAULT_TASKS = path.join(__dirname, "../templates/tasks.default.json");
14
+
15
+ export function ensureUserFiles() {
16
+ if (!fs.existsSync(BASE_DIR)) {
17
+ fs.mkdirSync(BASE_DIR);
18
+ }
19
+
20
+ if (!fs.existsSync(CONFIG_PATH)) {
21
+ fs.copyFileSync(DEFAULT_CONFIG, CONFIG_PATH);
22
+ console.log("📄 config.json created at ~/.jira-subtask (please update it)");
23
+ }
24
+
25
+ if (!fs.existsSync(TASKS_PATH)) {
26
+ fs.copyFileSync(DEFAULT_TASKS, TASKS_PATH);
27
+ console.log("📄 tasks.json created at ~/.jira-subtask (editable)");
28
+ }
29
+ }
30
+
31
+ export function loadConfig() {
32
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
33
+ }
34
+
35
+ export function loadTasks() {
36
+ return JSON.parse(fs.readFileSync(TASKS_PATH, "utf-8"));
37
+ }
38
+
39
+ export { CONFIG_PATH, TASKS_PATH };
package/src/jira.js ADDED
@@ -0,0 +1,57 @@
1
+ import axios from "axios";
2
+ import { loadConfig } from "./config.js";
3
+
4
+ export async function getMyAccountId() {
5
+ const config = loadConfig();
6
+
7
+ const res = await axios.get(
8
+ `${config.baseUrl}/rest/api/3/myself`,
9
+ {
10
+ auth: {
11
+ username: config.email,
12
+ password: config.apiToken
13
+ }
14
+ }
15
+ );
16
+
17
+ return res.data.accountId;
18
+ }
19
+
20
+ export async function createSubtask(parentKey, task, assigneeId) {
21
+ const config = loadConfig();
22
+
23
+
24
+ const fields= {
25
+ project: { id: config.projectId },
26
+ parent: { key: parentKey },
27
+ issuetype: { name: "Sub-task" },
28
+ summary: task.summary,
29
+ description: {
30
+ type: "doc",
31
+ version: 1,
32
+ content: [
33
+ {
34
+ type: "paragraph",
35
+ content: [{ type: "text", text: task.description }]
36
+ }
37
+ ]
38
+ },
39
+ }
40
+
41
+ if(assigneeId){
42
+ fields.assignee= { id: assigneeId }
43
+ }
44
+ const payload = {fields};
45
+ const res = await axios.post(
46
+ `${config.baseUrl}/rest/api/3/issue`,
47
+ payload,
48
+ {
49
+ auth: {
50
+ username: config.email,
51
+ password: config.apiToken
52
+ }
53
+ }
54
+ );
55
+
56
+ return res.data.key;
57
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "baseUrl": "https://yourcompany.atlassian.net",
3
+ "email": "your_email",
4
+ "apiToken": "your_api_token",
5
+ "projectId": "your_proj_id"
6
+ }
7
+
@@ -0,0 +1,45 @@
1
+ [
2
+ {
3
+ "type": "DEV",
4
+ "summary": "[DEV] Story Analysis",
5
+ "description": "Analyze requirements and acceptance criteria",
6
+ "assignee": "SELF"
7
+ },
8
+ {
9
+ "type": "DEV",
10
+ "summary": "[DEV] Code Changes",
11
+ "description": "Implement and commit required code changes",
12
+ "assignee": "SELF"
13
+ },
14
+ {
15
+ "type": "DEV",
16
+ "summary": "[DEV] Unit Testing",
17
+ "description": "Write and execute unit tests",
18
+ "assignee": "SELF"
19
+ },
20
+ {
21
+ "type": "DEV",
22
+ "summary": "[DEV] Dev Testing",
23
+ "description": "Perform functional testing",
24
+ "assignee": "SELF"
25
+ },
26
+ {
27
+ "type": "DEV",
28
+ "summary": "[DEV] Buddy Testing",
29
+ "description": "Buddy testing of implemented changes",
30
+ "assignee": "NONE"
31
+ },
32
+ {
33
+ "type": "QA",
34
+ "summary": "[QA] Test cases preparation",
35
+ "description": "Test cases preparation",
36
+ "assignee": "QA"
37
+ },
38
+ {
39
+ "type": "UX",
40
+ "summary": "UX Review",
41
+ "description": "Final UX review and validation",
42
+ "assignee": "None"
43
+ }
44
+ ]
45
+