markdown-magic 3.0.6 → 3.0.8
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/lib/process-contents.js +25 -4
- package/lib/transforms/{code.js → code/index.js} +45 -12
- package/lib/transforms/code/resolve-github-file.js +362 -0
- package/lib/transforms/code/resolve-github-file.test.js +32 -0
- package/lib/utils/fs.js +6 -1
- package/lib/utils/remoteRequest.js +4 -3
- package/lib/utils/text.js +8 -0
- package/package.json +1 -1
package/lib/process-contents.js
CHANGED
|
@@ -150,6 +150,7 @@ async function processContents(text, config) {
|
|
|
150
150
|
// console.log('config', config)
|
|
151
151
|
|
|
152
152
|
// console.log('returnedContent', returnedContent)
|
|
153
|
+
// process.exit(1)
|
|
153
154
|
|
|
154
155
|
if (returnedContent) {
|
|
155
156
|
tempContent = returnedContent
|
|
@@ -166,6 +167,10 @@ async function processContents(text, config) {
|
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
}, md, afterMiddleware)
|
|
170
|
+
/*
|
|
171
|
+
console.log('afterContent', afterContent)
|
|
172
|
+
process.exit(1)
|
|
173
|
+
/** */
|
|
169
174
|
|
|
170
175
|
if (debug) {
|
|
171
176
|
console.log('afterContent', afterContent)
|
|
@@ -181,7 +186,11 @@ async function processContents(text, config) {
|
|
|
181
186
|
// console.log('formattedNewContent', formattedNewContent)
|
|
182
187
|
/* Remove any conflicting imported comments */
|
|
183
188
|
const fix = removeConflictingComments(formattedNewContent, COMMENT_OPEN_REGEX, COMMENT_CLOSE_REGEX)
|
|
184
|
-
|
|
189
|
+
/*
|
|
190
|
+
console.log('fix')
|
|
191
|
+
deepLog(fix)
|
|
192
|
+
process.exit(1)
|
|
193
|
+
/** */
|
|
185
194
|
if (options.removeComments) {
|
|
186
195
|
// console.log('removeComments', options.removeComments)
|
|
187
196
|
}
|
|
@@ -194,10 +203,19 @@ async function processContents(text, config) {
|
|
|
194
203
|
const indent = indentString(fix, preserveIndent)
|
|
195
204
|
const newCont = `${openTag}${indent}${closeTag}`
|
|
196
205
|
/* Replace original contents */
|
|
197
|
-
|
|
206
|
+
// Must use replacer function because strings get coearced to regex or something
|
|
207
|
+
const newContents = md.replace(block.value, () => newCont)
|
|
208
|
+
/*
|
|
209
|
+
deepLog(newContents)
|
|
210
|
+
process.exit(1)
|
|
211
|
+
/** */
|
|
198
212
|
return Promise.resolve(newContents)
|
|
199
213
|
}, Promise.resolve(text))
|
|
200
214
|
|
|
215
|
+
// console.log('updatedContents')
|
|
216
|
+
// deepLog(updatedContents)
|
|
217
|
+
// process.exit(1)
|
|
218
|
+
|
|
201
219
|
// if (debug) {
|
|
202
220
|
// console.log('Output Markdown')
|
|
203
221
|
// console.log(updatedContents)
|
|
@@ -245,8 +263,11 @@ async function processContents(text, config) {
|
|
|
245
263
|
originalContents: text,
|
|
246
264
|
updatedContents,
|
|
247
265
|
}
|
|
248
|
-
|
|
249
|
-
|
|
266
|
+
/*
|
|
267
|
+
console.log('result')
|
|
268
|
+
deepLog(result)
|
|
269
|
+
process.exit(1)
|
|
270
|
+
/** */
|
|
250
271
|
return result
|
|
251
272
|
}
|
|
252
273
|
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
|
-
const remoteRequest = require('
|
|
4
|
-
const { isLocalPath } = require('
|
|
5
|
-
const { deepLog } = require('
|
|
6
|
-
const { getLineCount, getTextBetweenLines } = require('
|
|
3
|
+
const remoteRequest = require('../../utils/remoteRequest')
|
|
4
|
+
const { isLocalPath } = require('../../utils/fs')
|
|
5
|
+
const { deepLog } = require('../../utils/logs')
|
|
6
|
+
const { getLineCount, getTextBetweenLines } = require('../../utils/text')
|
|
7
|
+
const { resolveGithubContents, isGithubLink } = require('./resolve-github-file')
|
|
8
|
+
|
|
9
|
+
const GITHUB_LINK = /https:\/\/github\.com\/([^/\s]*)\/([^/\s]*)\/blob\//
|
|
10
|
+
const GIST_LINK = /https:\/\/gist\.github\.com\/([^/\s]*)\/([^/\s]*)(\/)?/
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* Options for specifying source code to include in documentation.
|
|
@@ -26,19 +30,26 @@ const { getLineCount, getTextBetweenLines } = require('../utils/text')
|
|
|
26
30
|
```
|
|
27
31
|
*/
|
|
28
32
|
|
|
33
|
+
|
|
29
34
|
// TODO code sections
|
|
30
35
|
// https://github.com/linear/linear/blob/94af540244864fbe466fb933256278e04e87513e/docs/transforms/code-section.js
|
|
31
36
|
// https://github.com/linear/linear/blob/bc39d23af232f9fdbe7df458b0aaa9554ca83c57/packages/sdk/src/_tests/readme.test.ts#L133-L140
|
|
32
37
|
// usage https://github.com/linear/linear/blame/93981d3a3db571e2f8efdce9f5271ea678941c43/packages/sdk/README.md#L1
|
|
33
38
|
|
|
34
|
-
module.exports = function CODE(api) {
|
|
39
|
+
module.exports = async function CODE(api) {
|
|
35
40
|
const { content, srcPath } = api
|
|
36
41
|
/** @type {CodeTransformOptions} */
|
|
37
42
|
const options = api.options || {}
|
|
38
43
|
// console.log('CODE API', api)
|
|
39
44
|
// process.exit(1)
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
const {
|
|
46
|
+
id,
|
|
47
|
+
lines,
|
|
48
|
+
isPrivate,
|
|
49
|
+
accessToken
|
|
50
|
+
} = options
|
|
51
|
+
|
|
52
|
+
let src = options.src
|
|
42
53
|
const originalContent = content
|
|
43
54
|
let code
|
|
44
55
|
let syntax = options.syntax
|
|
@@ -61,12 +72,34 @@ module.exports = function CODE(api) {
|
|
|
61
72
|
syntax = path.extname(codeFilePath).replace(/^./, '')
|
|
62
73
|
}
|
|
63
74
|
} else {
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
|
|
75
|
+
/* Automatically get raw code files from github */
|
|
76
|
+
// Convert https://github.com/DavidWells/markdown-magic/blob/master/package.json
|
|
77
|
+
// to https://raw.githubusercontent.com/DavidWells/markdown-magic/master/package.json
|
|
78
|
+
|
|
79
|
+
if (src.match(GITHUB_LINK)) {
|
|
80
|
+
src = src.replace(GITHUB_LINK, 'https://raw.githubusercontent.com/$1/$2/')
|
|
81
|
+
}
|
|
82
|
+
/* Automatically get raw code files from gist... needs api call.... */
|
|
83
|
+
// https://gist.github.com/DavidWells/7d2e0e1bc78f4ac59a123ddf8b74932d
|
|
84
|
+
// https://gist.githubusercontent.com/DavidWells/7d2e0e1bc78f4ac59a123ddf8b74932d/raw/0808a83de7f07c931fb81ed691c1d6bbafad29d1/aligning-images.md
|
|
85
|
+
|
|
86
|
+
let remoteContent
|
|
87
|
+
// Try initial remote request if public url
|
|
88
|
+
if (!isPrivate) {
|
|
89
|
+
remoteContent = remoteRequest(src)
|
|
90
|
+
}
|
|
67
91
|
if (!remoteContent) {
|
|
68
|
-
|
|
69
|
-
|
|
92
|
+
if (isGithubLink(src)) {
|
|
93
|
+
remoteContent = await resolveGithubContents({
|
|
94
|
+
repoFilePath: src,
|
|
95
|
+
accessToken,
|
|
96
|
+
// debug: true
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
if (!remoteContent) {
|
|
100
|
+
console.log(`WARNING: ${src} URL NOT FOUND or internet connection is off or no access to remove URL`)
|
|
101
|
+
return originalContent
|
|
102
|
+
}
|
|
70
103
|
}
|
|
71
104
|
code = remoteContent
|
|
72
105
|
syntax = path.extname(src).replace(/^./, '')
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
const https = require('https')
|
|
2
|
+
const { exec } = require('child_process')
|
|
3
|
+
const { getTextBetweenLines } = require('../../utils/text')
|
|
4
|
+
|
|
5
|
+
const VALID_SLUG_REGEX = /^[A-Z-a-z0-9_-]*$/
|
|
6
|
+
const VALID_FILE_REGEX = /^[^;]*$/
|
|
7
|
+
const GITHUB_LINK_REGEX = /^(?:https:\/\/)?github\.com\/([^/\s]*)\/([^/\s]*)\/blob\/([^/\s]*)\/([^\s]*)/
|
|
8
|
+
const GITHUB_RAW_LINK_REGEX = /^(?:https:\/\/)?raw\.githubusercontent\.com\/([^/\s]*)\/([^/\s]*)\/([^/\s]*)\/([^\s]*)/
|
|
9
|
+
|
|
10
|
+
function isGithubLink(str = '') {
|
|
11
|
+
return isGithubRepoLink(str) || isGithubRawLink(str)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isGithubRepoLink(str = '') {
|
|
15
|
+
return GITHUB_LINK_REGEX.test(str)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isGithubRawLink(str = '') {
|
|
19
|
+
return GITHUB_RAW_LINK_REGEX.test(str)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function convertLinkToRaw(link) {
|
|
23
|
+
if (!isGithubRepoLink(link)) return link
|
|
24
|
+
return link.replace(GITHUB_LINK_REGEX, 'https://raw.githubusercontent.com/$1/$2/$3/$4')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveGithubDetails(repoFilePath) {
|
|
28
|
+
let parts
|
|
29
|
+
if (isGithubRepoLink(repoFilePath)) {
|
|
30
|
+
parts = repoFilePath.match(GITHUB_LINK_REGEX)
|
|
31
|
+
}
|
|
32
|
+
if (isGithubRawLink(repoFilePath)) {
|
|
33
|
+
parts = repoFilePath.match(GITHUB_RAW_LINK_REGEX)
|
|
34
|
+
}
|
|
35
|
+
if (!parts) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
const [ _match, repoOwner, repoName, branchOrRef, filePath ] = parts
|
|
39
|
+
const [ filePathStart, hash ] = filePath.split('#')
|
|
40
|
+
const result = {
|
|
41
|
+
repoOwner,
|
|
42
|
+
repoName,
|
|
43
|
+
filePath: filePathStart,
|
|
44
|
+
}
|
|
45
|
+
if (isGitHash(branchOrRef)) {
|
|
46
|
+
result.ref = branchOrRef
|
|
47
|
+
} else {
|
|
48
|
+
result.branch = branchOrRef
|
|
49
|
+
}
|
|
50
|
+
if (hash) {
|
|
51
|
+
const range = parseLineRange(`#${hash}`)
|
|
52
|
+
if (range) {
|
|
53
|
+
result.range = range
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolves the contents of a file from a GitHub repository.
|
|
61
|
+
*
|
|
62
|
+
* @param {Object} options - The options for resolving the GitHub contents.
|
|
63
|
+
* @param {string} options.repoFilePath - The file path in the GitHub repository.
|
|
64
|
+
* @param {string} [options.accessToken] - The access token for authenticating with GitHub (optional).
|
|
65
|
+
* @param {boolean} [options.debug = false] - Whether to enable debug logging (optional).
|
|
66
|
+
* @returns {Promise<string>} - A promise that resolves to the contents of the file.
|
|
67
|
+
* @throws {Error} - If the GitHub link is invalid or if the file fetch fails.
|
|
68
|
+
*/
|
|
69
|
+
async function resolveGithubContents({
|
|
70
|
+
repoFilePath,
|
|
71
|
+
accessToken,
|
|
72
|
+
debug = false
|
|
73
|
+
}) {
|
|
74
|
+
const token = resolveAccessToken(accessToken)
|
|
75
|
+
const logger = (debug) ? console.log : () => {}
|
|
76
|
+
const githubDetails = resolveGithubDetails(repoFilePath)
|
|
77
|
+
if (!githubDetails) {
|
|
78
|
+
throw new Error(`Invalid github link. "${repoFilePath}" is not a valid github link`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
logger(`Github File Details "${repoFilePath}"`, githubDetails)
|
|
82
|
+
|
|
83
|
+
const payload = {
|
|
84
|
+
...githubDetails,
|
|
85
|
+
accessToken: token
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let errs = []
|
|
89
|
+
|
|
90
|
+
/* Try raw request first */
|
|
91
|
+
try {
|
|
92
|
+
const fileContent = await getGitHubFileContentsRaw(payload)
|
|
93
|
+
logger(`✅ GitHub file resolved via raw GET`)
|
|
94
|
+
return returnCode(fileContent, githubDetails.range)
|
|
95
|
+
} catch (err) {
|
|
96
|
+
logger('❌ Unable to resolve GitHub raw content')
|
|
97
|
+
errs.push(err)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Then try Github CLI or GitHub API */
|
|
101
|
+
const githubFetcher = (!token) ? getGitHubFileContentsCli : getGitHubFileContentsApi
|
|
102
|
+
try {
|
|
103
|
+
const fileContent = await githubFetcher(payload)
|
|
104
|
+
logger(`✅ GitHub file resolved via ${githubFetcher.name}`)
|
|
105
|
+
return returnCode(fileContent, githubDetails.range)
|
|
106
|
+
} catch (err) {
|
|
107
|
+
logger(`❌ Unable to resolve GitHub file via ${githubFetcher.name}`)
|
|
108
|
+
errs.push(err)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Then try API */
|
|
112
|
+
try {
|
|
113
|
+
const fileContent = await getGitHubFileContentsApi(payload)
|
|
114
|
+
logger(`✅ GitHub file resolved via ${getGitHubFileContentsApi.name}`)
|
|
115
|
+
return returnCode(fileContent, githubDetails.range)
|
|
116
|
+
} catch (err) {
|
|
117
|
+
logger(`❌ Unable to resolve GitHub file via ${githubFetcher.name}`)
|
|
118
|
+
errs.push(err)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
throw new Error(`Failed to fetch GitHub file "${repoFilePath}". \n${errs.forEach(err => err.message)}`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function returnCode(fileContent, lines) {
|
|
125
|
+
if (!lines) return fileContent
|
|
126
|
+
const [startLine, endLine] =lines
|
|
127
|
+
return getTextBetweenLines(fileContent, startLine, endLine)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Retrieves the contents of a file from a GitHub repository using the GitHub CLI.
|
|
132
|
+
*
|
|
133
|
+
* @param {Object} options - The options for retrieving the file contents.
|
|
134
|
+
* @param {string} options.repoOwner - The owner of the GitHub repository.
|
|
135
|
+
* @param {string} options.repoName - The name of the GitHub repository.
|
|
136
|
+
* @param {string} options.filePath - The path to the file in the repository.
|
|
137
|
+
* @param {string} [options.branch] - The branch name of the repository.
|
|
138
|
+
* @param {string} [options.ref] - The ref of the repository.
|
|
139
|
+
* @returns {Promise<string>} A promise that resolves with the decoded content of the file.
|
|
140
|
+
* @throws {Error} If there is an error retrieving the file contents.
|
|
141
|
+
*/
|
|
142
|
+
async function getGitHubFileContentsCli(options) {
|
|
143
|
+
validateInputs(options)
|
|
144
|
+
const {
|
|
145
|
+
repoOwner,
|
|
146
|
+
repoName,
|
|
147
|
+
filePath,
|
|
148
|
+
branch,
|
|
149
|
+
ref,
|
|
150
|
+
} = options
|
|
151
|
+
|
|
152
|
+
let flags = ''
|
|
153
|
+
if (ref) {
|
|
154
|
+
flags = `?ref=${ref}`
|
|
155
|
+
}
|
|
156
|
+
if (branch) {
|
|
157
|
+
flags = `?ref=${branch}`
|
|
158
|
+
}
|
|
159
|
+
const command = `gh api repos/${repoOwner}/${repoName}/contents/${filePath}${flags}`
|
|
160
|
+
/*
|
|
161
|
+
console.log('command', command)
|
|
162
|
+
/** */
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
exec(command, (error, stdout, stderr) => {
|
|
165
|
+
if (error) {
|
|
166
|
+
return reject(error)
|
|
167
|
+
}
|
|
168
|
+
const fileContent = JSON.parse(stdout).content;
|
|
169
|
+
const decodedContent = decode(fileContent)
|
|
170
|
+
return resolve(decodedContent)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Retrieves the contents of a file from a GitHub repository via the github API
|
|
177
|
+
*
|
|
178
|
+
* @param {Object} options - The options for retrieving the file contents.
|
|
179
|
+
* @param {string} options.repoOwner - The owner of the GitHub repository.
|
|
180
|
+
* @param {string} options.repoName - The name of the GitHub repository.
|
|
181
|
+
* @param {string} options.filePath - The path to the file in the repository.
|
|
182
|
+
* @param {string} [options.branch] - The branch name to fetch the file from. If not provided, the default branch will be used.
|
|
183
|
+
* @param {string} [options.ref] - The ref (commit SHA or branch name) to fetch the file from. If provided, it takes precedence over the branch.
|
|
184
|
+
* @param {string} [options.accessToken] - The access token for authenticating the request (optional).
|
|
185
|
+
* @returns {Promise<string>} A promise that resolves with the decoded contents of the file.
|
|
186
|
+
* @throws {Error} If the file retrieval fails or the response status code is not 200.
|
|
187
|
+
*/
|
|
188
|
+
function getGitHubFileContentsApi(options) {
|
|
189
|
+
validateInputs(options)
|
|
190
|
+
|
|
191
|
+
const {
|
|
192
|
+
repoOwner,
|
|
193
|
+
repoName,
|
|
194
|
+
filePath,
|
|
195
|
+
branch,
|
|
196
|
+
ref,
|
|
197
|
+
accessToken
|
|
198
|
+
} = options
|
|
199
|
+
|
|
200
|
+
let flags = ''
|
|
201
|
+
if (ref) {
|
|
202
|
+
flags = `?ref=${ref}`
|
|
203
|
+
}
|
|
204
|
+
if (branch) {
|
|
205
|
+
flags = `?branch=${branch}`
|
|
206
|
+
}
|
|
207
|
+
const apiEndpoint = `/repos/${repoOwner}/${repoName}/contents/${filePath}${flags}`
|
|
208
|
+
/*
|
|
209
|
+
// console.log('apiEndpoint', apiEndpoint)
|
|
210
|
+
/** */
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
const options = {
|
|
213
|
+
hostname: 'api.github.com',
|
|
214
|
+
path: apiEndpoint,
|
|
215
|
+
method: 'GET',
|
|
216
|
+
headers: {
|
|
217
|
+
'User-Agent': 'Node.js',
|
|
218
|
+
...(accessToken) ? { 'Authorization': `token ${accessToken}` } : {},
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/*
|
|
222
|
+
console.log('getGitHubFileContentsApi options', options)
|
|
223
|
+
/** */
|
|
224
|
+
const req = https.request(options, (res) => {
|
|
225
|
+
let data = ''
|
|
226
|
+
res.on('data', (chunk) => {
|
|
227
|
+
data += chunk
|
|
228
|
+
})
|
|
229
|
+
res.on('end', () => {
|
|
230
|
+
if (res.statusCode === 200) {
|
|
231
|
+
const fileContent = JSON.parse(data).content
|
|
232
|
+
const decodedContent = decode(fileContent)
|
|
233
|
+
resolve(decodedContent)
|
|
234
|
+
} else {
|
|
235
|
+
reject(new Error(`Failed to fetch file. Status code: ${res.statusCode}`))
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
req.on('error', (error) => {
|
|
241
|
+
reject(error)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
req.end()
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function getGitHubFileContentsRaw(options) {
|
|
249
|
+
validateInputs(options)
|
|
250
|
+
|
|
251
|
+
const {
|
|
252
|
+
repoOwner,
|
|
253
|
+
repoName,
|
|
254
|
+
filePath,
|
|
255
|
+
branch,
|
|
256
|
+
accessToken
|
|
257
|
+
} = options
|
|
258
|
+
|
|
259
|
+
const [ _filePath, hash ] = filePath.split('#')
|
|
260
|
+
return new Promise((resolve, reject) => {
|
|
261
|
+
const options = {
|
|
262
|
+
hostname: 'raw.githubusercontent.com',
|
|
263
|
+
path: `/${repoOwner}/${repoName}/${branch}/${_filePath}`,
|
|
264
|
+
method: 'GET',
|
|
265
|
+
headers: {
|
|
266
|
+
'User-Agent': 'Node.js',
|
|
267
|
+
...(accessToken) ? { 'Authorization': `token ${accessToken}` } : {},
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/*
|
|
271
|
+
console.log('getGitHubFileContentsRaw options', options)
|
|
272
|
+
/** */
|
|
273
|
+
const req = https.request(options, (res) => {
|
|
274
|
+
let data = ''
|
|
275
|
+
res.on('data', chunk => {
|
|
276
|
+
data += chunk
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
res.on('end', () => {
|
|
280
|
+
if (res.statusCode === 200) {
|
|
281
|
+
resolve(data)
|
|
282
|
+
} else {
|
|
283
|
+
reject(new Error(`Failed to fetch file. Status code: ${res.statusCode}`))
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
req.on('error', error => {
|
|
289
|
+
reject(error)
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
req.end()
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Validates the inputs for a repository operation.
|
|
298
|
+
*
|
|
299
|
+
* @param {Object} inputs - The inputs for the repository operation.
|
|
300
|
+
* @param {string} inputs.repoOwner - The owner of the repository.
|
|
301
|
+
* @param {string} inputs.repoName - The name of the repository.
|
|
302
|
+
* @param {string} inputs.filePath - The file path.
|
|
303
|
+
* @param {string} [inputs.branch] - The branch name.
|
|
304
|
+
* @param {string} [inputs.ref] - The Git reference.
|
|
305
|
+
* @throws {Error} If any of the inputs are invalid.
|
|
306
|
+
*/
|
|
307
|
+
function validateInputs({
|
|
308
|
+
repoOwner,
|
|
309
|
+
repoName,
|
|
310
|
+
filePath,
|
|
311
|
+
branch,
|
|
312
|
+
ref,
|
|
313
|
+
}) {
|
|
314
|
+
if (!VALID_SLUG_REGEX.test(repoOwner)) {
|
|
315
|
+
throw new Error(`Invalid repoOwner "${repoOwner}"`)
|
|
316
|
+
}
|
|
317
|
+
if (!VALID_SLUG_REGEX.test(repoName)) {
|
|
318
|
+
throw new Error(`Invalid repoName "${repoName}"`)
|
|
319
|
+
}
|
|
320
|
+
if (!VALID_FILE_REGEX.test(filePath)) {
|
|
321
|
+
throw new Error(`Invalid filePath "${filePath}"`)
|
|
322
|
+
}
|
|
323
|
+
if (branch && !VALID_FILE_REGEX.test(branch)) {
|
|
324
|
+
throw new Error(`Invalid branch "${branch}"`)
|
|
325
|
+
}
|
|
326
|
+
if (ref && !isGitHash(ref)) {
|
|
327
|
+
throw new Error(`Invalid ref "${ref}"`)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function resolveAccessToken(accessToken) {
|
|
332
|
+
if (typeof accessToken === 'string' && accessToken.match(/process\.env\./)) {
|
|
333
|
+
return process.env[accessToken.replace('process.env.', '')]
|
|
334
|
+
}
|
|
335
|
+
return accessToken || process.env.GITHUB_ACCESS_TOKEN
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function decode(fileContent) {
|
|
339
|
+
return Buffer.from(fileContent, 'base64').toString('utf-8')
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function isGitHash(str) {
|
|
343
|
+
// Regular expression to match Git hashes
|
|
344
|
+
const gitHashRegex = /^[vV]?[0-9a-fA-F]{40}$/
|
|
345
|
+
return gitHashRegex.test(str)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function parseLineRange(lineRangeString) {
|
|
349
|
+
const matches = lineRangeString.match(/#L(\d+)-L(\d+)/)
|
|
350
|
+
if (!matches) return
|
|
351
|
+
const startLine = parseInt(matches[1])
|
|
352
|
+
const endLine = parseInt(matches[2])
|
|
353
|
+
return [startLine, endLine]
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
module.exports = {
|
|
357
|
+
isGithubLink,
|
|
358
|
+
isGithubRawLink,
|
|
359
|
+
getGitHubFileContentsRaw,
|
|
360
|
+
resolveGithubDetails,
|
|
361
|
+
resolveGithubContents,
|
|
362
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const { resolveGithubContents, getGitHubFileContentsRaw } = require('./resolve-github-file')
|
|
2
|
+
|
|
3
|
+
let repoFilePath
|
|
4
|
+
repoFilePath = 'https://github.com/DavidWells/markdown-magic/blob/master/package.json'
|
|
5
|
+
// repoFilePath = 'https://github.com/DavidWells/notes/blob/master/cognito.md'
|
|
6
|
+
// repoFilePath = 'github.com/DavidWells/notes/blob/master/cognito.md#L1-L5'
|
|
7
|
+
repoFilePath = 'github.com/DavidWells/notes/blob/master/cognito.md'
|
|
8
|
+
// repoFilePath = 'https://raw.githubusercontent.com/DavidWells/notes/master/cognito.md'
|
|
9
|
+
// repoFilePath = 'raw.githubusercontent.com/DavidWells/notes/master/cognito.md'
|
|
10
|
+
// repoFilePath = 'https://github.com/reapit/foundations/blob/53b2be65ea69d5f1338dbea6e5028c7599d78cf7/packages/connect-session/src/browser/index.ts#L125-L163'
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
resolveGithubContents({
|
|
14
|
+
repoFilePath,
|
|
15
|
+
debug: true,
|
|
16
|
+
//accessToken: process.env.GITHUB_LAST_EDITED_TOKEN
|
|
17
|
+
})
|
|
18
|
+
.then(console.log)
|
|
19
|
+
.catch(console.error);
|
|
20
|
+
/** */
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
getGitHubFileContentsRaw({
|
|
24
|
+
repoOwner: 'DavidWells',
|
|
25
|
+
repoName: 'notes',
|
|
26
|
+
filePath: 'cognito.md',
|
|
27
|
+
branch: 'master',
|
|
28
|
+
accessToken: process.env.GITHUB_LAST_EDITED_TOKEN
|
|
29
|
+
})
|
|
30
|
+
.then(console.log)
|
|
31
|
+
.catch(console.error);
|
|
32
|
+
/** */
|
package/lib/utils/fs.js
CHANGED
|
@@ -2,7 +2,7 @@ const fs = require('fs').promises
|
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const globrex = require('globrex')
|
|
4
4
|
const isGlob = require('is-glob')
|
|
5
|
-
const
|
|
5
|
+
const _isLocalPath = require('is-local-path')
|
|
6
6
|
const { REGEX_REGEX, escapeRegexString } = require('./regex')
|
|
7
7
|
const { dirname, resolve, join } = require('path')
|
|
8
8
|
const { readdir, stat, readFile } = fs
|
|
@@ -165,6 +165,11 @@ function depth(string) {
|
|
|
165
165
|
return path.normalize(string).split(path.sep).length - 1;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
function isLocalPath(filePath) {
|
|
169
|
+
if (filePath.startsWith('github.com/') || filePath.startsWith('raw.githubusercontent.com/')) return false
|
|
170
|
+
return _isLocalPath(filePath)
|
|
171
|
+
}
|
|
172
|
+
|
|
168
173
|
module.exports = {
|
|
169
174
|
isLocalPath,
|
|
170
175
|
writeFile,
|
|
@@ -2,13 +2,14 @@ const request = require('sync-request')
|
|
|
2
2
|
|
|
3
3
|
module.exports = function remoteRequest(url) {
|
|
4
4
|
let body
|
|
5
|
+
const finalUrl = (url.match(/^https?:\/\//)) ? url : `https://${url}`
|
|
5
6
|
try {
|
|
6
7
|
// @ts-expect-error
|
|
7
|
-
const res = request('GET',
|
|
8
|
+
const res = request('GET', finalUrl)
|
|
8
9
|
body = res.getBody('utf8')
|
|
9
10
|
} catch (e) {
|
|
10
|
-
console.log(`WARNING: REMOTE URL ${
|
|
11
|
-
console.log(e.message)
|
|
11
|
+
console.log(`WARNING: REMOTE URL ${finalUrl} NOT FOUND`)
|
|
12
|
+
console.log((e.message || '').split('\n')[0])
|
|
12
13
|
}
|
|
13
14
|
return body
|
|
14
15
|
}
|
package/lib/utils/text.js
CHANGED
|
@@ -60,6 +60,14 @@ function replaceTextBetweenChars(str = '', start, end, newStr) {
|
|
|
60
60
|
return str.substring(0, start) + newStr + str.substring(end)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Retrieves the text content between the specified start and end lines.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} content - The content to extract text from.
|
|
67
|
+
* @param {number} startLine - The line number where the extraction should start.
|
|
68
|
+
* @param {number} endLine - The line number where the extraction should end.
|
|
69
|
+
* @returns {string|undefined} - The extracted text content, or undefined if both startLine and endLine are not defined.
|
|
70
|
+
*/
|
|
63
71
|
function getTextBetweenLines(content, startLine, endLine) {
|
|
64
72
|
const startDefined = typeof startLine !== 'undefined'
|
|
65
73
|
const endDefined = typeof endLine !== 'undefined'
|