|
10 | 10 | // Import LIBS |
11 | 11 | const fs = require('fs'), // to read/write files |
12 | 12 | path = require('path'), // to manipulate paths |
13 | | - ssri = require('ssri') // to generate SHA-256 hashes |
| 13 | + bumpUtilsFilePath = path.join(__dirname, '.cache/bump-utils.mjs') |
| 14 | + fs.mkdirSync(path.dirname(bumpUtilsFilePath), { recursive: true }) |
| 15 | + fs.writeFileSync(bumpUtilsFilePath, Buffer.from(await (await fetch( |
| 16 | + 'https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@latest/utils/bump/bump-utils.mjs')).arrayBuffer())) |
| 17 | + const bump = await import(require('url').pathToFileURL(bumpUtilsFilePath)) |
14 | 18 |
|
15 | 19 | // Init CACHE vars |
16 | 20 | const cacheMode = process.argv.includes('--cache'), |
17 | 21 | cacheFilePath = path.join(__dirname, '.cache/userJSpaths.json') |
18 | 22 |
|
19 | | - // Init UI COLORS |
20 | | - const nc = '\x1b[0m', // no color |
21 | | - dg = '\x1b[38;5;243m', // dim gray |
22 | | - bw = '\x1b[1;97m', // bright white |
23 | | - by = '\x1b[1;33m', // bright yellow |
24 | | - bg = '\x1b[1;92m', // bright green |
25 | | - br = '\x1b[1;91m' // bright red |
26 | | - |
27 | 23 | // Init REGEX |
28 | 24 | const regEx = { |
29 | 25 | hash: { commit: /(@|\?v=)([^/#]+)/, sri: /[^#]+$/ }, |
30 | 26 | resName: /[^/]+\/(?:dist)?\/?[^/]+\.js(?=[?#]|$)/, |
31 | 27 | jsURL: /^\/\/ @require\s+(https:\/\/cdn\.jsdelivr\.net\/gh\/.+)$/ |
32 | 28 | } |
33 | 29 |
|
34 | | - // Define FUNCTIONS |
35 | | - |
36 | | - const log = {}; |
37 | | - ['hash', 'info', 'working', 'success', 'error'].forEach(lvl => log[lvl] = function(msg) { |
38 | | - const logColor = lvl == 'hash' ? dg : lvl == 'info' ? bw : lvl == 'working' ? by : lvl == 'success' ? bg : br, |
39 | | - formattedMsg = logColor + ( log.endedWithLineBreak ? msg.trimStart() : msg ) + nc |
40 | | - console.log(formattedMsg) ; log.endedWithLineBreak = msg.toString().endsWith('\n') |
41 | | - }) |
42 | | - |
43 | | - function bumpUserJSver(userJSfilePath) { |
44 | | - const date = new Date(), |
45 | | - today = `${date.getFullYear()}.${date.getMonth() +1}.${date.getDate()}`, // YYYY.M.D format |
46 | | - reVersion = /(@version\s+)([\d.]+)/, |
47 | | - userJScontent = fs.readFileSync(userJSfilePath, 'utf-8'), |
48 | | - currentVer = userJScontent.match(reVersion)[2] |
49 | | - let newVer |
50 | | - if (currentVer.startsWith(today)) { // bump sub-ver |
51 | | - const verParts = currentVer.split('.'), |
52 | | - subVer = verParts.length > 3 ? parseInt(verParts[3], 10) +1 : 1 |
53 | | - newVer = `${today}.${subVer}` |
54 | | - } else // bump to today |
55 | | - newVer = today |
56 | | - fs.writeFileSync(userJSfilePath, userJScontent.replace(reVersion, `$1${newVer}`), 'utf-8') |
57 | | - console.log(`Updated: ${bw}v${currentVer}${nc} → ${bg}v${newVer}${nc}`) |
58 | | - } |
59 | | - |
60 | | - function fetchData(url) { |
61 | | - if (typeof fetch == 'undefined') // polyfill for Node.js < v21 |
62 | | - return new Promise((resolve, reject) => { |
63 | | - try { // to use http or https module |
64 | | - const protocol = url.match(/^([^:]+):\/\//)[1] |
65 | | - if (!/^https?$/.test(protocol)) reject(new Error('Invalid fetchData() URL.')) |
66 | | - require(protocol).get(url, resp => { |
67 | | - let rawData = '' |
68 | | - resp.on('data', chunk => rawData += chunk) |
69 | | - resp.on('end', () => resolve({ json: () => JSON.parse(rawData) })) |
70 | | - }).on('error', err => reject(new Error(err.message))) |
71 | | - } catch (err) { reject(new Error('Environment not supported.')) |
72 | | - }}) |
73 | | - else // use fetch() from Node.js v21+ |
74 | | - return fetch(url) |
75 | | - } |
76 | | - |
77 | | - async function findUserJS(dir = findUserJS.monorepoRoot) { |
78 | | - const userJSfiles = [] |
79 | | - if (!dir && !findUserJS.monorepoRoot) { // no arg passed, init monorepo root |
80 | | - dir = __dirname |
81 | | - while (!fs.existsSync(path.join(dir, 'package.json'))) |
82 | | - dir = path.dirname(dir) // traverse up to closest manifest dir |
83 | | - findUserJS.monorepoRoot = dir |
84 | | - } |
85 | | - dir = path.resolve(dir) |
86 | | - fs.readdirSync(dir).forEach(async entry => { |
87 | | - if (/^(?:\.|node_modules$)/.test(entry)) return |
88 | | - const entryPath = path.join(dir, entry) |
89 | | - if (fs.statSync(entryPath).isDirectory()) // recursively search subdirs |
90 | | - userJSfiles.push(...await findUserJS(entryPath)) |
91 | | - else if (entry.endsWith('.user.js')) { |
92 | | - console.log(entryPath) ; userJSfiles.push(entryPath) } |
93 | | - }) |
94 | | - return userJSfiles |
95 | | - } |
96 | | - |
97 | | - async function generateSRIhash(resURL, algorithm = 'sha256') { |
98 | | - const sriHash = ssri.fromData( |
99 | | - Buffer.from(await (await fetchData(resURL)).arrayBuffer()), { algorithms: [algorithm] }).toString() |
100 | | - log.hash(`${sriHash}\n`) |
101 | | - return sriHash |
102 | | - } |
103 | | - |
104 | | - async function getLatestCommitHash(repo, path) { |
105 | | - const endpoint = `https://api.github.com/repos/${repo}/commits`, |
106 | | - latestCommitHash = (await (await fetchData(`${endpoint}?path=${ path || '' }`)).json())[0]?.sha |
107 | | - if (latestCommitHash) log.hash(`${latestCommitHash}\n`) |
108 | | - return latestCommitHash |
109 | | - } |
110 | | - |
111 | | - async function isValidResource(resURL) { |
112 | | - try { |
113 | | - const resIsValid = !(await (await fetchData(resURL)).text()).startsWith('Package size exceeded') |
114 | | - if (!resIsValid) log.error(`\nInvalid resource: ${resURL}\n`) |
115 | | - return resIsValid |
116 | | - } catch (err) { return log.error(`\nCannot validate resource: ${resURL}\n`) } |
117 | | - } |
118 | | - |
119 | | - // Run MAIN routine |
120 | | - |
121 | 30 | // Collect userscripts |
122 | | - log.working(`\n${ cacheMode ? 'Collecting' : 'Searching for' } userscripts...\n`) |
| 31 | + bump.log.working(`\n${ cacheMode ? 'Collecting' : 'Searching for' } userscripts...\n`) |
123 | 32 | let userJSfiles = [] |
124 | 33 | if (cacheMode) { |
125 | 34 | try { // create missing cache file |
126 | 35 | fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true }) |
127 | 36 | const fd = fs.openSync(cacheFilePath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_RDWR) |
128 | | - log.error(`Cache file missing. Generating ${cacheFilePath}...\n`) |
129 | | - userJSfiles = await findUserJS() ; console.log('') |
| 37 | + bump.log.error(`Cache file missing. Generating ${cacheFilePath}...\n`) |
| 38 | + userJSfiles = await bump.findUserJS() ; console.log('') |
130 | 39 | fs.writeFileSync(fd, JSON.stringify(userJSfiles, null, 2), 'utf-8') |
131 | | - log.success(`\nCache file created @ ${cacheFilePath}`) |
| 40 | + bump.log.success(`\nCache file created @ ${cacheFilePath}`) |
132 | 41 | } catch (err) { // use existing cache file |
133 | 42 | userJSfiles = JSON.parse(fs.readFileSync(cacheFilePath, 'utf-8')) |
134 | 43 | console.log(userJSfiles) ; console.log('') |
135 | 44 | } |
136 | 45 | } else { // use findUserJS() |
137 | | - userJSfiles = await findUserJS() ; console.log('') } |
| 46 | + userJSfiles = await bump.findUserJS() ; console.log('') } |
138 | 47 |
|
139 | 48 | // Collect resources |
140 | | - log.working('\nCollecting resources...\n') |
| 49 | + bump.log.working('\nCollecting resources...\n') |
141 | 50 | const urlMap = {} ; let resCnt = 0 |
142 | 51 | userJSfiles.forEach(userJSfilePath => { |
143 | 52 | const userJScontent = fs.readFileSync(userJSfilePath, 'utf-8'), |
144 | 53 | resURLs = [...userJScontent.matchAll(new RegExp(regEx.jsURL.source, 'gm'))].map(match => match[1]) |
145 | 54 | if (resURLs.length > 0) { urlMap[userJSfilePath] = resURLs ; resCnt += resURLs.length } |
146 | 55 | }) |
147 | | - log.success(`${resCnt} potentially bumpable resource(s) found.`) |
| 56 | + bump.log.success(`${resCnt} potentially bumpable resource(s) found.`) |
148 | 57 |
|
149 | 58 | // Fetch latest commit hash for adamlui/ai-web-extensions |
150 | | - log.working('\nFetching latest commit hash for adamlui/ai-web-extensions...\n') |
151 | | - const latestCommitHashes = { aiweb: await getLatestCommitHash('adamlui/ai-web-extensions') } |
| 59 | + bump.log.working('\nFetching latest commit hash for adamlui/ai-web-extensions...\n') |
| 60 | + const latestCommitHashes = { aiweb: await bump.getLatestCommitHash('adamlui/ai-web-extensions') } |
152 | 61 |
|
153 | 62 | // Process each userscript |
154 | 63 | let urlsUpdatedCnt = 0 ; let filesUpdatedCnt = 0 |
|
158 | 67 | let repoName = userJSfilePath.split('\\').pop().replace('.user.js', '') |
159 | 68 | if (repoName.endsWith('-mode')) repoName = repoName.slice(0, -5) // for chatgpt-widescreen |
160 | 69 |
|
161 | | - log.working(`\nProcessing ${repoName}...\n`) |
| 70 | + bump.log.working(`\nProcessing ${repoName}...\n`) |
162 | 71 |
|
163 | 72 | // Fetch latest commit hash for repo/chromium/extension |
164 | 73 | if (urlMap[userJSfilePath].some(url => url.includes(repoName))) { |
165 | 74 | console.log('Fetching latest commit hash for Chromium extension...') |
166 | | - latestCommitHashes.chromium = await getLatestCommitHash(`adamlui/${repoName}`, 'chromium/extension') |
| 75 | + latestCommitHashes.chromium = await bump.getLatestCommitHash(`adamlui/${repoName}`, 'chromium/extension') |
167 | 76 | } |
168 | 77 |
|
169 | 78 | // Process each resource |
170 | 79 | let fileUpdated = false |
171 | 80 | for (const resURL of urlMap[userJSfilePath]) { |
172 | | - if (!await isValidResource(resURL)) continue // to next resource |
| 81 | + if (!await bump.isValidResource(resURL)) continue // to next resource |
173 | 82 | const resName = regEx.resName.exec(resURL)?.[0] || 'resource' // dir/filename for logs |
174 | 83 |
|
175 | 84 | // Compare/update commit hash |
176 | 85 | let resLatestCommitHash = latestCommitHashes[resURL.includes(repoName) ? 'chromium' : 'aiweb'] |
177 | 86 | if (resLatestCommitHash.startsWith( // compare hashes |
178 | 87 | regEx.hash.commit.exec(resURL)?.[2] || '')) { // commit hash didn't change... |
179 | | - console.log(`${resName} already up-to-date!`) ; log.endedWithLineBreak = false |
| 88 | + console.log(`${resName} already up-to-date!`) ; bump.log.endedWithLineBreak = false |
180 | 89 | continue // ...so skip resource |
181 | 90 | } |
182 | 91 | resLatestCommitHash = resLatestCommitHash.substring(0, 7) // abbr it |
183 | 92 | let updatedURL = resURL.replace(regEx.hash.commit, `$1${resLatestCommitHash}`) // update hash |
184 | | - if (!await isValidResource(updatedURL)) continue // to next resource |
| 93 | + if (!await bump.isValidResource(updatedURL)) continue // to next resource |
185 | 94 |
|
186 | 95 | // Generate/compare/update SRI hash |
187 | 96 | console.log(`${ !log.endedWithLineBreak ? '\n' : '' }Generating SRI (SHA-256) hash for ${resName}...`) |
188 | | - const newSRIhash = await generateSRIhash(updatedURL) |
| 97 | + const newSRIhash = await bump.generateSRIhash(updatedURL) |
189 | 98 | if (regEx.hash.sri.exec(resURL)?.[0] == newSRIhash) { // SRI hash didn't change |
190 | | - console.log(`${resName} already up-to-date!`) ; log.endedWithLineBreak = false |
| 99 | + console.log(`${resName} already up-to-date!`) ; bump.log.endedWithLineBreak = false |
191 | 100 | continue // ...so skip resource |
192 | 101 | } |
193 | 102 | updatedURL = updatedURL.replace(regEx.hash.sri, newSRIhash) // update hash |
194 | | - if (!await isValidResource(updatedURL)) continue // to next resource |
| 103 | + if (!await bump.isValidResource(updatedURL)) continue // to next resource |
195 | 104 |
|
196 | 105 | // Write updated URL to userscript |
197 | 106 | console.log(`Writing updated URL for ${resName}...`) |
198 | 107 | const userJScontent = fs.readFileSync(userJSfilePath, 'utf-8') |
199 | 108 | fs.writeFileSync(userJSfilePath, userJScontent.replace(resURL, updatedURL), 'utf-8') |
200 | | - log.success(`${resName} bumped!\n`) ; urlsUpdatedCnt++ ; fileUpdated = true |
| 109 | + bump.log.success(`${resName} bumped!\n`) ; urlsUpdatedCnt++ ; fileUpdated = true |
201 | 110 | } |
202 | 111 | if (fileUpdated) { |
203 | 112 | console.log(`${ !log.endedWithLineBreak ? '\n' : '' }Bumping userscript version...`) |
204 | | - bumpUserJSver(userJSfilePath) ; filesUpdatedCnt++ |
| 113 | + bump.bumpUserJSver(userJSfilePath) ; filesUpdatedCnt++ |
205 | 114 | } |
206 | 115 | } |
207 | 116 |
|
208 | 117 | // Log final summary |
209 | | - log[urlsUpdatedCnt > 0 ? 'success' : 'info']( |
| 118 | + bump.log[urlsUpdatedCnt > 0 ? 'success' : 'info']( |
210 | 119 | `\n${ urlsUpdatedCnt > 0 ? 'Success! ' : '' }${ |
211 | 120 | urlsUpdatedCnt} resource(s) bumped across ${filesUpdatedCnt} file(s).` |
212 | 121 | ) |
|
0 commit comments