imdone-cli 0.1.5 → 0.1.7
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/.imdone/actions/board.js +18 -0
- package/.imdone/actions/card.js +6 -0
- package/{src/usecases/__tests__/test-project/.imdone → .imdone}/config.yml +20 -40
- package/.imdone/properties/board.js +116 -0
- package/.imdone/properties/card.js +12 -0
- package/.imdone/style.css +0 -0
- package/.imdone/tags.yml +8 -0
- package/.imdone/templates/user_story.md +21 -0
- package/.imdoneignore +3 -0
- package/README.md +25 -22
- package/dist/.env +1 -0
- package/dist/index.cjs +871 -787
- package/dist/index.min.cjs +151 -151
- package/dist/index.min.cjs.map +4 -4
- package/package.json +5 -5
- package/dist/index.cjs.map +0 -7
- package/src/adapters/__tests__/create-jwt.sh +0 -11
- package/src/adapters/__tests__/jwt-private.pem +0 -28
- package/src/adapters/__tests__/jwt-public.pem +0 -9
- package/src/adapters/__tests__/license.spec.js +0 -27
- package/src/adapters/env.js +0 -21
- package/src/adapters/git.js +0 -113
- package/src/adapters/imdone-config-template.js +0 -79
- package/src/adapters/imdone.js +0 -76
- package/src/adapters/license.js +0 -83
- package/src/bin.js +0 -173
- package/src/index.js +0 -3
- package/src/ui/prompt.js +0 -192
- package/src/usecases/__tests__/headless-pull-from-jira.spec.js +0 -60
- package/src/usecases/headless-pull-from-jira.js +0 -58
- package/src/usecases/headless-push-to-jira.js +0 -42
- package/src/usecases/init.js +0 -27
@@ -1,11 +0,0 @@
|
|
1
|
-
#!/bin/bash
|
2
|
-
|
3
|
-
set -e
|
4
|
-
|
5
|
-
# Generate a 2048-bit RSA private key (unencrypted)
|
6
|
-
openssl genpkey -algorithm RSA -out ./jwt-private.pem -pkeyopt rsa_keygen_bits:2048
|
7
|
-
|
8
|
-
# Extract the public key from the private key
|
9
|
-
openssl rsa -pubout -in ./jwt-private.pem -out jwt-public.pem
|
10
|
-
|
11
|
-
echo "✅ RSA 2048-bit key pair generated in ./keys/"
|
@@ -1,28 +0,0 @@
|
|
1
|
-
-----BEGIN PRIVATE KEY-----
|
2
|
-
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC/zPuZfQQyke5+
|
3
|
-
KUs2AeKt4BMbO0TZgSt+1pLYzXXhtKj5qVn0GUY1QS03F3/dksUS+oND9jmyxBoK
|
4
|
-
0IdLUKa1pX66rM8nTb3fMYk+wVAn+oBK1hKSv/pCWkrw/oN19aAzl9yf5+BWBym1
|
5
|
-
JkoXYlU3607PPWzMZ26lZ/xGUpMsKHQmdX2Enk7ZfoHdT7xhnUkP+dQ5Qdf2rUyf
|
6
|
-
Y58Bb7yJtWlo00O0bs4+oBH4d5N+Ft/Qx3LxHKlDL374G+28fImT2tfAiYYs9Ash
|
7
|
-
diiecv1RTjNxtVFoo34Vu8x1eQMsoWLjtQKdMB5UxmmobwiYfULwyweCXKJuvo9L
|
8
|
-
MQK2zQRlAgMBAAECggEADxL+dDf/4DSZdKdsrpQz21wIzGNaXCjbgQa+uFTfjqtq
|
9
|
-
+2I8vGNPIgjbE6Egbsrl+8GeFvWPX9YrQM3WUExIqe6VmPjTeCCTS2sigQDi+p1k
|
10
|
-
v1z3TTrS4aukh6/NJ7R0EJH/KD8qYcCC3eaiPJfBIFm5Ui7e8eqwJsZXLLS5Nede
|
11
|
-
CjkdCTbHA3hSJIplEFSiTDrV7TTg+s9PwZV0GboOmUS9zmFBWRr1VTsPit5YbRty
|
12
|
-
VmvI89XDQROMimMmrL9v9dK0TeqzFABDi+Sad5lR5udxov9BfZWUEaBcwmpxh7kt
|
13
|
-
TaOPTXAw/wv9X05rBr0dmkoUEhyoeIk6aELyqSD6sQKBgQDzxsItdxYAoPufort8
|
14
|
-
D0qEdYlahISVnH23Yv7Q/+Hr5uZs7WG6MPYiPe5nSgYQcnA/IgXB2yebbM1vm6dD
|
15
|
-
nHIXYVmw/pB/1rIlqbxlvOf7eUiQ39FVp4h2LWSeY0aI0dg+XL6cpULf5n0+bJCD
|
16
|
-
/i01d6NcEPOxYPfbJsEFgckM0QKBgQDJawmE/lhxsgCcR+WxxDeGciZQIOydjr4/
|
17
|
-
caN062qijxXB6lZAi9fg0EcReuVmsXLw25TOHi9ABON6FftdbagTk9lgrcTWo21F
|
18
|
-
XxD1AeJoQUwlort3AnJ8SS/1b1C2KX8S3hI4vX2RXuqUGgubBdW4Y5aC0KOZAqhz
|
19
|
-
Yb0xGqRTVQKBgDxLwyeftu984gAALkNnPNU6hTjAYlLnHClJ8SEcyXKh8AitRmjZ
|
20
|
-
R2f8zYT6yDk1NRJIhggG/urwpHeglmSgw4+I4rhmnrMgFXw/WXwIl5CZ1RsQYSTA
|
21
|
-
hX5FiAetInsg/E2gfv0b20iqJ/xSugQL0H7TErLo9n2/ME8ibMfB7EqhAoGAbsUI
|
22
|
-
5TUj2tMz9r6rmcn5Z10bqPGSb0vzYNzUMhbN/DyIkK6ZZMIDpUWl7/0QcBuixMdd
|
23
|
-
3MVI4wJNP/Ua2lTWHH4xDDREjm4uR/pyTuwMYZ2XjRMj2d1krOlrEKV5U9UaZ3vt
|
24
|
-
tXqwtePnSi/Qth7NXKyYN7UaY6nMjjfwXjLysEkCgYB4b+1gVzuPWBZsJbo/yX2Q
|
25
|
-
HWjdwL9EgbaFHvqI6DVoMBMux+KB5KKVTn+yRS2NAR7fxdYsVeEz+z8ftfVbXRpt
|
26
|
-
7dpPJxBe+MpoOojGkpg8UC1/SIwG2Qtg4QDgdCK2QS8JbeRpziK0GyLIfiSG5i4R
|
27
|
-
CX69OWZrIxmbTy9Vun0nCg==
|
28
|
-
-----END PRIVATE KEY-----
|
@@ -1,9 +0,0 @@
|
|
1
|
-
-----BEGIN PUBLIC KEY-----
|
2
|
-
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv8z7mX0EMpHufilLNgHi
|
3
|
-
reATGztE2YErftaS2M114bSo+alZ9BlGNUEtNxd/3ZLFEvqDQ/Y5ssQaCtCHS1Cm
|
4
|
-
taV+uqzPJ0293zGJPsFQJ/qAStYSkr/6QlpK8P6DdfWgM5fcn+fgVgcptSZKF2JV
|
5
|
-
N+tOzz1szGdupWf8RlKTLCh0JnV9hJ5O2X6B3U+8YZ1JD/nUOUHX9q1Mn2OfAW+8
|
6
|
-
ibVpaNNDtG7OPqAR+HeTfhbf0Mdy8RypQy9++BvtvHyJk9rXwImGLPQLIXYonnL9
|
7
|
-
UU4zcbVRaKN+FbvMdXkDLKFi47UCnTAeVMZpqG8ImH1C8MsHglyibr6PSzECts0E
|
8
|
-
ZQIDAQAB
|
9
|
-
-----END PUBLIC KEY-----
|
@@ -1,27 +0,0 @@
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
2
|
-
import { generateLicense, verifyLicense } from "../license";
|
3
|
-
import { fileURLToPath } from 'url';
|
4
|
-
import path from 'path';
|
5
|
-
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
7
|
-
const __dirname = path.dirname(__filename);
|
8
|
-
|
9
|
-
describe("License Generation and Verification", () => {
|
10
|
-
const privateKeyPath = path.join(__dirname, 'jwt-private.pem');
|
11
|
-
const publicKeyPath = path.join(__dirname, 'jwt-public.pem');
|
12
|
-
const payload = {
|
13
|
-
name: "Test User",
|
14
|
-
email: "test@test.com",
|
15
|
-
org: "Test Organization",
|
16
|
-
plan: "premium",
|
17
|
-
expiresIn: "30d",
|
18
|
-
};
|
19
|
-
|
20
|
-
it("should generate a valid license", async () => {
|
21
|
-
const license = await generateLicense(payload, privateKeyPath);
|
22
|
-
expect(license).toBeDefined();
|
23
|
-
expect(typeof license).toBe("string");
|
24
|
-
const decoded = await verifyLicense(license, publicKeyPath);
|
25
|
-
expect(decoded.valid).toBe(true);
|
26
|
-
});
|
27
|
-
})
|
package/src/adapters/env.js
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
import { envExists as envExistsAdapter, appendToEnv, initEnv } from "../../../src/adapters/env";
|
2
|
-
import { logger } from "imdone-core/lib/adapters/logger.js";
|
3
|
-
export async function envExists(projectPath) {
|
4
|
-
return envExistsAdapter(projectPath)
|
5
|
-
}
|
6
|
-
|
7
|
-
export async function storeJiraCredentials(projectPath, jiraUsername, jiraApiToken) {
|
8
|
-
await appendToEnv(projectPath, 'JIRA_USERNAME', jiraUsername)
|
9
|
-
await appendToEnv(projectPath, 'JIRA_TOKEN', jiraApiToken)
|
10
|
-
logger.info('Stored Jira credentials in .env file')
|
11
|
-
}
|
12
|
-
|
13
|
-
export async function storeLicense(projectPath, license) {
|
14
|
-
await appendToEnv(projectPath, 'IMDONE_LICENSE', license)
|
15
|
-
logger.info('Stored license in .env file')
|
16
|
-
}
|
17
|
-
|
18
|
-
export async function init(projectPath) {
|
19
|
-
await initEnv(projectPath)
|
20
|
-
logger.info('Initialized .env file')
|
21
|
-
}
|
package/src/adapters/git.js
DELETED
@@ -1,113 +0,0 @@
|
|
1
|
-
import { exec } from 'child_process'
|
2
|
-
import { simpleGit } from 'simple-git'
|
3
|
-
import { constants } from 'imdone-core/lib/constants.js'
|
4
|
-
import { TAGS_FILE_PATH } from 'imdone-core/lib/adapters/storage/tags.js'
|
5
|
-
import { access, appendFile } from 'fs/promises'
|
6
|
-
import path from 'path'
|
7
|
-
|
8
|
-
const { CONFIG_FILE_YML } = constants
|
9
|
-
|
10
|
-
export async function isGitRepo(projectDir) {
|
11
|
-
try {
|
12
|
-
await access(path.join(projectDir, '.git'), constants.R_OK)
|
13
|
-
return true
|
14
|
-
} catch (error) {
|
15
|
-
return false
|
16
|
-
}
|
17
|
-
}
|
18
|
-
|
19
|
-
export async function createRepo(projectDir) {
|
20
|
-
if (await isGitRepo(projectDir)) {
|
21
|
-
return
|
22
|
-
}
|
23
|
-
gitRepo(projectDir).init()
|
24
|
-
}
|
25
|
-
|
26
|
-
export async function appendToGitignore(projectDir, ignorePath) {
|
27
|
-
const gitignorePath = path.join(projectDir, '.gitignore')
|
28
|
-
await appendFile(gitignorePath, `${ignorePath}\n`, 'utf8')
|
29
|
-
await addAndCommitChanges(projectDir, `Append ${ignorePath} to .gitignore`, [".gitignore"]);
|
30
|
-
}
|
31
|
-
|
32
|
-
export async function isFileModified(projectDir, filePath) {
|
33
|
-
// run git diff --name-only <file> || git diff --cached --name-only <file>
|
34
|
-
return new Promise((resolve, reject) => {
|
35
|
-
exec(`cd "${projectDir}" && git diff --name-only "${filePath}" || git diff --cached --name-only "${filePath}"`, (error, stdout, stderr) => {
|
36
|
-
if (error) {
|
37
|
-
console.error(`exec error: ${error}`);
|
38
|
-
reject(error);
|
39
|
-
} else {
|
40
|
-
resolve(stdout.trim() !== '');
|
41
|
-
}
|
42
|
-
});
|
43
|
-
});
|
44
|
-
}
|
45
|
-
|
46
|
-
export async function isImdoneInit(projectPath) {
|
47
|
-
const git = gitRepo(projectPath)
|
48
|
-
const status = await git.status()
|
49
|
-
if (status.isClean()) {
|
50
|
-
return false
|
51
|
-
}
|
52
|
-
const { files } = status
|
53
|
-
const configFile = files.find(file => file.path === CONFIG_FILE_YML)
|
54
|
-
const tagsFile = files.find(file => file.path === TAGS_FILE_PATH)
|
55
|
-
return configFile || tagsFile
|
56
|
-
}
|
57
|
-
|
58
|
-
export async function gitIgnoreExists(projectPath, ignorePath) {
|
59
|
-
const git = gitRepo(projectPath)
|
60
|
-
const ignored = await git.checkIgnore([ignorePath])
|
61
|
-
|
62
|
-
return ignored.length > 0
|
63
|
-
}
|
64
|
-
|
65
|
-
export async function hasLocalChanges(projectPath) {
|
66
|
-
const git = gitRepo(projectPath)
|
67
|
-
const status = await git.status()
|
68
|
-
return status?.files?.length > 0
|
69
|
-
}
|
70
|
-
|
71
|
-
export async function expectNoChanges(projectPath, message) {
|
72
|
-
if (await hasLocalChanges(projectPath)) {
|
73
|
-
throw new Error(`There are modified files. Commit or stash your changes before ${message}.`)
|
74
|
-
}
|
75
|
-
}
|
76
|
-
|
77
|
-
export async function addAndCommitChanges(projectPath, message, add = ['.']) {
|
78
|
-
const git = gitRepo(projectPath)
|
79
|
-
const files = await status()
|
80
|
-
if (files.length === 0) {
|
81
|
-
return
|
82
|
-
}
|
83
|
-
const toAdd = add.includes('.') ? add : add.filter(file => files.some(f => f.path === file))
|
84
|
-
if (toAdd.length === 0) {
|
85
|
-
return
|
86
|
-
}
|
87
|
-
await git.add(toAdd)
|
88
|
-
await git.commit(message)
|
89
|
-
}
|
90
|
-
|
91
|
-
export async function stashChanges(projectPath) {
|
92
|
-
const git = gitRepo(projectPath)
|
93
|
-
await git.stash(['push', '-m', `imdone-with-jira-${new Date().toISOString()}`]);
|
94
|
-
}
|
95
|
-
|
96
|
-
export async function stashPop(projectPath) {
|
97
|
-
const git = gitRepo(projectPath)
|
98
|
-
await git.stash(['pop']);
|
99
|
-
const { files } = await git.status()
|
100
|
-
return files
|
101
|
-
}
|
102
|
-
|
103
|
-
export async function status(projectPath) {
|
104
|
-
const git = gitRepo(projectPath)
|
105
|
-
const { files } = await git.status()
|
106
|
-
return files
|
107
|
-
}
|
108
|
-
|
109
|
-
export function gitRepo(projectPath) {
|
110
|
-
const git = simpleGit({ baseDir: projectPath })
|
111
|
-
return git
|
112
|
-
}
|
113
|
-
|
@@ -1,79 +0,0 @@
|
|
1
|
-
export function getImdoneConfigTemplate({name, jiraUrl, jql, jiraProjectKey}) {
|
2
|
-
return `keepEmptyPriority: true
|
3
|
-
code:
|
4
|
-
include_lists:
|
5
|
-
- TODO
|
6
|
-
- DOING
|
7
|
-
- DONE
|
8
|
-
- PLANNING
|
9
|
-
- FIXME
|
10
|
-
- ARCHIVE
|
11
|
-
- HACK
|
12
|
-
- CHANGED
|
13
|
-
- XXX
|
14
|
-
- IDEA
|
15
|
-
- NOTE
|
16
|
-
- REVIEW
|
17
|
-
- BACKLOG
|
18
|
-
lists:
|
19
|
-
- name: TODO
|
20
|
-
hidden: false
|
21
|
-
ignore: false
|
22
|
-
- name: DOING
|
23
|
-
hidden: false
|
24
|
-
ignore: false
|
25
|
-
- name: DONE
|
26
|
-
hidden: false
|
27
|
-
ignore: true
|
28
|
-
settings:
|
29
|
-
openIn: code
|
30
|
-
openCodeIn: default
|
31
|
-
journalType: New File
|
32
|
-
journalPath: current-sprint
|
33
|
-
newCardSyntax: HASHTAG
|
34
|
-
replaceSpacesWith: '-'
|
35
|
-
plugins:
|
36
|
-
JiraPlugin:
|
37
|
-
jql: ${jql}
|
38
|
-
issueType: Story
|
39
|
-
projectKeys:
|
40
|
-
- key: ${jiraProjectKey}
|
41
|
-
statuses:
|
42
|
-
- jira: To Do
|
43
|
-
list: TODO
|
44
|
-
- jira: In Progress
|
45
|
-
list: DOING
|
46
|
-
- jira: Done
|
47
|
-
list: DONE
|
48
|
-
url: '${jiraUrl}'
|
49
|
-
defaultList: DOING
|
50
|
-
defaultDirectory: current-sprint
|
51
|
-
devMode: false
|
52
|
-
journalTemplate: null
|
53
|
-
markdownOnly: false
|
54
|
-
kudosProbability: 0.33
|
55
|
-
name: ${name}
|
56
|
-
views: []
|
57
|
-
cards:
|
58
|
-
colors: []
|
59
|
-
template: ''
|
60
|
-
trackChanges: false
|
61
|
-
metaNewLine: true
|
62
|
-
addCompletedMeta: true
|
63
|
-
addCheckBoxTasks: false
|
64
|
-
doingList: DOING
|
65
|
-
doneList: DONE
|
66
|
-
tokenPrefix: '#'
|
67
|
-
taskPrefix: '#'
|
68
|
-
tagPrefix: '#'
|
69
|
-
metaSep: ':'
|
70
|
-
orderMeta: true
|
71
|
-
maxLines: 3
|
72
|
-
addNewCardsToTop: true
|
73
|
-
showTagsAndMeta: false
|
74
|
-
addStartedMeta: false
|
75
|
-
defaultList: TODO
|
76
|
-
archiveCompleted: true
|
77
|
-
archiveFolder: archive
|
78
|
-
`
|
79
|
-
};
|
package/src/adapters/imdone.js
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
import { logger } from 'imdone-core/lib/adapters/logger.js'
|
2
|
-
import { createFileSystemProject } from 'imdone-core/lib/project-factory.js'
|
3
|
-
import { constants } from 'imdone-core/lib/constants.js'
|
4
|
-
import { load, isImdoneProject } from 'imdone-core/lib/adapters/storage/config.js'
|
5
|
-
import { isImdoneInit, addAndCommitChanges } from './git.js'
|
6
|
-
import { appendFile } from 'fs/promises'
|
7
|
-
import path from 'path'
|
8
|
-
import { getImdoneConfigTemplate } from './imdone-config-template.js'
|
9
|
-
import JiraPlugin from '../../../main.js'
|
10
|
-
import { TAGS_FILE_PATH } from 'imdone-core/lib/adapters/storage/tags.js'
|
11
|
-
import { preparePathForWriting, writeFile } from 'imdone-core/lib/adapters/file-gateway.js'
|
12
|
-
const { CONFIG_FILE_YML, IGNORE_FILE } = constants
|
13
|
-
|
14
|
-
export async function getConfigPath(projectPath) {
|
15
|
-
const configPath = path.join(projectPath, CONFIG_FILE_YML)
|
16
|
-
return configPath
|
17
|
-
}
|
18
|
-
|
19
|
-
export async function getProject(projectPath, feedback = {}) {
|
20
|
-
if (!await isImdoneProject(projectPath)) {
|
21
|
-
throw new Error(`Project is not initialized. Run "imdone init" to initialize the project.`)
|
22
|
-
}
|
23
|
-
const config = await load(projectPath)
|
24
|
-
const project = createFileSystemProject({
|
25
|
-
path: projectPath,
|
26
|
-
config
|
27
|
-
})
|
28
|
-
project.toast = ({message}) => feedback.text = message
|
29
|
-
project.snackBar = ({message}) => feedback.text = message
|
30
|
-
|
31
|
-
project.emit = function(event, opts = {}) {
|
32
|
-
const { message, type, duration } = opts
|
33
|
-
logger.info(event, { message, type, duration })
|
34
|
-
}
|
35
|
-
await project.init()
|
36
|
-
|
37
|
-
if (await isImdoneInit(projectPath)) {
|
38
|
-
await addAndCommitChanges(projectPath, 'Initial commit', [CONFIG_FILE_YML, TAGS_FILE_PATH])
|
39
|
-
}
|
40
|
-
|
41
|
-
const jiraPlugin = await project.pluginManager.createPlugin(JiraPlugin)
|
42
|
-
const { pullFromJiraAction, pushToJiraAction } = jiraPlugin
|
43
|
-
return {
|
44
|
-
project,
|
45
|
-
jiraPlugin: {
|
46
|
-
pullFromJiraAction,
|
47
|
-
pushToJiraAction
|
48
|
-
}
|
49
|
-
}
|
50
|
-
}
|
51
|
-
|
52
|
-
export async function initProject(projectPath, {
|
53
|
-
name,
|
54
|
-
jiraUrl,
|
55
|
-
jiraProjectKey,
|
56
|
-
jql
|
57
|
-
}) {
|
58
|
-
const configContent = getImdoneConfigTemplate({
|
59
|
-
name,
|
60
|
-
jiraUrl,
|
61
|
-
jiraProjectKey,
|
62
|
-
jql
|
63
|
-
})
|
64
|
-
|
65
|
-
const configPath = getConfigPath(projectPath)
|
66
|
-
await preparePathForWriting(configPath)
|
67
|
-
await writeFile(configPath, configContent, 'utf8')
|
68
|
-
await getProject(projectPath)
|
69
|
-
}
|
70
|
-
|
71
|
-
export async function appendToImdoneIgnore(projectPath, ignorePath) {
|
72
|
-
const imdoneIgnorePath = path.join(projectPath, IGNORE_FILE)
|
73
|
-
await appendFile(imdoneIgnorePath, `${ignorePath}\n`, 'utf8')
|
74
|
-
await addAndCommitChanges(projectPath, `Append ${ignorePath} to ${IGNORE_FILE}`, [IGNORE_FILE])
|
75
|
-
}
|
76
|
-
|
package/src/adapters/license.js
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
2
|
-
import jwt from 'jsonwebtoken';
|
3
|
-
import path from 'node:path';
|
4
|
-
|
5
|
-
const DIRNAME = path.dirname(import.meta.url);
|
6
|
-
const PUBLIC_KEY_PATH = path.join(DIRNAME, 'jwt-public.pem');
|
7
|
-
|
8
|
-
export const DEFAULT_PAYLOAD = {
|
9
|
-
name: undefined,
|
10
|
-
email: undefined,
|
11
|
-
org: undefined,
|
12
|
-
plan: "premium",
|
13
|
-
expiresIn: '30d',
|
14
|
-
}
|
15
|
-
|
16
|
-
export async function generateLicense(payload, privateKeyPath) {
|
17
|
-
// Apply default values to payload
|
18
|
-
const finalPayload = { ...DEFAULT_PAYLOAD, ...payload };
|
19
|
-
|
20
|
-
// throw an exception if the payload is missing required fields
|
21
|
-
const requiredFields = ['name', 'email', 'org', 'plan', 'expiresIn'];
|
22
|
-
for (const field of requiredFields) {
|
23
|
-
if (!finalPayload[field]) {
|
24
|
-
throw new Error(`Missing required field: ${field}`);
|
25
|
-
}
|
26
|
-
}
|
27
|
-
|
28
|
-
// Check if the payload has any extra fields
|
29
|
-
const extraFields = Object.keys(finalPayload).filter(field => !DEFAULT_PAYLOAD.hasOwnProperty(field));
|
30
|
-
if (extraFields.length > 0) {
|
31
|
-
throw new Error(`Extra fields found in payload: ${extraFields.join(', ')}`);
|
32
|
-
}
|
33
|
-
|
34
|
-
const { expiresIn } = finalPayload;
|
35
|
-
delete finalPayload.expiresIn; // Remove expiresIn from payload
|
36
|
-
|
37
|
-
const privateKey = await readFile(privateKeyPath, 'utf8');
|
38
|
-
return jwt.sign(finalPayload, privateKey, {
|
39
|
-
algorithm: 'RS256',
|
40
|
-
expiresIn,
|
41
|
-
});
|
42
|
-
}
|
43
|
-
|
44
|
-
export async function verifyLicense(token, publicKeyPath = PUBLIC_KEY_PATH) {
|
45
|
-
try {
|
46
|
-
const publicKey = await readFile(publicKeyPath, 'utf8');
|
47
|
-
const decoded = jwt.verify(token, publicKey, {
|
48
|
-
algorithms: ['RS256'],
|
49
|
-
});
|
50
|
-
return { valid: true, data: License.fromPayload(decoded) };
|
51
|
-
} catch (err) {
|
52
|
-
return { valid: false, reason: err.message };
|
53
|
-
}
|
54
|
-
}
|
55
|
-
|
56
|
-
export class License {
|
57
|
-
constructor({name, email, org, plan, created, expires}) {
|
58
|
-
this.name = name;
|
59
|
-
this.email = email;
|
60
|
-
this.org = org;
|
61
|
-
this.plan = plan;
|
62
|
-
this.created = created;
|
63
|
-
this.expires = expires;
|
64
|
-
}
|
65
|
-
|
66
|
-
static fromPayload(payload) {
|
67
|
-
return new License({
|
68
|
-
name: payload.name,
|
69
|
-
email: payload.email,
|
70
|
-
org: payload.org,
|
71
|
-
plan: payload.plan,
|
72
|
-
created: new Date(payload.iat * 1000),
|
73
|
-
expires: new Date(payload.exp * 1000),
|
74
|
-
});
|
75
|
-
}
|
76
|
-
}
|
77
|
-
|
78
|
-
export async function validateEnvLicense() {
|
79
|
-
const { IMDONE_LICENSE } = process.env;
|
80
|
-
return IMDONE_LICENSE
|
81
|
-
? await verifyLicense(IMDONE_LICENSE)
|
82
|
-
: { valid: false, reason: 'No license found in environment variables.' };
|
83
|
-
}
|
package/src/bin.js
DELETED
@@ -1,173 +0,0 @@
|
|
1
|
-
#!/usr/bin/env node
|
2
|
-
|
3
|
-
import { Command } from 'commander';
|
4
|
-
import ora from 'ora';
|
5
|
-
import { pullFromJira, dependencies as pullDeps } from "./usecases/headless-pull-from-jira.js";
|
6
|
-
import { pushToJira, dependencies as pushDeps } from "./usecases/headless-push-to-jira.js";
|
7
|
-
import { logger, LogLevel } from 'imdone-core/lib/adapters/logger.js';
|
8
|
-
import { gitRepoCheck, imdoneProjectCheck, dotEnvCheck, gitIgnoreCheck, promptForConfig, promptForLicense, checkLicense } from './ui/prompt.js';
|
9
|
-
import { imdoneInit } from './usecases/init.js';
|
10
|
-
import { verifyLicense } from './adapters/license.js';
|
11
|
-
import { init as initEnv, storeLicense } from './adapters/env.js';
|
12
|
-
// get the version from package.json
|
13
|
-
import { version } from '../package.json';
|
14
|
-
logger.setLevel(process.env.LOG_LEVEL || LogLevel.ERROR);
|
15
|
-
|
16
|
-
async function pullCommand() {
|
17
|
-
const spinner = ora('Pulling issues from Jira...').start();
|
18
|
-
|
19
|
-
try {
|
20
|
-
const { result } = await pullFromJira(process.cwd(), { ...pullDeps, feedback: spinner });
|
21
|
-
const { existing, added } = result
|
22
|
-
const message = existing.length > 0 || added.length > 0
|
23
|
-
? `Pulled ${existing.length} existing and ${added.length} new issues from Jira`
|
24
|
-
: 'No issues to pull from Jira';
|
25
|
-
spinner.succeed(message);
|
26
|
-
} catch (error) {
|
27
|
-
spinner.fail(`Failed to pull issues from Jira: ${error.message}`);
|
28
|
-
logger.error(error.stack);
|
29
|
-
process.exit(1);
|
30
|
-
}
|
31
|
-
}
|
32
|
-
|
33
|
-
async function pushCommand() {
|
34
|
-
const spinner = ora('Pushing changes to Jira...').start();
|
35
|
-
try {
|
36
|
-
const { result } = await pushToJira(process.cwd(), { ...pushDeps, feedback: ora });
|
37
|
-
const message = result.length > 0 ? `Pushed ${result.length} changes to Jira` : 'No changes to push to Jira';
|
38
|
-
spinner.succeed(message);
|
39
|
-
result.forEach(res => {
|
40
|
-
console.log(res.url)
|
41
|
-
})
|
42
|
-
} catch (error) {
|
43
|
-
spinner.fail(`Failed to push changes to Jira: ${error.message}`);
|
44
|
-
logger.error(error.stack)
|
45
|
-
process.exit(1);
|
46
|
-
}
|
47
|
-
}
|
48
|
-
|
49
|
-
const program = new Command();
|
50
|
-
|
51
|
-
program
|
52
|
-
.name('imdone')
|
53
|
-
.description('Imdone CLI to pull from and push to Jira')
|
54
|
-
.version(version);
|
55
|
-
|
56
|
-
program
|
57
|
-
.command('pull')
|
58
|
-
.description('Pull issues from Jira')
|
59
|
-
.action(async () => {
|
60
|
-
await checkLicense();
|
61
|
-
await pullCommand();
|
62
|
-
});
|
63
|
-
|
64
|
-
program
|
65
|
-
.command('push')
|
66
|
-
.description('Push changes to Jira')
|
67
|
-
.action(async () => {
|
68
|
-
await checkLicense();
|
69
|
-
await pullCommand();
|
70
|
-
await pushCommand();
|
71
|
-
});
|
72
|
-
|
73
|
-
program
|
74
|
-
.command('init')
|
75
|
-
.description('Initialize Jira integration')
|
76
|
-
.option('--name [name]', 'Project name')
|
77
|
-
.option('--jira-url [jiraUrl]', 'Jira URL (e.g., https://your-domain.atlassian.net)')
|
78
|
-
.option('--jira-project-key [jiraProjectKey]', 'Jira project key (e.g., ABC)')
|
79
|
-
.option('--jira-username [jiraUsername]', 'Jira username (email)')
|
80
|
-
.option('--jira-api-token [jiraApiToken]', 'Jira API token')
|
81
|
-
.option('--jql [jql]', 'JQL query to fetch issues (e.g., Sprint in openSprints() and status != "Done")')
|
82
|
-
.action(async (options) => {
|
83
|
-
const projectPath = process.cwd();
|
84
|
-
|
85
|
-
try {
|
86
|
-
if (
|
87
|
-
!await gitRepoCheck(projectPath)
|
88
|
-
|| !await gitIgnoreCheck(projectPath)
|
89
|
-
|| !await dotEnvCheck(projectPath)
|
90
|
-
|| !await imdoneProjectCheck(projectPath)
|
91
|
-
) {
|
92
|
-
console.log('Configuration cancelled');
|
93
|
-
process.exit(1);
|
94
|
-
}
|
95
|
-
} catch (error) {
|
96
|
-
logger.error('Imdone initialization failed:', error);
|
97
|
-
process.exit(1);
|
98
|
-
}
|
99
|
-
|
100
|
-
const config = await promptForConfig(options);
|
101
|
-
|
102
|
-
const spinner = ora('Initializing imdone...').start();
|
103
|
-
|
104
|
-
try {
|
105
|
-
await imdoneInit(projectPath, config);
|
106
|
-
spinner.succeed(`Imdone initialization for "${config.name}" completed successfully`);
|
107
|
-
} catch (error) {
|
108
|
-
spinner.fail(`Imdone initialization failed: ${error.message}`);
|
109
|
-
logger.error(error);
|
110
|
-
process.exit(1);
|
111
|
-
}
|
112
|
-
});
|
113
|
-
|
114
|
-
program
|
115
|
-
.command('license')
|
116
|
-
.description('Enter or update your Imdone license')
|
117
|
-
.option('--token [token]', 'License token')
|
118
|
-
.option('--show', 'Show current license information')
|
119
|
-
.action(async (options) => {
|
120
|
-
const projectPath = process.cwd();
|
121
|
-
|
122
|
-
if (options.show) {
|
123
|
-
const spinner = ora('Fetching license information...').start();
|
124
|
-
try {
|
125
|
-
const licenseCheck = await verifyLicense(process.env.IMDONE_LICENSE);
|
126
|
-
if (licenseCheck.valid) {
|
127
|
-
const { name, email, expires } = licenseCheck.data
|
128
|
-
spinner.succeed(`Current license: ${name} (${email}) - Expires: ${expires.toLocaleDateString()}`);
|
129
|
-
} else {
|
130
|
-
spinner.fail(`Invalid or expired license: ${licenseCheck.reason}`);
|
131
|
-
}
|
132
|
-
} catch (error) {
|
133
|
-
spinner.fail(`Failed to fetch license information: ${error.message}`);
|
134
|
-
logger.error(error);
|
135
|
-
}
|
136
|
-
return;
|
137
|
-
}
|
138
|
-
|
139
|
-
let licenseToken = options.token;
|
140
|
-
|
141
|
-
// If no token provided via command line, prompt for it
|
142
|
-
if (!licenseToken) {
|
143
|
-
licenseToken = await promptForLicense();
|
144
|
-
}
|
145
|
-
|
146
|
-
const spinner = ora('Validating license...').start();
|
147
|
-
|
148
|
-
try {
|
149
|
-
// Verify the license is valid before storing it
|
150
|
-
const licenseCheck = await verifyLicense(licenseToken);
|
151
|
-
|
152
|
-
if (!licenseCheck.valid) {
|
153
|
-
spinner.fail(`Invalid license: ${licenseCheck.reason}`);
|
154
|
-
process.exit(1);
|
155
|
-
}
|
156
|
-
|
157
|
-
// License is valid, store it
|
158
|
-
await storeLicense(projectPath, licenseToken);
|
159
|
-
|
160
|
-
const { name, email, expires } = licenseCheck.data
|
161
|
-
spinner.succeed(`License accepted for ${name} (${email}) - Expires: ${expires.toLocaleDateString()}`);
|
162
|
-
} catch (error) {
|
163
|
-
spinner.fail(`Failed to validate or store license: ${error.message}`);
|
164
|
-
logger.error(error);
|
165
|
-
process.exit(1);
|
166
|
-
}
|
167
|
-
});
|
168
|
-
|
169
|
-
(async function main() {
|
170
|
-
await initEnv(process.cwd());
|
171
|
-
program.parse();
|
172
|
-
})();
|
173
|
-
|
package/src/index.js
DELETED