@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
|
@@ -11,22 +11,22 @@ class MarkdownToTest {
|
|
|
11
11
|
generateTests(markdown) {
|
|
12
12
|
const lines = markdown.split('\n')
|
|
13
13
|
const versionMatch = markdown.match(/^#\s+(v[\d.]+)\s*-\s*(\d{4}-\d{2}-\d{2})/)
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
if (!versionMatch) {
|
|
16
16
|
throw new Error('Markdown must start with version header: # vX.Y.Z - YYYY-MM-DD')
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
const [, version, date] = versionMatch
|
|
20
20
|
let testCode = this.generateImports()
|
|
21
21
|
testCode += this.generateDescribeHeader(version, date)
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
const sections = this.extractSections(lines)
|
|
24
24
|
testCode += this.generateSectionTests(sections)
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
testCode += '})\n'
|
|
27
27
|
return testCode
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
/**
|
|
31
31
|
* Generate import statements for node:test
|
|
32
32
|
* @returns {string} Import code
|
|
@@ -37,7 +37,7 @@ import { ok, strictEqual } from "node:assert"
|
|
|
37
37
|
|
|
38
38
|
`
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
/**
|
|
42
42
|
* Generate main describe block header
|
|
43
43
|
* @param {string} version - Release version
|
|
@@ -48,7 +48,7 @@ import { ok, strictEqual } from "node:assert"
|
|
|
48
48
|
return `describe("${version} - ${date}", () => {
|
|
49
49
|
`
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
/**
|
|
53
53
|
* Extract sections from markdown lines
|
|
54
54
|
* @param {string[]} lines - Markdown lines
|
|
@@ -59,20 +59,20 @@ import { ok, strictEqual } from "node:assert"
|
|
|
59
59
|
const sections = []
|
|
60
60
|
/** @type {{title: string, tasks: Array<{content: string, status: string, slug: string}>} | null} */
|
|
61
61
|
let currentSection = null
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
for (let i = 0; i < lines.length; i++) {
|
|
64
64
|
const line = lines[i]
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
// H2 section
|
|
67
67
|
if (line.startsWith('## ')) {
|
|
68
68
|
currentSection = {
|
|
69
69
|
title: line.slice(3).trim(),
|
|
70
|
-
tasks: /** @type {Array<{content: string, status: string, slug: string}>} */([])
|
|
70
|
+
tasks: /** @type {Array<{content: string, status: string, slug: string}>} */ ([]),
|
|
71
71
|
}
|
|
72
72
|
sections.push(currentSection)
|
|
73
73
|
continue
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
// H3 task
|
|
77
77
|
if (line.startsWith('###') && currentSection) {
|
|
78
78
|
const statusMatch = line.match(/^###( Draft| InProgress)? \*\*(.+)\*\* \[(.+)\]$/)
|
|
@@ -81,16 +81,16 @@ import { ok, strictEqual } from "node:assert"
|
|
|
81
81
|
currentSection.tasks.push({
|
|
82
82
|
content,
|
|
83
83
|
status: status ? status.trim() : 'Done',
|
|
84
|
-
slug
|
|
84
|
+
slug,
|
|
85
85
|
})
|
|
86
86
|
}
|
|
87
87
|
continue
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
return sections
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
/**
|
|
95
95
|
* Generate test code for sections and their tasks
|
|
96
96
|
* @param {Array<{title: string, tasks: Array}>} sections - Sections with tasks
|
|
@@ -98,40 +98,42 @@ import { ok, strictEqual } from "node:assert"
|
|
|
98
98
|
*/
|
|
99
99
|
generateSectionTests(sections) {
|
|
100
100
|
let code = ''
|
|
101
|
-
|
|
102
|
-
sections.forEach(section => {
|
|
101
|
+
|
|
102
|
+
sections.forEach((section) => {
|
|
103
103
|
code += ` describe("${section.title}", () => {
|
|
104
104
|
`
|
|
105
|
-
|
|
106
|
-
section.tasks.forEach(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
|
|
106
|
+
section.tasks.forEach(
|
|
107
|
+
/** @param {{status: string, content: string}} task */ (task) => {
|
|
108
|
+
switch (task.status) {
|
|
109
|
+
case 'Draft':
|
|
110
|
+
code += ` it.todo("${task.content}", () => {
|
|
110
111
|
ok(false, "Draft task - not yet implemented")
|
|
111
112
|
})
|
|
112
113
|
`
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
break
|
|
115
|
+
case 'InProgress':
|
|
116
|
+
code += ` it.skip("${task.content}", () => {
|
|
116
117
|
ok(false, "In progress task - not yet completed")
|
|
117
118
|
})
|
|
118
119
|
`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
break
|
|
121
|
+
default: // Done
|
|
122
|
+
code += ` it("${task.content}", () => {
|
|
122
123
|
ok(true, "Task completed successfully")
|
|
123
124
|
})
|
|
124
125
|
`
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
)
|
|
130
|
+
|
|
129
131
|
code += ` })
|
|
130
132
|
`
|
|
131
133
|
})
|
|
132
|
-
|
|
134
|
+
|
|
133
135
|
return code
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
export default MarkdownToTest
|
|
139
|
+
export default MarkdownToTest
|
package/src/Release/Person.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HumanName, HumanContact, HumanGender } from
|
|
1
|
+
import { HumanName, HumanContact, HumanGender } from '@nan0web/verse'
|
|
2
2
|
|
|
3
3
|
export default class Person {
|
|
4
4
|
/** @type {HumanName} */
|
|
@@ -8,14 +8,10 @@ export default class Person {
|
|
|
8
8
|
/** @type {HumanContact[]} */
|
|
9
9
|
contacts
|
|
10
10
|
constructor(input = {}) {
|
|
11
|
-
const {
|
|
12
|
-
name = new HumanName([]),
|
|
13
|
-
gender = new HumanGender(-1),
|
|
14
|
-
contacts = []
|
|
15
|
-
} = input
|
|
11
|
+
const { name = new HumanName([]), gender = new HumanGender(-1), contacts = [] } = input
|
|
16
12
|
this.name = HumanName.from(name)
|
|
17
13
|
this.gender = HumanGender.from(gender)
|
|
18
|
-
this.contacts = contacts.map(c => HumanContact.from(c))
|
|
14
|
+
this.contacts = contacts.map((c) => HumanContact.from(c))
|
|
19
15
|
}
|
|
20
16
|
/**
|
|
21
17
|
* @param {*} input
|
|
@@ -26,4 +22,3 @@ export default class Person {
|
|
|
26
22
|
return new Person(input)
|
|
27
23
|
}
|
|
28
24
|
}
|
|
29
|
-
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { readdirSync, existsSync, renameSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} ContractEntry
|
|
6
|
+
* @property {string} version - Release version (e.g. "v1.0.0")
|
|
7
|
+
* @property {string} path - Absolute path to the contract file
|
|
8
|
+
* @property {string} type - "spec" or "test"
|
|
9
|
+
* @property {string} dir - Directory containing the contract
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} VersionStatus
|
|
14
|
+
* @property {string} version - Release version
|
|
15
|
+
* @property {'wip' | 'closed' | 'mixed'} state - Version state
|
|
16
|
+
* @property {string[]} specs - Paths to spec files
|
|
17
|
+
* @property {string[]} tests - Paths to test files
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Рекурсивний сканер для пошуку release контрактів.
|
|
22
|
+
* Підтримує вкладену структуру: releases/{major}/{minor}/v{version}/
|
|
23
|
+
* та плоску: releases/v{version}/
|
|
24
|
+
*/
|
|
25
|
+
export default class Scanner {
|
|
26
|
+
/** @type {string} */
|
|
27
|
+
root
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} root - Root directory of the project
|
|
31
|
+
*/
|
|
32
|
+
constructor(root) {
|
|
33
|
+
this.root = resolve(root)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Find all .spec.js files (WIP contracts)
|
|
38
|
+
* @param {string} [version] - Optional version filter
|
|
39
|
+
* @returns {ContractEntry[]}
|
|
40
|
+
*/
|
|
41
|
+
findSpecs(version) {
|
|
42
|
+
return this.#scan(join(this.root, 'releases'), '.spec.js', version)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Find all .test.js files (Closed contracts)
|
|
47
|
+
* @param {string} [version] - Optional version filter
|
|
48
|
+
* @returns {ContractEntry[]}
|
|
49
|
+
*/
|
|
50
|
+
findTests(version) {
|
|
51
|
+
const oldTests = this.#scan(join(this.root, 'releases'), '.test.js', version)
|
|
52
|
+
const newTests = this.#scan(join(this.root, 'src', 'releases'), '.test.js', version)
|
|
53
|
+
return [...oldTests, ...newTests]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get status of all versions
|
|
58
|
+
* @returns {VersionStatus[]}
|
|
59
|
+
*/
|
|
60
|
+
status() {
|
|
61
|
+
const specs = this.findSpecs()
|
|
62
|
+
const tests = this.findTests()
|
|
63
|
+
|
|
64
|
+
/** @type {Map<string, VersionStatus>} */
|
|
65
|
+
const map = new Map()
|
|
66
|
+
|
|
67
|
+
for (const entry of [...specs, ...tests]) {
|
|
68
|
+
let status = map.get(entry.version)
|
|
69
|
+
if (!status) {
|
|
70
|
+
status = {
|
|
71
|
+
version: entry.version,
|
|
72
|
+
state: 'wip',
|
|
73
|
+
specs: [],
|
|
74
|
+
tests: [],
|
|
75
|
+
}
|
|
76
|
+
map.set(entry.version, status)
|
|
77
|
+
}
|
|
78
|
+
if (entry.type === 'spec') status.specs.push(entry.path)
|
|
79
|
+
else status.tests.push(entry.path)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const status of map.values()) {
|
|
83
|
+
if (status.specs.length > 0 && status.tests.length > 0) {
|
|
84
|
+
status.state = 'mixed'
|
|
85
|
+
} else if (status.specs.length === 0 && status.tests.length > 0) {
|
|
86
|
+
status.state = 'closed'
|
|
87
|
+
} else {
|
|
88
|
+
status.state = 'wip'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return [...map.values()].sort((a, b) => a.version.localeCompare(b.version))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Close a release: rename all .spec.js → .test.js
|
|
97
|
+
* @param {string} [version] - Version to close. If omitted, closes all WIP.
|
|
98
|
+
* @returns {{ closed: number, files: string[] }}
|
|
99
|
+
*/
|
|
100
|
+
close(version) {
|
|
101
|
+
const specs = this.findSpecs(version)
|
|
102
|
+
|
|
103
|
+
if (version && specs.length === 0) {
|
|
104
|
+
// Check if the version exists at all (might already be closed)
|
|
105
|
+
const tests = this.findTests(version)
|
|
106
|
+
if (tests.length === 0) {
|
|
107
|
+
throw new Error(`Version ${version} not found in releases/`)
|
|
108
|
+
}
|
|
109
|
+
return { closed: 0, files: [] }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const files = []
|
|
113
|
+
for (const spec of specs) {
|
|
114
|
+
const newPath = spec.path.replace('.spec.js', '.test.js')
|
|
115
|
+
renameSync(spec.path, newPath)
|
|
116
|
+
files.push(newPath)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { closed: files.length, files }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Recursively scan directory for contract files
|
|
124
|
+
* @param {string} baseDir - Directory to scan
|
|
125
|
+
* @param {string} suffix - File suffix (.spec.js or .test.js)
|
|
126
|
+
* @param {string} [version] - Optional version filter
|
|
127
|
+
* @returns {ContractEntry[]}
|
|
128
|
+
*/
|
|
129
|
+
#scan(baseDir, suffix, version) {
|
|
130
|
+
if (!existsSync(baseDir)) return []
|
|
131
|
+
|
|
132
|
+
/** @type {ContractEntry[]} */
|
|
133
|
+
const results = []
|
|
134
|
+
this.#walkDir(baseDir, suffix, version, results)
|
|
135
|
+
return results
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {string} dir
|
|
140
|
+
* @param {string} suffix
|
|
141
|
+
* @param {string | undefined} version
|
|
142
|
+
* @param {ContractEntry[]} results
|
|
143
|
+
*/
|
|
144
|
+
#walkDir(dir, suffix, version, results) {
|
|
145
|
+
let entries
|
|
146
|
+
try {
|
|
147
|
+
entries = readdirSync(dir, { withFileTypes: true })
|
|
148
|
+
} catch {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Check if this directory is a version directory (starts with 'v' and has dots)
|
|
153
|
+
const dirName = dir.split('/').pop() || ''
|
|
154
|
+
const isVersionDir = /^v\d+\.\d+\.\d+/.test(dirName)
|
|
155
|
+
|
|
156
|
+
if (isVersionDir) {
|
|
157
|
+
if (version && dirName !== version) return
|
|
158
|
+
|
|
159
|
+
for (const entry of entries) {
|
|
160
|
+
if (entry.isFile() && entry.name.endsWith(suffix)) {
|
|
161
|
+
results.push({
|
|
162
|
+
version: dirName,
|
|
163
|
+
path: join(dir, entry.name),
|
|
164
|
+
type: suffix === '.spec.js' ? 'spec' : 'test',
|
|
165
|
+
dir,
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Recurse into subdirectories
|
|
173
|
+
for (const entry of entries) {
|
|
174
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
175
|
+
this.#walkDir(join(dir, entry.name), suffix, version, results)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
package/src/Release.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import ReleaseDocument from
|
|
1
|
+
import ReleaseDocument from './Release/Document.js'
|
|
2
2
|
import Logger from '@nan0web/log'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {Object} ReleaseConfig
|
|
6
6
|
* @property {string} [version] - Release version
|
|
7
7
|
* @property {string | number | Date | undefined} [createdAt] -
|
|
8
|
+
* @property {string | number | Date | undefined} [date] - Alias for createdAt
|
|
8
9
|
* @property {string | number | Date | undefined} [startAt] -
|
|
9
10
|
* @property {string | number | Date | undefined} [planAt] -
|
|
10
11
|
* @property {string | number | Date | undefined} [completeAt] -
|
|
@@ -15,7 +16,7 @@ import Logger from '@nan0web/log'
|
|
|
15
16
|
/**
|
|
16
17
|
* Release management class that coordinates task validation and release execution
|
|
17
18
|
*/
|
|
18
|
-
class Release {
|
|
19
|
+
export default class Release {
|
|
19
20
|
/** @type {string} */
|
|
20
21
|
version
|
|
21
22
|
/** @type {Date} */
|
|
@@ -40,12 +41,12 @@ class Release {
|
|
|
40
41
|
constructor(config = {}) {
|
|
41
42
|
const {
|
|
42
43
|
version = '0.0.0',
|
|
43
|
-
createdAt = new Date(),
|
|
44
|
+
createdAt = config.date || new Date(),
|
|
44
45
|
startAt,
|
|
45
46
|
planAt,
|
|
46
47
|
completeAt,
|
|
47
48
|
document,
|
|
48
|
-
tasks = new Map()
|
|
49
|
+
tasks = new Map(),
|
|
49
50
|
} = config
|
|
50
51
|
|
|
51
52
|
this.version = String(version)
|
|
@@ -56,10 +57,18 @@ class Release {
|
|
|
56
57
|
this.document = ReleaseDocument.from(document)
|
|
57
58
|
this.tasks = tasks
|
|
58
59
|
this.logger = new Logger()
|
|
60
|
+
|
|
61
|
+
// Store additional config properties
|
|
62
|
+
for (const [key, value] of Object.entries(config)) {
|
|
63
|
+
if (!(key in this)) {
|
|
64
|
+
/** @ts-ignore */
|
|
65
|
+
this[key] = value
|
|
66
|
+
}
|
|
67
|
+
}
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
get path() {
|
|
62
|
-
return [...this.version.replace(
|
|
71
|
+
return [...this.version.replace('v', '').split('.').slice(0, 2), this.version].join('/')
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
/**
|
|
@@ -68,9 +77,9 @@ class Release {
|
|
|
68
77
|
*/
|
|
69
78
|
async validate() {
|
|
70
79
|
const results = {
|
|
71
|
-
passed: /** @type {Array} */([]),
|
|
72
|
-
failed: /** @type {Array<{taskId: any, error: string}>} */([]),
|
|
73
|
-
pending: /** @type {Array} */([])
|
|
80
|
+
passed: /** @type {Array} */ ([]),
|
|
81
|
+
failed: /** @type {Array<{taskId: any, error: string}>} */ ([]),
|
|
82
|
+
pending: /** @type {Array} */ ([]),
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
for (const [taskId, testFn] of this.tasks.entries()) {
|
|
@@ -78,7 +87,7 @@ class Release {
|
|
|
78
87
|
await testFn()
|
|
79
88
|
results.passed.push(taskId)
|
|
80
89
|
} catch (error) {
|
|
81
|
-
results.failed.push({ taskId, error: /** @type {Error} */(error).message })
|
|
90
|
+
results.failed.push({ taskId, error: /** @type {Error} */ (error).message })
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
|
|
@@ -124,9 +133,7 @@ class Release {
|
|
|
124
133
|
completed: completedTasks,
|
|
125
134
|
failed: failedTasks,
|
|
126
135
|
pending: pendingTasks,
|
|
127
|
-
progress: totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0
|
|
136
|
+
progress: totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0,
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
|
-
|
|
132
|
-
export default Release
|
package/src/Team.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HumanName, HumanGender, HumanContact } from
|
|
1
|
+
import { HumanName, HumanGender, HumanContact } from '@nan0web/verse'
|
|
2
2
|
|
|
3
3
|
class Person {
|
|
4
4
|
/** @type {HumanName} */
|
|
@@ -7,7 +7,7 @@ class Person {
|
|
|
7
7
|
gender
|
|
8
8
|
/** @type {HumanContact[]} */
|
|
9
9
|
contacts
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
constructor(name) {
|
|
12
12
|
this.name = new HumanName(name)
|
|
13
13
|
this.gender = new HumanGender(-1)
|
|
@@ -16,20 +16,20 @@ class Person {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
class CLevelTeam {
|
|
19
|
-
static name =
|
|
20
|
-
static ceo = new Person([
|
|
21
|
-
static cfo = new Person([
|
|
19
|
+
static name = 'C Level'
|
|
20
|
+
static ceo = new Person(['Yaroslav', 'Wise'])
|
|
21
|
+
static cfo = new Person(['Chingis'])
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
class UXTeam {
|
|
25
|
-
static name =
|
|
26
|
-
static designer = new Person([
|
|
25
|
+
static name = 'UX'
|
|
26
|
+
static designer = new Person(['Taras', 'Shevchenko'])
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
class Company {
|
|
30
|
-
static name =
|
|
30
|
+
static name = 'NaN•Web'
|
|
31
31
|
static c = CLevelTeam
|
|
32
32
|
static ux = UXTeam
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export default Company
|
|
35
|
+
export default Company
|
|
@@ -78,9 +78,9 @@ export class ProjectManagement {
|
|
|
78
78
|
*/
|
|
79
79
|
async validateProjectState() {
|
|
80
80
|
const results = {
|
|
81
|
-
passed: /** @type {string[]} */([]),
|
|
82
|
-
failed: /** @type {string[]} */([]),
|
|
83
|
-
pending: /** @type {Array<{taskId: string, error: string}>} */([])
|
|
81
|
+
passed: /** @type {string[]} */ ([]),
|
|
82
|
+
failed: /** @type {string[]} */ ([]),
|
|
83
|
+
pending: /** @type {Array<{taskId: string, error: string}>} */ ([]),
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
for (const [taskId, testPath] of this.tasks.entries()) {
|
|
@@ -92,7 +92,7 @@ export class ProjectManagement {
|
|
|
92
92
|
results.failed.push(taskId)
|
|
93
93
|
}
|
|
94
94
|
} catch (error) {
|
|
95
|
-
results.pending.push({ taskId, error: /** @type {Error} */(error).message })
|
|
95
|
+
results.pending.push({ taskId, error: /** @type {Error} */ (error).message })
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -110,7 +110,7 @@ export class ProjectManagement {
|
|
|
110
110
|
return {
|
|
111
111
|
passed: true,
|
|
112
112
|
duration: 0,
|
|
113
|
-
assertions: 0
|
|
113
|
+
assertions: 0,
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -129,9 +129,9 @@ export class ProjectManagement {
|
|
|
129
129
|
total: this.tasks.size,
|
|
130
130
|
completed: projectState.passed.length,
|
|
131
131
|
failed: projectState.failed.length,
|
|
132
|
-
pending: projectState.pending.length
|
|
132
|
+
pending: projectState.pending.length,
|
|
133
133
|
},
|
|
134
|
-
ready: projectState.failed.length === 0
|
|
134
|
+
ready: projectState.failed.length === 0,
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
}
|
|
@@ -171,7 +171,7 @@ export class ReleaseManager {
|
|
|
171
171
|
return {
|
|
172
172
|
version,
|
|
173
173
|
validation,
|
|
174
|
-
published: await this.publish(version, options)
|
|
174
|
+
published: await this.publish(version, options),
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
package/src/cli/index.js
ADDED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class AppCommandOptions {
|
|
1
|
+
export default class AppCommandOptions {
|
|
2
2
|
/** @type {boolean} */
|
|
3
3
|
webui = false
|
|
4
4
|
/** @type {boolean} */
|
|
@@ -6,16 +6,16 @@ class AppCommandOptions {
|
|
|
6
6
|
/** @type {boolean} */
|
|
7
7
|
quiet = false
|
|
8
8
|
/** @type {string} */
|
|
9
|
-
releaseDir =
|
|
9
|
+
releaseDir = './releases'
|
|
10
10
|
/** @type {boolean} */
|
|
11
11
|
write = false
|
|
12
12
|
/** @type {string} */
|
|
13
|
-
user =
|
|
13
|
+
user = 'anonymous'
|
|
14
14
|
/** @type {boolean} */
|
|
15
15
|
ignoreFailTests = false
|
|
16
16
|
/** @type {boolean} */
|
|
17
17
|
full = false
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
/**
|
|
20
20
|
* @param {Object} input
|
|
21
21
|
*/
|
|
@@ -23,5 +23,3 @@ class AppCommandOptions {
|
|
|
23
23
|
Object.assign(this, input)
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
export default AppCommandOptions
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { OutputMessage } from '@nan0web/co'
|
|
2
|
+
import Command from './Command.js'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
import Scanner from '../Release/Scanner.js'
|
|
5
|
+
|
|
6
|
+
class CloseBody {
|
|
7
|
+
static version = { help: 'Release version', defaultValue: '' }
|
|
8
|
+
/** @type {string} */
|
|
9
|
+
version = ''
|
|
10
|
+
constructor({ version = '' } = {}) {
|
|
11
|
+
this.version = String(version)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default class CloseCommand extends Command {
|
|
16
|
+
static name = 'close'
|
|
17
|
+
static help = 'Close a release and move tests to src/'
|
|
18
|
+
static Body = CloseBody
|
|
19
|
+
|
|
20
|
+
/** @param {Partial<Command> & { body?: Partial<CloseBody> }} [input={}] */
|
|
21
|
+
constructor(input = {}) {
|
|
22
|
+
super(input)
|
|
23
|
+
this.body = new CloseBody(/** @type {*} */ (input))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async *run() {
|
|
27
|
+
const v = this.body.version || ''
|
|
28
|
+
|
|
29
|
+
yield new OutputMessage(`🛜 nan0release close ${v || 'all'}`)
|
|
30
|
+
|
|
31
|
+
const scanner = new Scanner(process.cwd())
|
|
32
|
+
const specs = scanner.findSpecs(v)
|
|
33
|
+
|
|
34
|
+
if (specs.length === 0) {
|
|
35
|
+
yield new OutputMessage(`No pending specs found for version ${v || 'all'}`)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
yield new OutputMessage(`Running ${specs.length} pending specs...`)
|
|
40
|
+
|
|
41
|
+
const files = specs.map((s) => s.path)
|
|
42
|
+
await this._run('node', ['--test', ...files], 'Cannot close: Specs failed')
|
|
43
|
+
|
|
44
|
+
yield new OutputMessage(`✅ Specs passed. Closing...`)
|
|
45
|
+
|
|
46
|
+
let movedCount = 0
|
|
47
|
+
const { renameSync, mkdirSync } = await import('node:fs')
|
|
48
|
+
const { join, dirname } = await import('node:path')
|
|
49
|
+
|
|
50
|
+
for (const spec of specs) {
|
|
51
|
+
const relativePath = spec.path.substring(process.cwd().length + 1)
|
|
52
|
+
const destRelative = relativePath
|
|
53
|
+
.replace(/^releases\//, 'src/releases/')
|
|
54
|
+
.replace('.spec.js', '.test.js')
|
|
55
|
+
const destPath = join(process.cwd(), destRelative)
|
|
56
|
+
|
|
57
|
+
mkdirSync(dirname(destPath), { recursive: true })
|
|
58
|
+
renameSync(spec.path, destPath)
|
|
59
|
+
movedCount++
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
yield new OutputMessage({
|
|
63
|
+
content: [`✅ Successfully closed ${movedCount} specs and moved to src/releases/`],
|
|
64
|
+
type: OutputMessage.TYPES.SUCCESS,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import DBFS from '@nan0web/db-fs'
|
|
2
|
+
import { OutputMessage } from '@nan0web/co'
|
|
3
|
+
import Logger from '@nan0web/log'
|
|
4
|
+
import { runSpawn } from '@nan0web/test'
|
|
5
|
+
import { UiMessage } from '@nan0web/ui'
|
|
6
|
+
|
|
7
|
+
/** @typedef {import("@nan0web/test/types/exec/runSpawn").SpawnResult} SpawnResult */
|
|
8
|
+
|
|
9
|
+
export default class Command extends UiMessage {
|
|
10
|
+
/** @type {DBFS} */
|
|
11
|
+
fs
|
|
12
|
+
/** @type {Logger} */
|
|
13
|
+
logger
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Partial<UiMessage> & { }} [input={}]
|
|
17
|
+
*/
|
|
18
|
+
constructor(input = {}) {
|
|
19
|
+
super(input)
|
|
20
|
+
const { fs, logger } = UiMessage.parseBody(input, this.Body)
|
|
21
|
+
this.fs = fs ?? DBFS.from({})
|
|
22
|
+
this.logger = Logger.from(logger ?? {})
|
|
23
|
+
}
|
|
24
|
+
get Body() {
|
|
25
|
+
return /** @type {typeof Command} */ (this.constructor).Body
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Run the command.
|
|
29
|
+
* @param {string} cmd
|
|
30
|
+
* @param {string[]} [args=[]]
|
|
31
|
+
* @param {string} [fail=""]
|
|
32
|
+
* @returns {Promise<SpawnResult>}
|
|
33
|
+
* @throws {Error}
|
|
34
|
+
*/
|
|
35
|
+
async _run(cmd, args = [], fail = '') {
|
|
36
|
+
const result = await runSpawn(cmd, args)
|
|
37
|
+
if (0 !== result.code) {
|
|
38
|
+
this.logger.error(result.error)
|
|
39
|
+
throw new Error(fail)
|
|
40
|
+
}
|
|
41
|
+
return result
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @returns {AsyncGenerator<OutputMessage>} */
|
|
45
|
+
async *run() {
|
|
46
|
+
throw new Error('Must be extended')
|
|
47
|
+
}
|
|
48
|
+
}
|