@nan0web/release 1.0.1 → 1.0.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 +24 -42
- package/bin/nan0release.js +27 -42
- package/package.json +48 -42
- package/project.yaml +79 -0
- package/src/README.md.js +90 -93
- package/src/Release/Document.js +13 -19
- package/src/Release/MarkdownToTest.js +35 -33
- package/src/Release/Person.js +3 -8
- package/src/Release/Scanner.js +179 -0
- package/src/Release.js +19 -12
- package/src/Team.js +9 -9
- package/src/architecture/ProjectManagementAsCode.js +8 -8
- package/src/cli/index.js +9 -0
- package/src/co/AppCommandOptions.js +4 -6
- package/src/commands/CloseCommand.js +67 -0
- package/src/commands/Command.js +48 -0
- package/src/commands/DepsCommand.js +143 -0
- package/src/commands/InitCommand.js +48 -56
- package/src/commands/PublishCommand.js +80 -119
- package/src/commands/SpecCommand.js +41 -0
- package/src/commands/StatusCommand.js +26 -0
- package/src/db/ReleaseDB.js +5 -5
- package/src/index.js +18 -0
- package/src/ui/cli/ReleaseCLi.js +52 -0
- package/src/utils/runSpawn.js +0 -0
- package/types/Release/Document.d.ts +2 -2
- package/types/Release/Person.d.ts +3 -3
- package/types/Release/Scanner.d.ts +90 -0
- package/types/Release.d.ts +37 -33
- package/types/ReleaseCLI.d.ts +29 -28
- package/types/Team.d.ts +3 -3
- package/types/architecture/ProjectManagementAsCode.d.ts +1 -1
- package/types/cli/index.d.ts +2 -0
- package/types/co/AppCommandMessage.d.ts +10 -11
- package/types/co/AppCommandOptions.d.ts +1 -2
- package/types/commands/BaseCommand.d.ts +23 -0
- package/types/commands/ChatCommand.d.ts +9 -9
- package/types/commands/ChatMessage.d.ts +36 -0
- package/types/commands/CloseCommand.d.ts +25 -0
- package/types/commands/Command.d.ts +28 -0
- package/types/commands/DepsCommand.d.ts +32 -0
- package/types/commands/DepsMessage.d.ts +28 -0
- package/types/commands/HostCommand.d.ts +7 -7
- package/types/commands/HostMessage.d.ts +44 -0
- package/types/commands/InitCommand.d.ts +24 -19
- package/types/commands/InitMessage.d.ts +24 -0
- package/types/commands/ListCommand.d.ts +8 -8
- package/types/commands/ListMessage.d.ts +30 -0
- package/types/commands/PublishCommand.d.ts +36 -47
- package/types/commands/PublishMessage.d.ts +39 -0
- package/types/commands/SealCommand.d.ts +7 -7
- package/types/commands/SealMessage.d.ts +28 -0
- package/types/commands/ServeCommand.d.ts +7 -7
- package/types/commands/ServeMessage.d.ts +30 -0
- package/types/commands/ShowCommand.d.ts +7 -7
- package/types/commands/SpecCommand.d.ts +25 -0
- package/types/commands/StatusCommand.d.ts +9 -0
- package/types/commands/SubCommand.d.ts +9 -7
- package/types/commands/ValidateCommand.d.ts +7 -7
- package/types/commands/ValidateMessage.d.ts +20 -0
- package/types/db/ReleaseDB.d.ts +2 -2
- package/types/index.d.ts +10 -0
- package/types/ui/cli/ReleaseCLi.d.ts +10 -0
- package/types/ui/cli/index.d.ts +0 -0
- package/types/utils/runSpawn.d.ts +0 -0
- package/.datasets/README.dataset.jsonl +0 -19
- package/.editorconfig +0 -20
- package/CHANGELOG.md +0 -6
- package/CONTRIBUTING.md +0 -42
- package/bin/commands/InitCommand.js +0 -10
- package/bin/lib/functions.js +0 -240
- package/bin/lib/functions.test.js +0 -138
- package/bin/release.js +0 -12
- package/bin/release.test.js +0 -73
- package/bin/temp/package.json +0 -1
- package/docs/system.uk.md +0 -413
- package/docs/uk/README.md +0 -284
- package/legacy/PrepareCommand.js +0 -154
- package/legacy/PrepareCommand.test.js +0 -189
- package/legacy/changelog.js +0 -446
- package/releases/1/0/v1.0.0/models/Person.js +0 -26
- package/releases/1/0/v1.0.0/models/Person.test.js +0 -44
- package/releases/1/0/v1.0.0/models/company.js +0 -11
- package/releases/1/0/v1.0.0/models/members.js +0 -14
- package/releases/1/0/v1.0.0/models/members.test.js +0 -21
- package/releases/1/0/v1.0.0/models/teams/DevTeam.js +0 -6
- package/releases/1/0/v1.0.0/models/teams/DevTeam.test.js +0 -15
- package/releases/1/0/v1.0.0/models/teams/index.js +0 -5
- package/releases/1/0/v1.0.0/release.js +0 -12
- package/releases/1/0/v1.0.0/release.md +0 -121
- package/releases/1/0/v1.0.0/release.test.js +0 -36
- package/releases/1/0/v1.0.0/test/YaRaSLove/git-native.test.js +0 -14
- package/releases/current.js +0 -3
- package/releases/index.js +0 -11
- package/src/Release/MarkdownToTest.test.js +0 -60
- package/src/ReleaseCLI.js +0 -122
- package/src/architecture/ProjectManagementAsCode.test.js +0 -128
- package/src/co/AppCommandMessage.js +0 -22
- package/src/commands/ChatCommand.js +0 -56
- package/src/commands/HostCommand.js +0 -63
- package/src/commands/ListCommand.js +0 -56
- package/src/commands/SealCommand.js +0 -51
- package/src/commands/ServeCommand.js +0 -53
- package/src/commands/ShowCommand.js +0 -58
- package/src/commands/SubCommand.js +0 -21
- package/src/commands/ValidateCommand.js +0 -66
- package/system.md +0 -571
- package/tsconfig.json +0 -23
- /package/{bin/lib/temp/pnpm-lock.yaml → src/ui/cli/index.js} +0 -0
package/legacy/changelog.js
DELETED
|
@@ -1,446 +0,0 @@
|
|
|
1
|
-
import { applyFilter, findAllFiles, saveFile, findFilesIn } from "nanoweb-fs"
|
|
2
|
-
import { readFileSync, statSync } from "node:fs"
|
|
3
|
-
import { resolve, relative } from "node:path"
|
|
4
|
-
import { execSync } from "node:child_process"
|
|
5
|
-
import { packFiles } from "../functions/pack.js"
|
|
6
|
-
import Request from "../../src/apps/core/Request.js"
|
|
7
|
-
import Response from "../../src/apps/core/Response.js"
|
|
8
|
-
import LLMApp from "../../src/apps/cgi/LLMApp.js"
|
|
9
|
-
import TestRunner from "../functions/TestRunner.js"
|
|
10
|
-
|
|
11
|
-
const binaryFileExtensions = [
|
|
12
|
-
'.png', '.webp', '.avif', '.jpg', '.bmp', '.jpeg', '.gif',
|
|
13
|
-
'.tiff', '.ico', '.svg', '.mp3', '.mp4', '.wav', '.ogg',
|
|
14
|
-
'.flac', '.mkv', '.mov', '.avi', '.wmv', '.zip', '.rar',
|
|
15
|
-
'.7z', '.tar', '.gz', '.pdf', '.exe', '.dll', '.bin',
|
|
16
|
-
]
|
|
17
|
-
|
|
18
|
-
function trimQuotes(str) {
|
|
19
|
-
return str.replace(/^"|"$/g, '')
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Reads a file if it passes the provided filter and is not a binary file.
|
|
24
|
-
* @param {string} filePath - The path to the file.
|
|
25
|
-
* @param {Object} filter - An optional filter to apply.
|
|
26
|
-
* @returns {string|false} - The file content as a string or false if skipped.
|
|
27
|
-
*/
|
|
28
|
-
function readFile(filePath, filter = {}) {
|
|
29
|
-
// Check if the file is binary
|
|
30
|
-
if (binaryFileExtensions.some(ext => filePath.endsWith(ext))) {
|
|
31
|
-
return '@@ BINARY.FILE @@'
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
// Apply additional filter before reading
|
|
36
|
-
if (applyFilter(filePath, filter)) {
|
|
37
|
-
return readFileSync(filePath, 'utf-8')
|
|
38
|
-
}
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.warn(`⚠️ Could not read file: ${filePath}`, error.message)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return false
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Changelog application class.
|
|
48
|
-
* @extends LLMApp
|
|
49
|
-
*/
|
|
50
|
-
class ChangelogApp extends LLMApp {
|
|
51
|
-
static ROUTES = {
|
|
52
|
-
...LLMApp.ROUTES,
|
|
53
|
-
write: "Generate changelog based on the latest changes",
|
|
54
|
-
}
|
|
55
|
-
static DEFAULT_ROUTE = "write"
|
|
56
|
-
static OPTIONS = {
|
|
57
|
-
...LLMApp.OPTIONS,
|
|
58
|
-
keepComments: {
|
|
59
|
-
type: Boolean, prefix: "--", help: "Keep comments from source files for LLM prompt"
|
|
60
|
-
},
|
|
61
|
-
dry: {
|
|
62
|
-
type: Boolean, prefix: "--", help: "Show all the options without making any requests"
|
|
63
|
-
},
|
|
64
|
-
recentCache: {
|
|
65
|
-
type: Boolean, prefix: "--", help: "Use recent cache instead of making a request",
|
|
66
|
-
},
|
|
67
|
-
sourceDir: {
|
|
68
|
-
type: String, prefix: "-", help: "Source directory to analyze for changes"
|
|
69
|
-
},
|
|
70
|
-
targetFile: {
|
|
71
|
-
type: String, prefix: "-", help: "Target file to save prompt"
|
|
72
|
-
},
|
|
73
|
-
inputTokenLimit: { type: Number, prefix: "-", help: "Token count limit" },
|
|
74
|
-
inputSizeLimit: { type: Number, prefix: "-", help: "Size limit in bytes" },
|
|
75
|
-
ignoreTests: { type: Boolean, prefix: "--", help: "Ignore test files when generating changelog" }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
detectVersion(version) {
|
|
79
|
-
if (Array.isArray(version)) {
|
|
80
|
-
const [v] = version.map(v => this.detectVersion(v)).filter(Boolean)
|
|
81
|
-
return v ?? null
|
|
82
|
-
}
|
|
83
|
-
const arr = String(version).split('\n').map(v => v.trim()).filter(v => v.length > 0)
|
|
84
|
-
for (let i = 0; i < arr.length; i++) {
|
|
85
|
-
const v = arr[i]
|
|
86
|
-
if (v.startsWith('## [')) {
|
|
87
|
-
return v.split(']')[0].replace('## [', '').trim()
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return null
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Trims the end of a string.
|
|
95
|
-
*
|
|
96
|
-
* @param {string} text - The string to trim.
|
|
97
|
-
* @returns {string} - The trimmed string.
|
|
98
|
-
*/
|
|
99
|
-
trimEnd(text) {
|
|
100
|
-
return text.replace(/\s+$/, '');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
extractVersions(changelog) {
|
|
104
|
-
const versions = []
|
|
105
|
-
let intro = ""
|
|
106
|
-
const versionsMap = new Map()
|
|
107
|
-
let currentVersion = null
|
|
108
|
-
|
|
109
|
-
if (!changelog) {
|
|
110
|
-
return { intro, versions }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const rows = changelog.split('\n')
|
|
114
|
-
|
|
115
|
-
for (const row of rows) {
|
|
116
|
-
const trimmedRow = row.trim()
|
|
117
|
-
if (trimmedRow.startsWith('## [')) {
|
|
118
|
-
if (currentVersion !== null) {
|
|
119
|
-
const text = this.trimEnd(currentVersion.join('\n'))
|
|
120
|
-
versionsMap.set(this.detectVersion(currentVersion), text)
|
|
121
|
-
versions.push(text)
|
|
122
|
-
}
|
|
123
|
-
currentVersion = [this.trimEnd(row)]
|
|
124
|
-
} else if (currentVersion === null) {
|
|
125
|
-
intro += row + "\n"
|
|
126
|
-
} else {
|
|
127
|
-
currentVersion.push(this.trimEnd(row))
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (currentVersion !== null) {
|
|
132
|
-
const text = this.trimEnd(currentVersion.join('\n'))
|
|
133
|
-
versionsMap.set(this.detectVersion(currentVersion), text)
|
|
134
|
-
versions.push(text)
|
|
135
|
-
}
|
|
136
|
-
return { intro, versions, versionsMap }
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
getChangeLogVersions(sourceDir) {
|
|
140
|
-
const changelogFile = resolve(sourceDir, 'CHANGELOG.md')
|
|
141
|
-
let changelog = ''
|
|
142
|
-
try {
|
|
143
|
-
changelog = readFileSync(changelogFile, 'utf-8')
|
|
144
|
-
} catch (error) {
|
|
145
|
-
console.warn(`⚠️ Could not read CHANGELOG.md: ${error.message}`)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const { intro, versions, versionsMap } = this.extractVersions(changelog)
|
|
149
|
-
return { intro, versions, versionsMap, changelog }
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Reads the CHANGELOG.md file, extracts recent versions, and formats them for LLM input.
|
|
154
|
-
* @param {string} sourceDir
|
|
155
|
-
* @param {number} [messageSize=0]
|
|
156
|
-
* @param {number} [maxSize=Infinity]
|
|
157
|
-
* @returns {object} - Recent changelog entries and metadata.
|
|
158
|
-
* @property {string} prompt - The formatted changelog prompt.
|
|
159
|
-
* @property {Array<string>} intro - Introductory lines from the changelog.
|
|
160
|
-
* @property {number} pass - Number of versions added to the prompt.
|
|
161
|
-
* @property {number} recentCount - Number of recent versions included.
|
|
162
|
-
* @property {Array<string>} recentVersions - Recent version entries.
|
|
163
|
-
* @property {Array<string>} versions - All version entries from the changelog.
|
|
164
|
-
*/
|
|
165
|
-
getRecentChangeLog(sourceDir, messageSize = 0, maxSize = Infinity) {
|
|
166
|
-
const { intro, versions, changelog } = this.getChangeLogVersions(sourceDir)
|
|
167
|
-
|
|
168
|
-
let prompt = ""
|
|
169
|
-
let pass = 0
|
|
170
|
-
|
|
171
|
-
if (!changelog) {
|
|
172
|
-
return { prompt, intro, pass, versions }
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const recentCount = 2
|
|
176
|
-
const recentVersions = versions.slice(0, recentCount)
|
|
177
|
-
|
|
178
|
-
for (const version of recentVersions) {
|
|
179
|
-
if (prompt.length + version.length > 9_000 || messageSize + prompt.length > maxSize) {
|
|
180
|
-
break
|
|
181
|
-
}
|
|
182
|
-
++pass
|
|
183
|
-
prompt += version.trim() + '\n'
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return { prompt, intro, pass, recentCount, recentVersions, versions }
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
getChangedFiles(sourceDir = this.cwd, ignore = []) {
|
|
190
|
-
const changes = execSync(`git status --porcelain`, { cwd: sourceDir }).toString().split("\n")
|
|
191
|
-
const files = changes.map(line => line.trim()).filter(line => '' !== line).map(file => file.split(' '))
|
|
192
|
-
const newDirs = files.filter(([status]) => '??' === status).map(([_, ...args]) => (args.join(' '))).map(trimQuotes).filter(f => !ignore.includes(f))
|
|
193
|
-
const modifiedFiles = files.filter(([status]) => 'M' === status).map(([_, ...args]) => (args.join(' '))).map(trimQuotes).filter(f => !ignore.includes(f))
|
|
194
|
-
const newFiles = findFilesIn(newDirs).map(f => relative(sourceDir, f))
|
|
195
|
-
const allFiles = [...newFiles, ...modifiedFiles]
|
|
196
|
-
|
|
197
|
-
if (allFiles.length === 0) {
|
|
198
|
-
return { files: [], errors: [] }
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const errors = []
|
|
202
|
-
const diffs = {}
|
|
203
|
-
newFiles.forEach(file => {
|
|
204
|
-
try {
|
|
205
|
-
const filePath = resolve(sourceDir, file)
|
|
206
|
-
const stat = statSync(filePath)
|
|
207
|
-
if (stat.isDirectory()) {
|
|
208
|
-
const dirFiles = findAllFiles(filePath, {}, ignore)
|
|
209
|
-
dirFiles.forEach(f => {
|
|
210
|
-
const content = readFile(f, /\.+/)
|
|
211
|
-
if (content) diffs[relative(sourceDir, f)] = content
|
|
212
|
-
})
|
|
213
|
-
} else {
|
|
214
|
-
const content = readFile(filePath, /\.+/)
|
|
215
|
-
if (content) diffs[file] = content
|
|
216
|
-
}
|
|
217
|
-
} catch (error) {
|
|
218
|
-
errors.push({ message: `⚠️ Could not read new file: ${file}`, stack: error.stack })
|
|
219
|
-
}
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
modifiedFiles.forEach(file => {
|
|
223
|
-
try {
|
|
224
|
-
const diff = execSync(`git diff -- ${file}`, { cwd: sourceDir }).toString().trim()
|
|
225
|
-
if (diff) diffs[file] = diff
|
|
226
|
-
} catch (error) {
|
|
227
|
-
errors.push({ message: `⚠️ Could not fetch git diff for: ${file}`, stack: error.stack })
|
|
228
|
-
}
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
const fileBlocks = Object.entries(diffs).map(([file, content]) => ({
|
|
232
|
-
file,
|
|
233
|
-
content
|
|
234
|
-
}))
|
|
235
|
-
return { files: fileBlocks, errors }
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
replaceVersion(versionContent, version, at) {
|
|
239
|
-
const rows = versionContent.split('\n').filter(row => !row.trim().startsWith('## ['))
|
|
240
|
-
rows.unshift(`## [${version}] - ${new Date(at).toISOString().split('T')[0]}`)
|
|
241
|
-
return rows.join('\n')
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
mergeVersions(current, target, version = null, at = Date.now()) {
|
|
245
|
-
const c = this.extractVersions(current)
|
|
246
|
-
const t = this.extractVersions(target)
|
|
247
|
-
const v = Array.from(t.versionsMap.keys()).filter(Boolean)
|
|
248
|
-
const getContent = () => {
|
|
249
|
-
const versionContent = t.versionsMap.get(version)
|
|
250
|
-
if (!versionContent) {
|
|
251
|
-
throw new Error(`Version ${version} not found in target changelog.`)
|
|
252
|
-
}
|
|
253
|
-
return versionContent
|
|
254
|
-
}
|
|
255
|
-
let result = []
|
|
256
|
-
if (version) {
|
|
257
|
-
if (v.includes(version)) {
|
|
258
|
-
result.push(this.replaceVersion(getContent(), version, at).trim())
|
|
259
|
-
} else {
|
|
260
|
-
throw new Error(`Version ${version} not found in target changelog.`)
|
|
261
|
-
}
|
|
262
|
-
} else if (v.length > 0) {
|
|
263
|
-
version = v[0]
|
|
264
|
-
if (version.includes(".")) {
|
|
265
|
-
result.push(this.replaceVersion(getContent(), version, at).trim())
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
c.versions.forEach((versionContent) => {
|
|
269
|
-
const ver = this.detectVersion(versionContent)
|
|
270
|
-
if (ver !== version) {
|
|
271
|
-
result.push(versionContent.trim())
|
|
272
|
-
}
|
|
273
|
-
})
|
|
274
|
-
return (c.intro + result.join('\n\n')).trim()
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async runTests(res) {
|
|
278
|
-
// Example usage
|
|
279
|
-
const testRunner = new TestRunner(this.cwd)
|
|
280
|
-
const format = new Intl.NumberFormat("en-US").format
|
|
281
|
-
const progress = (data, suffix = "") => {
|
|
282
|
-
res.write(`Testing ✅ ${data.pass} ❗️ ${data.errs} | Files: ✅ ${data.filesPass} ❗️ ${data.filesErr} | ⏰ ${format(data.time)}ms${suffix}`)
|
|
283
|
-
}
|
|
284
|
-
testRunner.on("start", (data) => {
|
|
285
|
-
res.write("Running tests... ")
|
|
286
|
-
})
|
|
287
|
-
testRunner.on("data", (data) => {
|
|
288
|
-
progress(data)
|
|
289
|
-
})
|
|
290
|
-
testRunner.on("error", (error) => {
|
|
291
|
-
if (["Debugger attached.", ""].includes(error.line)) {
|
|
292
|
-
return
|
|
293
|
-
}
|
|
294
|
-
if (error.line) {
|
|
295
|
-
return
|
|
296
|
-
}
|
|
297
|
-
res.error(`⚠️ Tests failed: ${error.message}`)
|
|
298
|
-
res.debug(error.stack)
|
|
299
|
-
})
|
|
300
|
-
testRunner.on("end", (data) => {
|
|
301
|
-
progress(data, data.errs ? "" : ` => ✅ ${data.pass} (${data.filesPass})`)
|
|
302
|
-
if (data.errs > 0) {
|
|
303
|
-
res.error(`⚠️ Tests completed with errors: ${data.errs} failed out of ${data.total}`)
|
|
304
|
-
res.debug(data.stderr.filter(s => "" !== s).join("\n"))
|
|
305
|
-
}
|
|
306
|
-
})
|
|
307
|
-
const ok = await testRunner.run()
|
|
308
|
-
return ok
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Generates changelog by analyzing changed files and sending to LLM.
|
|
313
|
-
* @param {Request} req - Input request.
|
|
314
|
-
* @param {Response} res - Output response.
|
|
315
|
-
* @returns {Promise<Response>} Response.
|
|
316
|
-
*/
|
|
317
|
-
async routeWrite(req, res) {
|
|
318
|
-
const { body, config } = req
|
|
319
|
-
const isDry = body?.opts?.dry
|
|
320
|
-
const sourceDir = body.sourceDir || this.cwd
|
|
321
|
-
const targetFile = body.targetFile
|
|
322
|
-
|
|
323
|
-
const { driver, model, config: chatConfig = {} } = this.llmDriverFor(["changelog", "learn", "$default"], config.chat)
|
|
324
|
-
if (driver) {
|
|
325
|
-
await driver.init()
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const inputTokenLimit = body.inputTokenLimit || chatConfig?.maxInput || chatConfig?.maxTokens || model?.maxInput || model?.maxTokens || 32_000
|
|
329
|
-
const inputSizeLimit = body.inputSizeLimit || model?.maxInputBytes || model?.maxBytes || (inputTokenLimit ? inputTokenLimit * 3 : 128_000)
|
|
330
|
-
const ignore = body.i || chatConfig?.resolve?.ignore || ['node_modules', 'dist', 'build', '.min.', { endsWith: '.config.js' }, 'CHANGELOG.md']
|
|
331
|
-
const ignoreTests = body.ignoreTests || chatConfig?.ignoreTests || false
|
|
332
|
-
|
|
333
|
-
if (!ignoreTests) {
|
|
334
|
-
const ok = await this.runTests(res)
|
|
335
|
-
if (!ok) {
|
|
336
|
-
res.error("⚠️ Tests failed, cannot proceed with changelog generation.")
|
|
337
|
-
res.info("You can either fix the tests or run this command with --ignoreTests option to skip tests")
|
|
338
|
-
res.info("You can also set ignoreTests to true in the config.")
|
|
339
|
-
return res.send()
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const instructions = this.getInstructions(["coder", "changelog"])
|
|
344
|
-
const id = this.getRequestID()
|
|
345
|
-
let cache = this.loadCache(id)
|
|
346
|
-
|
|
347
|
-
if (body.recentCache) {
|
|
348
|
-
const arr = this.listCaches()
|
|
349
|
-
cache = this.loadCache(arr.pop())
|
|
350
|
-
res.info("Loaded cache", id)
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (false === cache) {
|
|
354
|
-
const { files: fileBlocks, errors = [] } = this.getChangedFiles(sourceDir, ignore)
|
|
355
|
-
errors.forEach(err => {
|
|
356
|
-
res.error(err.message)
|
|
357
|
-
res.debug(err.stack)
|
|
358
|
-
})
|
|
359
|
-
if (0 === fileBlocks.length) {
|
|
360
|
-
res.info(`✅ No new or modified files detected.`)
|
|
361
|
-
return res.send()
|
|
362
|
-
}
|
|
363
|
-
const expectedResponseRate = 0.1
|
|
364
|
-
const { maxInput } = this.getMaxInput(model, chatConfig, body)
|
|
365
|
-
const opts = {
|
|
366
|
-
id, instructions, model, driver,
|
|
367
|
-
maxInput,
|
|
368
|
-
expectedResponseRate,
|
|
369
|
-
resolve: {
|
|
370
|
-
target: chatConfig?.resolve?.target || [this.cwd],
|
|
371
|
-
accept: chatConfig?.resolve?.accept || null,
|
|
372
|
-
ignore: body.i || chatConfig?.resolve?.ignore ||
|
|
373
|
-
chatConfig?.resolve?.skip || ['node_modules', 'dist', 'build', '.min.', { endsWith: '.config.js' }]
|
|
374
|
-
},
|
|
375
|
-
concurrent: chatConfig?.concurrent || 1,
|
|
376
|
-
files: fileBlocks
|
|
377
|
-
}
|
|
378
|
-
const { blocks, tokens, size, iCount } = await this.packRequest(opts)
|
|
379
|
-
|
|
380
|
-
this.emit("pack.collected", { id, opts, blocks, iCount, tokens, size, model })
|
|
381
|
-
|
|
382
|
-
const recentChangelog = this.getRecentChangeLog(sourceDir, 0, inputSizeLimit)
|
|
383
|
-
const changelogPrompt = recentChangelog.prompt ? `## \n### Recent CHANGELOG.md entries\n#### \`./CHANGELOG.md\`\n\`\`\`md\n${recentChangelog.prompt}\n\`\`\`\n\n` : ''
|
|
384
|
-
for (const block of blocks) {
|
|
385
|
-
let tokens = await this.ensureTokensCount(changelogPrompt, driver)
|
|
386
|
-
block.prompts.push({ prompt: changelogPrompt, size: changelogPrompt.length, tokens })
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (isDry) {
|
|
390
|
-
res.info("Prompt with attached files and their changes:")
|
|
391
|
-
blocks.forEach(block => {
|
|
392
|
-
res.info(String(block.prompts.filter(block => !block.file).map(p => String(p.prompt)).join("\n\n")))
|
|
393
|
-
res.info("## CONTEXT (only file names for dry mode)")
|
|
394
|
-
res.info("- " + String(block.prompts.filter(block => block.file).map(p => String(p.file)).sort().join("\n- ")))
|
|
395
|
-
})
|
|
396
|
-
} else {
|
|
397
|
-
if (!driver) {
|
|
398
|
-
res.warn("Driver not found")
|
|
399
|
-
if (targetFile) {
|
|
400
|
-
const packed = packFiles(fileBlocks, targetFile, { keepComments: body.opts?.keepComments ?? false })
|
|
401
|
-
const related = packed.map(p => ([relative(sourceDir, p[0]), p[1]]))
|
|
402
|
-
res.info("Packed files into:")
|
|
403
|
-
res.table(related, [], { padding: 3, prefix: "- " })
|
|
404
|
-
} else {
|
|
405
|
-
res.warn("Target file not specified, skipping packing")
|
|
406
|
-
}
|
|
407
|
-
return res.send()
|
|
408
|
-
}
|
|
409
|
-
const chunks = await this.completeChunks(blocks, { driver, model, id, config: chatConfig })
|
|
410
|
-
cache = { id, chunks }
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (cache && !isDry) {
|
|
415
|
-
await this.unpackChunks({ ...cache, noSave: true })
|
|
416
|
-
const changelogPath = resolve(sourceDir, 'CHANGELOG.md')
|
|
417
|
-
const files = cache.chunks.reduce((acc, chunk) => ({ ...acc, ...this.extract(chunk.response) }), {})
|
|
418
|
-
const newContent = files['./CHANGELOG.md'] || files['CHANGELOG.md']
|
|
419
|
-
if (!newContent) {
|
|
420
|
-
res.warn(`No CHANGELOG.md content found in LLM response`)
|
|
421
|
-
return res.send()
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// const newData = this.extractVersions(newContent)
|
|
425
|
-
const current = this.getChangeLogVersions(sourceDir)
|
|
426
|
-
// git tag -l
|
|
427
|
-
const text = this.mergeVersions(current.changelog, newContent)
|
|
428
|
-
saveFile(changelogPath, text)
|
|
429
|
-
res.info(`Updated ./CHANGELOG.md with new content`)
|
|
430
|
-
|
|
431
|
-
if (!ignoreTests) {
|
|
432
|
-
const ok = await this.runTests(res)
|
|
433
|
-
if (!ok) {
|
|
434
|
-
res.error("⚠️ Tests failed after response")
|
|
435
|
-
res.info("Use git command to revert changes")
|
|
436
|
-
res.info("\n git checkout -- CHANGELOG.md\n")
|
|
437
|
-
return res.send()
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
return res.send()
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
export default ChangelogApp
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Contact } from "@nan0web/co"
|
|
2
|
-
|
|
3
|
-
class Person {
|
|
4
|
-
name
|
|
5
|
-
contacts
|
|
6
|
-
gpgKey
|
|
7
|
-
revokedKey
|
|
8
|
-
constructor(input = {}) {
|
|
9
|
-
const {
|
|
10
|
-
name = "",
|
|
11
|
-
contacts = [],
|
|
12
|
-
gpgKey = null,
|
|
13
|
-
revokedKey = null
|
|
14
|
-
} = input
|
|
15
|
-
this.name = String(name)
|
|
16
|
-
this.contacts = contacts.map(c => Contact.from(c))
|
|
17
|
-
this.gpgKey = gpgKey
|
|
18
|
-
this.revokedKey = revokedKey
|
|
19
|
-
}
|
|
20
|
-
static from(input) {
|
|
21
|
-
if (input instanceof Person) return input
|
|
22
|
-
return new Person(input)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default Person
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
|
|
4
|
-
import Person from "./Person.js"
|
|
5
|
-
import { Contact } from "@nan0web/contact"
|
|
6
|
-
|
|
7
|
-
describe("Person Model", () => {
|
|
8
|
-
it("constructs with default empty values", () => {
|
|
9
|
-
const person = new Person()
|
|
10
|
-
assert.equal(person.name, "")
|
|
11
|
-
assert.deepEqual(person.contacts, [])
|
|
12
|
-
assert.equal(person.gpgKey, null)
|
|
13
|
-
assert.equal(person.revokedKey, null)
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it("constructs with given input", () => {
|
|
17
|
-
const input = {
|
|
18
|
-
name: "Alice",
|
|
19
|
-
contacts: ["https://example.com", "mailto:alice@example.com"],
|
|
20
|
-
gpgKey: "KEY123",
|
|
21
|
-
revokedKey: "OLDKEY456"
|
|
22
|
-
}
|
|
23
|
-
const person = new Person(input)
|
|
24
|
-
assert.equal(person.name, "Alice")
|
|
25
|
-
assert.equal(person.gpgKey, "KEY123")
|
|
26
|
-
assert.equal(person.revokedKey, "OLDKEY456")
|
|
27
|
-
assert.equal(person.contacts.length, 2)
|
|
28
|
-
assert.ok(person.contacts[0] instanceof Contact)
|
|
29
|
-
assert.ok(person.contacts[1] instanceof Contact)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it("can be created using static from method", () => {
|
|
33
|
-
const input = { name: "Bob" }
|
|
34
|
-
const person = Person.from(input)
|
|
35
|
-
assert.ok(person instanceof Person)
|
|
36
|
-
assert.equal(person.name, "Bob")
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it("returns same instance when from receives Person", () => {
|
|
40
|
-
const p1 = new Person({ name: "Charlie" })
|
|
41
|
-
const p2 = Person.from(p1)
|
|
42
|
-
assert.equal(p1, p2)
|
|
43
|
-
})
|
|
44
|
-
})
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Contact } from "@nan0web/co"
|
|
2
|
-
import Person from "./Person.js"
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
YaRaSLove: new Person({
|
|
6
|
-
name: "YaRaSLove",
|
|
7
|
-
contacts: [
|
|
8
|
-
new Contact("https://yaro.page"),
|
|
9
|
-
new Contact("mailto:support@yaro.page"),
|
|
10
|
-
new Contact("tel:00380673861050"),
|
|
11
|
-
],
|
|
12
|
-
gpgKey: "?"
|
|
13
|
-
}),
|
|
14
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
|
|
4
|
-
import members from "./members.js"
|
|
5
|
-
|
|
6
|
-
describe("Members Model", () => {
|
|
7
|
-
it("exports YaRaSLove as a member", () => {
|
|
8
|
-
assert.ok(members.YaRaSLove)
|
|
9
|
-
assert.equal(members.YaRaSLove.name, "YaRaSLove")
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it("YaRaSLove has three contacts", () => {
|
|
13
|
-
const yar = members.YaRaSLove
|
|
14
|
-
assert.equal(yar.contacts.length, 3)
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it("YaRaSLove includes a valid GPG key", () => {
|
|
18
|
-
const yar = members.YaRaSLove
|
|
19
|
-
assert.equal(yar.gpgKey, "ABC123456789")
|
|
20
|
-
})
|
|
21
|
-
})
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
|
|
4
|
-
import DevTeam from "./DevTeam.js"
|
|
5
|
-
|
|
6
|
-
describe("DevTeam Model", () => {
|
|
7
|
-
it("has a static name defined", () => {
|
|
8
|
-
assert.equal(DevTeam.name, "Software development team")
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it("references developer from members", () => {
|
|
12
|
-
assert.ok(DevTeam.developer)
|
|
13
|
-
assert.equal(DevTeam.developer.name, "YaRaSLove")
|
|
14
|
-
})
|
|
15
|
-
})
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// import Release from "@nan0web/release"
|
|
2
|
-
import Release from "../../../../src/Release.js"
|
|
3
|
-
import company from "./models/company.js"
|
|
4
|
-
|
|
5
|
-
export default new Release({
|
|
6
|
-
version: "v1.0.0",
|
|
7
|
-
date: "2025-08-18",
|
|
8
|
-
|
|
9
|
-
company,
|
|
10
|
-
teams: company.teams,
|
|
11
|
-
members: company.members,
|
|
12
|
-
})
|