kup-cli 0.1.0-beta.3 → 0.1.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/bin/cli.js +7 -0
- package/lib/error.js +39 -0
- package/lib/file.js +1 -1
- package/lib/main.js +25 -8
- package/lib/meta.js +131 -0
- package/lib/parse.js +11 -8
- package/lib/repo.js +2 -1
- package/lib/sync.js +74 -24
- package/lib/token.js +22 -13
- package/package.json +15 -8
package/bin/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import yargs from 'yargs'
|
|
3
3
|
import { hideBin } from 'yargs/helpers'
|
|
4
|
+
import { printKupError } from '../lib/error.js'
|
|
4
5
|
import { isDebugging } from '../lib/util.js'
|
|
5
6
|
import { main } from '../lib/main.js'
|
|
6
7
|
import { validate } from '../lib/validate.js'
|
|
@@ -48,3 +49,9 @@ main(argv)
|
|
|
48
49
|
.then(() => {
|
|
49
50
|
console.log('[Kup] Done!')
|
|
50
51
|
})
|
|
52
|
+
.catch((error) => {
|
|
53
|
+
if (!printKupError(error)) {
|
|
54
|
+
console.error(error)
|
|
55
|
+
}
|
|
56
|
+
process.exit(error.exitCode || 1)
|
|
57
|
+
})
|
package/lib/error.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class KupError extends Error {
|
|
2
|
+
constructor(outputLines, exitCode = 1) {
|
|
3
|
+
const firstLine = outputLines[0]
|
|
4
|
+
super(firstLine?.text || 'Kup command failed.')
|
|
5
|
+
this.name = 'KupError'
|
|
6
|
+
this.outputLines = outputLines
|
|
7
|
+
this.exitCode = exitCode
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function errorLine(text) {
|
|
12
|
+
return {
|
|
13
|
+
method: 'error',
|
|
14
|
+
text,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function logLine(text) {
|
|
19
|
+
return {
|
|
20
|
+
method: 'log',
|
|
21
|
+
text,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function printKupError(error) {
|
|
26
|
+
if (!(error instanceof KupError)) return false
|
|
27
|
+
|
|
28
|
+
error.outputLines.forEach(({ method, text }) => {
|
|
29
|
+
console[method](text)
|
|
30
|
+
})
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
KupError,
|
|
36
|
+
errorLine,
|
|
37
|
+
logLine,
|
|
38
|
+
printKupError,
|
|
39
|
+
}
|
package/lib/file.js
CHANGED
package/lib/main.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isDebugging } from './util.js'
|
|
2
|
+
import { KupError, errorLine } from './error.js'
|
|
2
3
|
import { readTextFile } from './file.js'
|
|
3
4
|
import { parse } from './parse.js'
|
|
4
5
|
import { getRepo } from './repo.js'
|
|
@@ -18,9 +19,10 @@ async function main(argv) {
|
|
|
18
19
|
try {
|
|
19
20
|
content = await readTextFile(file)
|
|
20
21
|
} catch (e) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
throw new KupError([
|
|
23
|
+
errorLine(`[Kup] [Error] Cannot read file "${ file }"!`),
|
|
24
|
+
errorLine(e.message),
|
|
25
|
+
])
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const fileInfo = parse(content)
|
|
@@ -33,11 +35,22 @@ async function main(argv) {
|
|
|
33
35
|
|
|
34
36
|
// 通过各种方式获取 repo
|
|
35
37
|
// 优先级: 命令行参数 > 文件元数据 > 文件所在项目的 package.json 中的 `kup.repo` 字段
|
|
36
|
-
let repoReal =
|
|
37
|
-
|
|
38
|
+
let repoReal = ''
|
|
39
|
+
let repoSource = ''
|
|
40
|
+
if (repo) {
|
|
41
|
+
repoReal = repo
|
|
42
|
+
repoSource = 'cli'
|
|
43
|
+
} else if (fileInfo.meta.repo) {
|
|
44
|
+
repoReal = fileInfo.meta.repo
|
|
45
|
+
repoSource = 'meta'
|
|
46
|
+
} else {
|
|
47
|
+
repoReal = await getRepo(file)
|
|
48
|
+
if (repoReal) repoSource = 'package'
|
|
49
|
+
}
|
|
38
50
|
if (!repoReal) {
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
throw new KupError([
|
|
52
|
+
errorLine('[Kup] [Error] Cannot get `repo` to sync to!'),
|
|
53
|
+
])
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
// 检查 TOKEN
|
|
@@ -47,7 +60,11 @@ async function main(argv) {
|
|
|
47
60
|
if (idReal) {
|
|
48
61
|
await updateIssue(fileInfo, repoReal, idReal)
|
|
49
62
|
} else {
|
|
50
|
-
await postIssue(fileInfo, repoReal
|
|
63
|
+
await postIssue(fileInfo, repoReal, {
|
|
64
|
+
file,
|
|
65
|
+
repoSource,
|
|
66
|
+
hasRepoInMeta: !!fileInfo.meta.repo,
|
|
67
|
+
})
|
|
51
68
|
}
|
|
52
69
|
}
|
|
53
70
|
}
|
package/lib/meta.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const SEPARATOR = '---'
|
|
2
|
+
const SEPARATOR_END_ALT = '...'
|
|
3
|
+
|
|
4
|
+
function updateIssueMeta(content, {
|
|
5
|
+
id,
|
|
6
|
+
repo = '',
|
|
7
|
+
shouldWriteRepo = false,
|
|
8
|
+
} = {}) {
|
|
9
|
+
const eol = detectEol(content)
|
|
10
|
+
const lines = content.split(eol)
|
|
11
|
+
const metaSection = detectMetaSection(lines)
|
|
12
|
+
|
|
13
|
+
if (!metaSection) {
|
|
14
|
+
return insertNewMetaSection(lines, {
|
|
15
|
+
eol,
|
|
16
|
+
id,
|
|
17
|
+
repo,
|
|
18
|
+
shouldWriteRepo,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const metaLines = lines.slice(metaSection.start + 1, metaSection.end)
|
|
23
|
+
upsertMetaLine(metaLines, 'repo', repo, shouldWriteRepo, ['id', 'tags', 'title'])
|
|
24
|
+
upsertMetaLine(metaLines, 'id', String(id), true, ['tags', 'title'], ['repo'])
|
|
25
|
+
|
|
26
|
+
return [
|
|
27
|
+
...lines.slice(0, metaSection.start + 1),
|
|
28
|
+
...metaLines,
|
|
29
|
+
...lines.slice(metaSection.end),
|
|
30
|
+
].join(eol)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function detectEol(content) {
|
|
34
|
+
return content.includes('\r\n') ? '\r\n' : '\n'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function detectMetaSection(lines) {
|
|
38
|
+
let start = 0
|
|
39
|
+
while (start < lines.length && !lines[start].trim()) {
|
|
40
|
+
start++
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const firstLine = lines[start]
|
|
44
|
+
const secondLine = lines[start + 1]
|
|
45
|
+
if (!firstLine || !secondLine) return null
|
|
46
|
+
|
|
47
|
+
const hasSeparatorStart = firstLine.trimEnd() === SEPARATOR
|
|
48
|
+
const noEmptyLineAfterSeparatorStart = !!secondLine.trimEnd()
|
|
49
|
+
const hasYamlKey = /^#?\s*[\w\-]+:/.test(secondLine.trim())
|
|
50
|
+
if (!(hasSeparatorStart && noEmptyLineAfterSeparatorStart && hasYamlKey)) {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
55
|
+
const line = lines[i].trimEnd()
|
|
56
|
+
if (line === SEPARATOR || line === SEPARATOR_END_ALT) {
|
|
57
|
+
return { start, end: i }
|
|
58
|
+
}
|
|
59
|
+
if (!line) break
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function insertNewMetaSection(lines, { eol, id, repo, shouldWriteRepo }) {
|
|
66
|
+
let insertAt = 0
|
|
67
|
+
while (insertAt < lines.length && !lines[insertAt].trim()) {
|
|
68
|
+
insertAt++
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const metaLines = [SEPARATOR]
|
|
72
|
+
if (shouldWriteRepo) metaLines.push(`repo: ${ repo }`)
|
|
73
|
+
metaLines.push(`id: ${ id }`)
|
|
74
|
+
metaLines.push(SEPARATOR)
|
|
75
|
+
|
|
76
|
+
const bodyLines = lines.slice(insertAt)
|
|
77
|
+
const result = [...metaLines]
|
|
78
|
+
if (bodyLines.length) {
|
|
79
|
+
result.push('')
|
|
80
|
+
result.push(...bodyLines)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return result.join(eol)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function upsertMetaLine(metaLines, key, value, shouldWrite, beforeKeys = [], afterKeys = []) {
|
|
87
|
+
if (!shouldWrite) return
|
|
88
|
+
|
|
89
|
+
const existingIndex = findMetaLineIndex(metaLines, key)
|
|
90
|
+
if (existingIndex >= 0) {
|
|
91
|
+
metaLines[existingIndex] = replaceMetaLineValue(metaLines[existingIndex], key, value)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const insertIndex = findInsertIndex(metaLines, beforeKeys, afterKeys)
|
|
96
|
+
metaLines.splice(insertIndex, 0, `${ key }: ${ value }`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function findMetaLineIndex(metaLines, key) {
|
|
100
|
+
return metaLines.findIndex((line) => {
|
|
101
|
+
const match = /^(\s*)([\w\-]+)(\s*:.*)$/.exec(line)
|
|
102
|
+
return match?.[2] === key
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function replaceMetaLineValue(line, key, value) {
|
|
107
|
+
const match = new RegExp(`^(\\s*${ key }\\s*:)(\\s*)(.*?)(\\s+#.*)?$`).exec(line)
|
|
108
|
+
if (!match) return `${ key }: ${ value }`
|
|
109
|
+
|
|
110
|
+
const [, prefix, , , comment = ''] = match
|
|
111
|
+
return `${ prefix } ${ value }${ comment }`
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function findInsertIndex(metaLines, beforeKeys, afterKeys) {
|
|
115
|
+
for (const key of beforeKeys) {
|
|
116
|
+
const index = findMetaLineIndex(metaLines, key)
|
|
117
|
+
if (index >= 0) return index
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let afterIndex = -1
|
|
121
|
+
for (const key of afterKeys) {
|
|
122
|
+
const index = findMetaLineIndex(metaLines, key)
|
|
123
|
+
if (index > afterIndex) afterIndex = index
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return afterIndex >= 0 ? afterIndex + 1 : metaLines.length
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export {
|
|
130
|
+
updateIssueMeta,
|
|
131
|
+
}
|
package/lib/parse.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import YAML from 'yaml'
|
|
2
2
|
|
|
3
|
+
import { KupError, errorLine } from './error.js'
|
|
3
4
|
import { isPlainObject } from './util.js'
|
|
4
5
|
|
|
5
6
|
// parse markdown file:
|
|
@@ -67,17 +68,19 @@ function _parseMetaSection(lines, metaLineQty) {
|
|
|
67
68
|
try {
|
|
68
69
|
result = YAML.parse(yaml)
|
|
69
70
|
} catch (e) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
throw new KupError([
|
|
72
|
+
errorLine('[Kup] [Error] Cannot parse YAML meta section:'),
|
|
73
|
+
errorLine(SEPARATOR),
|
|
74
|
+
errorLine(yaml),
|
|
75
|
+
errorLine(SEPARATOR),
|
|
76
|
+
errorLine(e.message),
|
|
77
|
+
])
|
|
76
78
|
}
|
|
77
79
|
// console.log(result)
|
|
78
80
|
if (!isPlainObject(result)) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
throw new KupError([
|
|
82
|
+
errorLine('[Kup] [Error] YAML data must be an Object!'),
|
|
83
|
+
])
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
// format
|
package/lib/repo.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import findUp from 'find-up'
|
|
1
|
+
import { findUp } from 'find-up'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
|
|
4
4
|
import { readTextFile } from './file.js'
|
|
@@ -25,6 +25,7 @@ async function _getRepoFromGit() {
|
|
|
25
25
|
async function _getPkg(sourceFile = '') {
|
|
26
26
|
const cwd = sourceFile ? path.resolve(path.dirname(sourceFile)) : process.cwd()
|
|
27
27
|
const file = await findUp('package.json', { cwd })
|
|
28
|
+
if (!file) return null
|
|
28
29
|
|
|
29
30
|
let pkg = ''
|
|
30
31
|
try {
|
package/lib/sync.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import ghGot from 'gh-got'
|
|
2
2
|
import inquirer from 'inquirer'
|
|
3
3
|
|
|
4
|
+
import { KupError, errorLine, logLine } from './error.js'
|
|
5
|
+
import { readTextFile, writeTextFile } from './file.js'
|
|
6
|
+
import { updateIssueMeta } from './meta.js'
|
|
4
7
|
import { isDebugging } from '../lib/util.js'
|
|
5
8
|
|
|
6
9
|
let proxyOptionsForGot = {}
|
|
@@ -18,14 +21,7 @@ if (isDebugging()) {
|
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
async function updateIssue(fileInfo, repo, id) {
|
|
21
|
-
|
|
22
|
-
// 因为接口是 PATCH 方法,只需要提供需要更新的字段
|
|
23
|
-
// Markdown 文件未提供的字段就不提交了,而不是提交空值,以免清空已有值
|
|
24
|
-
const title = fileInfo.meta.title || fileInfo.title
|
|
25
|
-
const body = fileInfo.content
|
|
26
|
-
const data = { body }
|
|
27
|
-
if (title) data.title = title
|
|
28
|
-
if (fileInfo.meta.tags) data.labels = fileInfo.meta.tags
|
|
24
|
+
const data = buildUpdateIssuePayload(fileInfo)
|
|
29
25
|
if (isDebugging()) {
|
|
30
26
|
console.log('[Kup] [Debug] requestBody =', data)
|
|
31
27
|
}
|
|
@@ -38,13 +34,14 @@ async function updateIssue(fileInfo, repo, id) {
|
|
|
38
34
|
try {
|
|
39
35
|
response = await ghGot(api, {
|
|
40
36
|
method,
|
|
41
|
-
|
|
37
|
+
json: data,
|
|
42
38
|
token: process.env.GITHUB_TOKEN,
|
|
43
39
|
...proxyOptionsForGot,
|
|
44
40
|
})
|
|
45
41
|
} catch (e) {
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
throw new KupError([
|
|
43
|
+
errorLine('[Kup] [Error] Request error: ' + e.message),
|
|
44
|
+
])
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
// handle response
|
|
@@ -55,12 +52,8 @@ async function updateIssue(fileInfo, repo, id) {
|
|
|
55
52
|
}
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
async function postIssue(fileInfo, repo) {
|
|
59
|
-
|
|
60
|
-
const title = fileInfo.meta.title || fileInfo.title || 'Issue posted by Kup @' + new Date().toISOString()
|
|
61
|
-
const body = fileInfo.content
|
|
62
|
-
const data = { title, body }
|
|
63
|
-
if (fileInfo.meta.tags) data.labels = fileInfo.meta.tags
|
|
55
|
+
async function postIssue(fileInfo, repo, options = {}) {
|
|
56
|
+
const data = buildPostIssuePayload(fileInfo)
|
|
64
57
|
if (isDebugging()) {
|
|
65
58
|
console.log('[Kup] [Debug] requestBody =', data)
|
|
66
59
|
}
|
|
@@ -84,17 +77,19 @@ async function postIssue(fileInfo, repo) {
|
|
|
84
77
|
try {
|
|
85
78
|
response = await ghGot(api, {
|
|
86
79
|
method,
|
|
87
|
-
|
|
80
|
+
json: data,
|
|
88
81
|
token: process.env.GITHUB_TOKEN,
|
|
89
82
|
...proxyOptionsForGot,
|
|
90
83
|
})
|
|
91
84
|
} catch (e) {
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
throw new KupError([
|
|
86
|
+
errorLine('[Kup] [Error] Request error: ' + e.message),
|
|
87
|
+
])
|
|
94
88
|
}
|
|
95
89
|
} else {
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
throw new KupError([
|
|
91
|
+
logLine('[Kup] Aborted!'),
|
|
92
|
+
])
|
|
98
93
|
}
|
|
99
94
|
|
|
100
95
|
// handle response
|
|
@@ -104,12 +99,67 @@ async function postIssue(fileInfo, repo) {
|
|
|
104
99
|
console.log(`[Kup] [Success] Posted to "${ repo }#${ id }"!`)
|
|
105
100
|
console.log(`[Kup] [Success] URL: ${ url }`)
|
|
106
101
|
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
if (options.file) {
|
|
103
|
+
await writeIssueMeta(options.file, {
|
|
104
|
+
id,
|
|
105
|
+
repo,
|
|
106
|
+
shouldWriteRepo: options.repoSource === 'cli' && !options.hasRepoInMeta,
|
|
107
|
+
})
|
|
108
|
+
console.log(`[Kup] [Notice] Updated metadata in Markdown file: ${ options.file }`)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function writeIssueMeta(file, { id, repo, shouldWriteRepo }) {
|
|
114
|
+
let content = ''
|
|
115
|
+
try {
|
|
116
|
+
content = await readTextFile(file)
|
|
117
|
+
} catch (e) {
|
|
118
|
+
throw new KupError([
|
|
119
|
+
errorLine(`[Kup] [Error] Cannot read file "${ file }"!`),
|
|
120
|
+
errorLine(e.message),
|
|
121
|
+
])
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const nextContent = updateIssueMeta(content, {
|
|
125
|
+
id,
|
|
126
|
+
repo,
|
|
127
|
+
shouldWriteRepo,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await writeTextFile(file, nextContent)
|
|
132
|
+
} catch (e) {
|
|
133
|
+
throw new KupError([
|
|
134
|
+
errorLine(`[Kup] [Error] Cannot write file "${ file }"!`),
|
|
135
|
+
errorLine(e.message),
|
|
136
|
+
])
|
|
109
137
|
}
|
|
110
138
|
}
|
|
111
139
|
|
|
140
|
+
function buildUpdateIssuePayload(fileInfo) {
|
|
141
|
+
// 因为接口是 PATCH 方法,只需要提供需要更新的字段
|
|
142
|
+
// Markdown 文件未提供的字段就不提交了,而不是提交空值,以免清空已有值
|
|
143
|
+
const title = fileInfo.meta.title || fileInfo.title
|
|
144
|
+
const body = fileInfo.content
|
|
145
|
+
const data = { body }
|
|
146
|
+
if (title) data.title = title
|
|
147
|
+
if (fileInfo.meta.tags) data.labels = fileInfo.meta.tags
|
|
148
|
+
return data
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildPostIssuePayload(fileInfo) {
|
|
152
|
+
const title = fileInfo.meta.title || fileInfo.title || 'Issue posted by Kup @' + new Date().toISOString()
|
|
153
|
+
const body = fileInfo.content
|
|
154
|
+
const data = { title, body }
|
|
155
|
+
if (fileInfo.meta.tags) data.labels = fileInfo.meta.tags
|
|
156
|
+
return data
|
|
157
|
+
}
|
|
158
|
+
|
|
112
159
|
export {
|
|
160
|
+
buildPostIssuePayload,
|
|
161
|
+
buildUpdateIssuePayload,
|
|
113
162
|
updateIssue,
|
|
114
163
|
postIssue,
|
|
164
|
+
writeIssueMeta,
|
|
115
165
|
}
|
package/lib/token.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import inquirer from 'inquirer'
|
|
2
2
|
|
|
3
|
+
import { KupError, errorLine, logLine } from './error.js'
|
|
4
|
+
|
|
3
5
|
async function getToken() {
|
|
4
6
|
let token = process.env.GITHUB_TOKEN
|
|
5
7
|
if (!token) {
|
|
@@ -7,10 +9,11 @@ async function getToken() {
|
|
|
7
9
|
token = await _askToken()
|
|
8
10
|
// 把用户提供的 token 写入环境变量,不过只对当前进程有效
|
|
9
11
|
process.env.GITHUB_TOKEN = token
|
|
10
|
-
} else if (!
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
} else if (!validateToken(token)) {
|
|
13
|
+
throw new KupError([
|
|
14
|
+
errorLine('[Kup] [Error] The GitHub token you provide is invalid!'),
|
|
15
|
+
errorLine('[Kup] [Error] It must be a classic (ghp_...) or fine-grained (github_pat_...) PAT.'),
|
|
16
|
+
])
|
|
14
17
|
}
|
|
15
18
|
return token
|
|
16
19
|
}
|
|
@@ -25,22 +28,28 @@ async function _askToken() {
|
|
|
25
28
|
])
|
|
26
29
|
const token = answer.token
|
|
27
30
|
if (!token) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
throw new KupError([
|
|
32
|
+
logLine('[Kup] Aborted!'),
|
|
33
|
+
])
|
|
34
|
+
} else if (!validateToken(token)) {
|
|
35
|
+
throw new KupError([
|
|
36
|
+
errorLine('[Kup] [Error] The GitHub token you provide is invalid!'),
|
|
37
|
+
errorLine('[Kup] [Error] It must be a classic (ghp_...) or fine-grained (github_pat_...) PAT.'),
|
|
38
|
+
])
|
|
34
39
|
}
|
|
35
40
|
return token
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
function validateToken(token) {
|
|
44
|
+
if (typeof token !== 'string' || token.length < 20) return false
|
|
45
|
+
// classic PAT:ghp_...
|
|
46
|
+
// fine-grained PAT:github_pat_...
|
|
47
|
+
const RE = /^(ghp_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+)$/
|
|
48
|
+
return RE.test(token.trim())
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
|
|
44
52
|
export {
|
|
45
53
|
getToken,
|
|
54
|
+
validateToken,
|
|
46
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kup-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A CLI tool to sync local Markdown files to GitHub issues.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"debug": "export KUP_DEBUG_MODE=1",
|
|
11
11
|
"postpublish": "npx npm-mirror-sync",
|
|
12
|
-
"test": "
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"test:unit": "vitest run tests/unit",
|
|
15
|
+
"test:integration": "vitest run tests/integration"
|
|
13
16
|
},
|
|
14
17
|
"repository": {
|
|
15
18
|
"type": "git",
|
|
@@ -27,20 +30,24 @@
|
|
|
27
30
|
"node": ">=18"
|
|
28
31
|
},
|
|
29
32
|
"dependencies": {
|
|
30
|
-
"find-up": "^
|
|
31
|
-
"gh-got": "^
|
|
32
|
-
"inquirer": "^
|
|
33
|
-
"yaml": "^2.
|
|
34
|
-
"yargs": "^
|
|
33
|
+
"find-up": "^8.0.0",
|
|
34
|
+
"gh-got": "^12.0.0",
|
|
35
|
+
"inquirer": "^13.4.1",
|
|
36
|
+
"yaml": "^2.8.3",
|
|
37
|
+
"yargs": "^18.0.0"
|
|
35
38
|
},
|
|
36
39
|
"devDependencies": {
|
|
37
|
-
"
|
|
40
|
+
"execa": "^9.6.1",
|
|
41
|
+
"https-proxy-agent": "^9.0.0",
|
|
42
|
+
"nock": "^14.0.12",
|
|
43
|
+
"vitest": "^4.1.4"
|
|
38
44
|
},
|
|
39
45
|
"files": [
|
|
40
46
|
"*.js",
|
|
41
47
|
"lib/",
|
|
42
48
|
"bin/"
|
|
43
49
|
],
|
|
50
|
+
"packageManager": "pnpm@10.33.0",
|
|
44
51
|
"publishConfig": {
|
|
45
52
|
"registry": "https://registry.npmjs.org/"
|
|
46
53
|
}
|