kup-cli 0.1.1 → 0.1.3

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 CHANGED
@@ -75,9 +75,11 @@ Kup 会把 `file.md` 文件的内容更新到 `foo/bar` 仓库的编号为 `123`
75
75
 
76
76
  1. 调用命令行时指定的 `--repo` 参数。
77
77
  1. Markdown 文件内的 [元数据](https://github.com/cssmagic/kup/issues/1) 的 `repo` 字段。
78
- 1. 当前项目的 `package.json` 文件内的 `kup.repo` 字段。Kup 会从当前目录向上逐级寻找 `package.json` 文件。
78
+ 1. 当前项目的 `package.json` 文件内的 `kup.repo` 字段。Kup 会以 Markdown 文件所在目录为起点逐级向上寻找 `package.json` 文件。
79
+ 1. 如果 `package.json` 文件内没有 `kup.repo` 字段,则 Kup 会尝试根据 `repository` 字段来猜测仓库名(在使用前会向用户确认)。
80
+ 1. 如果以上方式都没有成功,则 Kup 会继续查找同级或上级目录中的 `.git/config` 文件,并尝试根据其中 `remote "origin"` 的 `url` 字段来猜测仓库名(在使用前会向用户确认)。
79
81
 
80
- 如果整个项目的同步目标都是同一个仓库,则可以采用最后一种方式统一指定 `repo` 参数。
82
+ 如果整个项目的同步目标都是同一个仓库,则建议采用 `kup.repo` 字段统一指定 `repo` 参数。
81
83
 
82
84
  ### 如何更方便地指定 `id` 参数?
83
85
 
package/lib/main.js CHANGED
@@ -1,3 +1,5 @@
1
+ import inquirer from 'inquirer'
2
+
1
3
  import { isDebugging } from './util.js'
2
4
  import { KupError, errorLine } from './error.js'
3
5
  import { readTextFile } from './file.js'
@@ -34,7 +36,11 @@ async function main(argv) {
34
36
  }
35
37
 
36
38
  // 通过各种方式获取 repo
37
- // 优先级: 命令行参数 > 文件元数据 > 文件所在项目的 package.json 中的 `kup.repo` 字段
39
+ // 优先级:
40
+ // P1 命令行参数
41
+ // P2 文件元数据
42
+ // P3 package.json 中的 `kup.repo` 字段
43
+ // P4 package.json 中的 `repository` 字段(需确认)
38
44
  let repoReal = ''
39
45
  let repoSource = ''
40
46
  if (repo) {
@@ -44,8 +50,24 @@ async function main(argv) {
44
50
  repoReal = fileInfo.meta.repo
45
51
  repoSource = 'meta'
46
52
  } else {
47
- repoReal = await getRepo(file)
48
- if (repoReal) repoSource = 'package'
53
+ const repoResult = await getRepo(file)
54
+ if (repoResult.needsConfirm) {
55
+ const answer = await inquirer.prompt([
56
+ {
57
+ name: 'useGuessedRepo',
58
+ type: 'confirm',
59
+ message: `Kup guessed the GitHub repo "${ repoResult.repo }" from ${ getRepoSourceLabel(repoResult.source) }, use it?`,
60
+ default: true,
61
+ },
62
+ ])
63
+ if (answer.useGuessedRepo) {
64
+ repoReal = repoResult.repo
65
+ repoSource = repoResult.source
66
+ }
67
+ } else if (repoResult.repo) {
68
+ repoReal = repoResult.repo
69
+ repoSource = repoResult.source
70
+ }
49
71
  }
50
72
  if (!repoReal) {
51
73
  throw new KupError([
@@ -69,6 +91,17 @@ async function main(argv) {
69
91
  }
70
92
  }
71
93
 
94
+ function getRepoSourceLabel(source = '') {
95
+ switch (source) {
96
+ case 'package.repository':
97
+ return 'package.json#repository'
98
+ case 'git.origin':
99
+ return '.git/config remote "origin"'
100
+ default:
101
+ return 'an inferred source'
102
+ }
103
+ }
104
+
72
105
  export {
73
106
  main,
74
107
  }
package/lib/repo.js CHANGED
@@ -1,3 +1,4 @@
1
+ import fs from 'fs/promises'
1
2
  import { findUp } from 'find-up'
2
3
  import path from 'path'
3
4
 
@@ -5,21 +6,73 @@ import { readTextFile } from './file.js'
5
6
  import { validateRepo } from './validate.js'
6
7
 
7
8
  async function getRepo(sourceFile = '') {
8
- // 目前只实现这一种方式
9
- return await _getRepoFromPkgKup(sourceFile)
9
+ const json = await _getPkg(sourceFile)
10
+
11
+ if (json) {
12
+ const packageRepo = _getRepoFromPkgKup(json)
13
+ if (packageRepo.repo) {
14
+ return {
15
+ repo: packageRepo.repo,
16
+ source: 'package',
17
+ needsConfirm: false,
18
+ }
19
+ }
20
+
21
+ if (!packageRepo.hasKupRepoField) {
22
+ const guessedRepo = _getRepoFromPkgRepo(json)
23
+ if (guessedRepo) {
24
+ return {
25
+ repo: guessedRepo,
26
+ source: 'package.repository',
27
+ needsConfirm: true,
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ const guessedRepo = await _getRepoFromGit(sourceFile)
34
+ return {
35
+ repo: guessedRepo,
36
+ source: guessedRepo ? 'git.origin' : '',
37
+ needsConfirm: !!guessedRepo,
38
+ }
10
39
  }
11
40
 
12
- async function _getRepoFromPkgKup(sourceFile = '') {
13
- const json = await _getPkg(sourceFile)
41
+ function _getRepoFromPkgKup(json) {
42
+ const hasKupRepoField = !!json?.kup && Object.prototype.hasOwnProperty.call(json.kup, 'repo')
14
43
  const repo = json?.kup?.repo
15
44
  const result = validateRepo(repo)
16
- return result.status ? repo : ''
45
+ return {
46
+ repo: result.status ? repo : '',
47
+ hasKupRepoField,
48
+ }
17
49
  }
18
- async function _getRepoFromPkgRepo() {
19
- // TODO
50
+ function _getRepoFromPkgRepo(json) {
51
+ const repository = json?.repository
52
+ if (!repository) return ''
53
+
54
+ if (typeof repository === 'string') {
55
+ return normalizeRepositoryToRepo(repository)
56
+ }
57
+
58
+ if (typeof repository === 'object' && repository.url) {
59
+ return normalizeRepositoryToRepo(repository.url)
60
+ }
61
+
62
+ return ''
20
63
  }
21
- async function _getRepoFromGit() {
22
- // TODO
64
+ async function _getRepoFromGit(sourceFile = '') {
65
+ const configFile = await _getGitConfigFile(sourceFile)
66
+ if (!configFile) return ''
67
+
68
+ let config = ''
69
+ try {
70
+ config = await readTextFile(configFile)
71
+ } catch {
72
+ return ''
73
+ }
74
+
75
+ return _getRepoFromGitConfig(config)
23
76
  }
24
77
 
25
78
  async function _getPkg(sourceFile = '') {
@@ -45,9 +98,89 @@ async function _getPkg(sourceFile = '') {
45
98
  return json
46
99
  }
47
100
 
101
+ async function _getGitConfigFile(sourceFile = '') {
102
+ let cwd = sourceFile ? path.resolve(path.dirname(sourceFile)) : process.cwd()
103
+ while (true) {
104
+ const gitPath = path.join(cwd, '.git')
105
+
106
+ try {
107
+ const stats = await fs.stat(gitPath)
108
+ if (stats.isDirectory()) {
109
+ const configFile = path.join(gitPath, 'config')
110
+ try {
111
+ await fs.access(configFile)
112
+ return configFile
113
+ } catch {
114
+ return ''
115
+ }
116
+ }
117
+ return ''
118
+ } catch {
119
+ const parent = path.dirname(cwd)
120
+ if (parent === cwd) return ''
121
+ cwd = parent
122
+ }
123
+ }
124
+ }
125
+
126
+ function _getRepoFromGitConfig(config) {
127
+ if (typeof config !== 'string' || !config.trim()) return ''
128
+
129
+ const lines = config.split(/\r?\n/)
130
+ let inOriginRemote = false
131
+ for (const line of lines) {
132
+ const trimmed = line.trim()
133
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith(';')) continue
134
+
135
+ const sectionMatched = /^\[\s*(.*?)\s*\]$/.exec(trimmed)
136
+ if (sectionMatched) {
137
+ inOriginRemote = /^remote\s+"origin"$/i.test(sectionMatched[1])
138
+ continue
139
+ }
140
+ if (!inOriginRemote) continue
141
+
142
+ const entryMatched = /^([A-Za-z][\w-]*)\s*=\s*(.*?)\s*$/.exec(trimmed)
143
+ if (!entryMatched) continue
144
+
145
+ const key = entryMatched[1].toLowerCase()
146
+ const value = entryMatched[2]
147
+ if (key === 'url') return normalizeRepositoryToRepo(value)
148
+ }
149
+
150
+ return ''
151
+ }
152
+
153
+ function normalizeRepositoryToRepo(repository) {
154
+ if (typeof repository !== 'string') return ''
155
+
156
+ const value = repository.trim()
157
+ if (!value) return ''
158
+
159
+ const candidates = [
160
+ /^github:([\w.-]+\/[\w.-]+)$/i,
161
+ /^(?:git\+)?https?:\/\/github\.com\/([\w.-]+\/[\w.-]+?)(?:\.git)?(?:\/)?$/i,
162
+ /^git@github\.com:([\w.-]+\/[\w.-]+?)(?:\.git)?$/i,
163
+ /^ssh:\/\/git@github\.com\/([\w.-]+\/[\w.-]+?)(?:\.git)?(?:\/)?$/i,
164
+ /^([\w.-]+\/[\w.-]+)$/,
165
+ ]
166
+
167
+ for (const pattern of candidates) {
168
+ const matched = pattern.exec(value)
169
+ if (!matched) continue
170
+
171
+ const repo = matched[1]
172
+ const result = validateRepo(repo)
173
+ if (result.status) return repo
174
+ }
175
+
176
+ return ''
177
+ }
178
+
48
179
  export {
49
180
  getRepo,
50
- // __getRepoFromPkgKup: _getRepoFromPkgKup,
51
- // __getRepoFromPkgRepo: _getRepoFromPkgRepo,
52
- // __getRepoFromGit: _getRepoFromGit,
181
+ _getRepoFromPkgKup as __getRepoFromPkgKup,
182
+ _getRepoFromPkgRepo as __getRepoFromPkgRepo,
183
+ _getRepoFromGit as __getRepoFromGit,
184
+ _getRepoFromGitConfig as __getRepoFromGitConfig,
185
+ normalizeRepositoryToRepo as __normalizeRepositoryToRepo,
53
186
  }
package/lib/validate.js CHANGED
@@ -36,7 +36,7 @@ function validateId(id) {
36
36
  }
37
37
  }
38
38
  function validateRepo(repo) {
39
- const REPO_PATTERN = /^[\w\-]+\/[\w\-]+$/
39
+ const REPO_PATTERN = /^[\w.-]+\/[\w.-]+$/
40
40
  let errorMsg = ''
41
41
  if (!repo) {
42
42
  errorMsg = '`repo` must be a string!'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kup-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "A CLI tool to sync local Markdown files to GitHub issues.",
5
5
  "type": "module",
6
6
  "bin": {