@nan0web/release 1.0.0 → 1.0.2
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 +21 -14
- 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/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 +1 -1
- 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/README.md.js +0 -468
- 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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DepsCommand – Message‑based version of the old `DepsCommand`.
|
|
3
|
+
*
|
|
4
|
+
* Checks and optionally fixes workspace / link dependencies.
|
|
5
|
+
*
|
|
6
|
+
* Body schema:
|
|
7
|
+
* fix – boolean – whether to apply fixes (default: false)
|
|
8
|
+
*
|
|
9
|
+
* @module release/commands/DepsCommand
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Message, OutputMessage } from '@nan0web/co'
|
|
13
|
+
|
|
14
|
+
import Command from './Command.js'
|
|
15
|
+
|
|
16
|
+
class DepsBody {
|
|
17
|
+
/** @type {boolean} */
|
|
18
|
+
fix = false
|
|
19
|
+
static fix = {
|
|
20
|
+
help: 'Apply fixes automatically',
|
|
21
|
+
defaultValue: false,
|
|
22
|
+
}
|
|
23
|
+
/** @type {boolean} */
|
|
24
|
+
latest = false
|
|
25
|
+
static latest = {
|
|
26
|
+
help: 'Update @nan0web packages to the latests available versions',
|
|
27
|
+
defaultValue: false,
|
|
28
|
+
}
|
|
29
|
+
constructor(input = {}) {
|
|
30
|
+
const { fix = this.fix, latest = this.latest } = Message.parseBody(input, DepsBody)
|
|
31
|
+
this.fix = Boolean(fix)
|
|
32
|
+
this.latest = Boolean(latest)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default class DepsCommand extends Command {
|
|
37
|
+
static name = 'deps'
|
|
38
|
+
static help = 'Dependencies manager'
|
|
39
|
+
static Body = DepsBody
|
|
40
|
+
/** @type {DepsBody} */
|
|
41
|
+
body
|
|
42
|
+
|
|
43
|
+
/** @param {Partial<DepsBody>} [input={}] */
|
|
44
|
+
constructor(input = {}) {
|
|
45
|
+
super({})
|
|
46
|
+
this.body = Command.parseBody(input, DepsBody)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {DepsCommand} [msg]
|
|
51
|
+
* @returns {AsyncGenerator<OutputMessage>}
|
|
52
|
+
*/
|
|
53
|
+
async *run(msg = new DepsCommand()) {
|
|
54
|
+
const pkg = await this.fs.loadDocument('package.json', {})
|
|
55
|
+
yield new OutputMessage(['Loaded package.json', JSON.stringify(pkg).length + ' bytes'])
|
|
56
|
+
const deps = {
|
|
57
|
+
devDependencies: pkg?.devDependencies ?? {},
|
|
58
|
+
dependencies: pkg?.dependencies ?? {},
|
|
59
|
+
peerDependencies: pkg?.peerDependencies ?? {},
|
|
60
|
+
}
|
|
61
|
+
const fixes = new Map()
|
|
62
|
+
const fails = new Set()
|
|
63
|
+
|
|
64
|
+
const isLocal = (ver) =>
|
|
65
|
+
['workspace:', 'link:'].some((p) => String(ver).toLowerCase().startsWith(p))
|
|
66
|
+
|
|
67
|
+
const changes = new Map()
|
|
68
|
+
|
|
69
|
+
for (const [group, map] of Object.entries(deps)) {
|
|
70
|
+
yield new OutputMessage(`${group} (${Object.keys(map).length} items)`)
|
|
71
|
+
for (const [dep, ver] of Object.entries(map ?? {})) {
|
|
72
|
+
if (isLocal(ver) || msg.body.latest) {
|
|
73
|
+
const result = await this._run(
|
|
74
|
+
'npm',
|
|
75
|
+
['info', dep, '--json'],
|
|
76
|
+
'Cannot retrieve npm package info',
|
|
77
|
+
)
|
|
78
|
+
try {
|
|
79
|
+
const data = JSON.parse(result.text)
|
|
80
|
+
if (data.version) {
|
|
81
|
+
fixes.set(dep, data.version)
|
|
82
|
+
yield new OutputMessage({
|
|
83
|
+
content: [` ${dep}: ${ver} → ${data.version}`],
|
|
84
|
+
type: OutputMessage.TYPES.WARNING,
|
|
85
|
+
})
|
|
86
|
+
} else {
|
|
87
|
+
fails.add(dep)
|
|
88
|
+
yield new OutputMessage({
|
|
89
|
+
content: [` ${dep}: 404 — not found`],
|
|
90
|
+
type: OutputMessage.TYPES.ERROR,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
} catch (/** @type {any} */ err) {
|
|
94
|
+
yield new OutputMessage(err)
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
yield new OutputMessage({
|
|
98
|
+
content: [` ${dep}: ${ver}`],
|
|
99
|
+
type: OutputMessage.TYPES.SUCCESS,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (fails.size) {
|
|
106
|
+
yield new OutputMessage(
|
|
107
|
+
new Error(`Some dependencies are not yet published on npm: ${[...fails].join(', ')}`),
|
|
108
|
+
)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if ((msg.body.fix || msg.body.latest) && fixes.size) {
|
|
113
|
+
for (const [dep, ver] of fixes) {
|
|
114
|
+
// naive fix – replace in package.json
|
|
115
|
+
const args = ['install']
|
|
116
|
+
if (pkg.devDependencies?.[dep]) {
|
|
117
|
+
pkg.devDependencies[dep] = ver
|
|
118
|
+
args.push('--save-dev')
|
|
119
|
+
}
|
|
120
|
+
if (pkg.dependencies?.[dep]) pkg.dependencies[dep] = ver
|
|
121
|
+
args.push(dep + '@latest')
|
|
122
|
+
if (msg.body.latest) {
|
|
123
|
+
yield new OutputMessage([`Installing dependency`])
|
|
124
|
+
await this._run('npm', args)
|
|
125
|
+
}
|
|
126
|
+
changes.set(dep, `${dep}: ${ver}`)
|
|
127
|
+
yield new OutputMessage({
|
|
128
|
+
content: [`Fixed ${dep} → ${ver}`],
|
|
129
|
+
type: OutputMessage.TYPES.SUCCESS,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
await this.fs.saveDocument('package.json', pkg)
|
|
133
|
+
yield new OutputMessage({
|
|
134
|
+
content: [`Fixes saved (${changes.size} changes)`],
|
|
135
|
+
type: OutputMessage.TYPES.SUCCESS,
|
|
136
|
+
})
|
|
137
|
+
yield new OutputMessage({ content: ['Dependencies fixed'] })
|
|
138
|
+
} else {
|
|
139
|
+
yield new OutputMessage(`Run with --fix to apply ${fixes.size} available fixes`)
|
|
140
|
+
yield new OutputMessage({ content: ['Dependencies check completed'] })
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|