@nan0web/release 1.0.0
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/.datasets/README.dataset.jsonl +19 -0
- package/.editorconfig +20 -0
- package/CHANGELOG.md +6 -0
- package/CONTRIBUTING.md +42 -0
- package/LICENSE +15 -0
- package/README.md +284 -0
- package/bin/commands/InitCommand.js +10 -0
- package/bin/lib/functions.js +240 -0
- package/bin/lib/functions.test.js +138 -0
- package/bin/lib/temp/pnpm-lock.yaml +0 -0
- package/bin/nan0release.js +57 -0
- package/bin/release.js +12 -0
- package/bin/release.test.js +73 -0
- package/bin/temp/package.json +1 -0
- package/docs/system.uk.md +413 -0
- package/docs/uk/README.md +284 -0
- package/legacy/PrepareCommand.js +154 -0
- package/legacy/PrepareCommand.test.js +189 -0
- package/legacy/changelog.js +446 -0
- package/package.json +43 -0
- package/releases/1/0/v1.0.0/models/Person.js +26 -0
- package/releases/1/0/v1.0.0/models/Person.test.js +44 -0
- package/releases/1/0/v1.0.0/models/company.js +11 -0
- package/releases/1/0/v1.0.0/models/members.js +14 -0
- package/releases/1/0/v1.0.0/models/members.test.js +21 -0
- package/releases/1/0/v1.0.0/models/teams/DevTeam.js +6 -0
- package/releases/1/0/v1.0.0/models/teams/DevTeam.test.js +15 -0
- package/releases/1/0/v1.0.0/models/teams/index.js +5 -0
- package/releases/1/0/v1.0.0/release.js +12 -0
- package/releases/1/0/v1.0.0/release.md +121 -0
- package/releases/1/0/v1.0.0/release.test.js +36 -0
- package/releases/1/0/v1.0.0/test/YaRaSLove/git-native.test.js +14 -0
- package/releases/current.js +3 -0
- package/releases/index.js +11 -0
- package/src/README.md.js +468 -0
- package/src/Release/Document.js +110 -0
- package/src/Release/MarkdownToTest.js +137 -0
- package/src/Release/MarkdownToTest.test.js +60 -0
- package/src/Release/Person.js +29 -0
- package/src/Release.js +132 -0
- package/src/ReleaseCLI.js +122 -0
- package/src/Team.js +35 -0
- package/src/architecture/ProjectManagementAsCode.js +264 -0
- package/src/architecture/ProjectManagementAsCode.test.js +128 -0
- package/src/co/AppCommandMessage.js +22 -0
- package/src/co/AppCommandOptions.js +27 -0
- package/src/commands/ChatCommand.js +56 -0
- package/src/commands/HostCommand.js +63 -0
- package/src/commands/InitCommand.js +65 -0
- package/src/commands/ListCommand.js +56 -0
- package/src/commands/PublishCommand.js +144 -0
- package/src/commands/SealCommand.js +51 -0
- package/src/commands/ServeCommand.js +53 -0
- package/src/commands/ShowCommand.js +58 -0
- package/src/commands/SubCommand.js +21 -0
- package/src/commands/ValidateCommand.js +66 -0
- package/src/db/ReleaseDB.js +54 -0
- package/system.md +571 -0
- package/tsconfig.json +23 -0
- package/types/Release/Document.d.ts +31 -0
- package/types/Release/MarkdownToTest.d.ts +47 -0
- package/types/Release/Person.d.ts +17 -0
- package/types/Release.d.ts +89 -0
- package/types/ReleaseCLI.d.ts +33 -0
- package/types/Team.d.ts +27 -0
- package/types/architecture/ProjectManagementAsCode.d.ts +159 -0
- package/types/co/AppCommandMessage.d.ts +12 -0
- package/types/co/AppCommandOptions.d.ts +23 -0
- package/types/commands/ChatCommand.d.ts +20 -0
- package/types/commands/HostCommand.d.ts +16 -0
- package/types/commands/InitCommand.d.ts +20 -0
- package/types/commands/ListCommand.d.ts +21 -0
- package/types/commands/PublishCommand.d.ts +49 -0
- package/types/commands/SealCommand.d.ts +21 -0
- package/types/commands/ServeCommand.d.ts +17 -0
- package/types/commands/ShowCommand.d.ts +19 -0
- package/types/commands/SubCommand.d.ts +8 -0
- package/types/commands/ValidateCommand.d.ts +19 -0
- package/types/db/ReleaseDB.d.ts +30 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # Project Management as Code Architecture
|
|
3
|
+
*
|
|
4
|
+
* This architecture implements project management through automated testing,
|
|
5
|
+
* where each task is represented as a test case that validates system behavior.
|
|
6
|
+
*
|
|
7
|
+
* ## Core Concepts
|
|
8
|
+
*
|
|
9
|
+
* 1. **Tasks as Tests**: Each project task becomes a test suite that validates
|
|
10
|
+
* implementation correctness
|
|
11
|
+
* 2. **Status Tracking**: Task status moves automatically through CI/CD pipeline
|
|
12
|
+
* when tests pass/fail
|
|
13
|
+
* 3. **Release Validation**: Releases are validated by running task tests as
|
|
14
|
+
* part of the release process
|
|
15
|
+
*
|
|
16
|
+
* ## Implementation Details
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Base TestSuite class for task management
|
|
21
|
+
*/
|
|
22
|
+
class TestSuite {
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} name - Suite name
|
|
25
|
+
* @param {Function} fn - Test function
|
|
26
|
+
*/
|
|
27
|
+
constructor(name, fn) {
|
|
28
|
+
this.name = name
|
|
29
|
+
this.fn = fn
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Run the test suite
|
|
34
|
+
* @returns {Promise<Object>}
|
|
35
|
+
*/
|
|
36
|
+
async run() {
|
|
37
|
+
// Implementation would integrate with node:test runner
|
|
38
|
+
return { passed: true, duration: 0, assertions: 0 }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Task management through node:test infrastructure
|
|
44
|
+
* @class ProjectManagement
|
|
45
|
+
*/
|
|
46
|
+
export class ProjectManagement {
|
|
47
|
+
/**
|
|
48
|
+
* Initialize project management system
|
|
49
|
+
* @param {Object} [config] - Configuration options
|
|
50
|
+
* @param {string} [config.projectPath] - Path to project root
|
|
51
|
+
* @param {boolean} [config.autoTrack] - Enable automatic status tracking
|
|
52
|
+
*/
|
|
53
|
+
constructor(config = {}) {
|
|
54
|
+
/** @type {string} */
|
|
55
|
+
this.projectPath = config.projectPath || '.'
|
|
56
|
+
/** @type {boolean} */
|
|
57
|
+
this.autoTrack = config.autoTrack || false
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Task registry mapping task IDs to their test files
|
|
61
|
+
* @type {Map<string, string>}
|
|
62
|
+
*/
|
|
63
|
+
this.tasks = new Map()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Register a task with its validation test
|
|
68
|
+
* @param {string} taskId - Unique task identifier
|
|
69
|
+
* @param {string} testFilePath - Path to the test file
|
|
70
|
+
*/
|
|
71
|
+
registerTask(taskId, testFilePath) {
|
|
72
|
+
this.tasks.set(taskId, testFilePath)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Run all registered task tests to determine project status
|
|
77
|
+
* @returns {Promise<Object>} Test results summary
|
|
78
|
+
*/
|
|
79
|
+
async validateProjectState() {
|
|
80
|
+
const results = {
|
|
81
|
+
passed: /** @type {string[]} */([]),
|
|
82
|
+
failed: /** @type {string[]} */([]),
|
|
83
|
+
pending: /** @type {Array<{taskId: string, error: string}>} */([])
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const [taskId, testPath] of this.tasks.entries()) {
|
|
87
|
+
try {
|
|
88
|
+
const testResult = await this.runTaskTest(testPath)
|
|
89
|
+
if (testResult.passed) {
|
|
90
|
+
results.passed.push(taskId)
|
|
91
|
+
} else {
|
|
92
|
+
results.failed.push(taskId)
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
results.pending.push({ taskId, error: /** @type {Error} */(error).message })
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return results
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Execute individual task test
|
|
104
|
+
* @param {string} testPath - Path to test file
|
|
105
|
+
* @returns {Promise<Object>} Test execution result
|
|
106
|
+
*/
|
|
107
|
+
async runTaskTest(testPath) {
|
|
108
|
+
// In real implementation, this would use node:test runner
|
|
109
|
+
// This is a simplified representation
|
|
110
|
+
return {
|
|
111
|
+
passed: true,
|
|
112
|
+
duration: 0,
|
|
113
|
+
assertions: 0
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generate release readiness report
|
|
119
|
+
* @param {string} version - Target version for release
|
|
120
|
+
* @returns {Promise<Object>} Release validation status
|
|
121
|
+
*/
|
|
122
|
+
async validateRelease(version) {
|
|
123
|
+
const projectState = await this.validateProjectState()
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
version,
|
|
127
|
+
timestamp: new Date().toISOString(),
|
|
128
|
+
tasks: {
|
|
129
|
+
total: this.tasks.size,
|
|
130
|
+
completed: projectState.passed.length,
|
|
131
|
+
failed: projectState.failed.length,
|
|
132
|
+
pending: projectState.pending.length
|
|
133
|
+
},
|
|
134
|
+
ready: projectState.failed.length === 0
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Release management system that coordinates task validation
|
|
141
|
+
* @class ReleaseManager
|
|
142
|
+
*/
|
|
143
|
+
export class ReleaseManager {
|
|
144
|
+
/**
|
|
145
|
+
* @type {ProjectManagement}
|
|
146
|
+
*/
|
|
147
|
+
projectManager
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Initialize release manager
|
|
151
|
+
* @param {ProjectManagement} projectManager - Project management instance
|
|
152
|
+
*/
|
|
153
|
+
constructor(projectManager) {
|
|
154
|
+
this.projectManager = projectManager
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Execute release process with task validation
|
|
159
|
+
* @param {string} type - Release type (patch, minor, major)
|
|
160
|
+
* @param {Object} options - Release options
|
|
161
|
+
* @returns {Promise<Object>} Release execution result
|
|
162
|
+
*/
|
|
163
|
+
async executeRelease(type, options = {}) {
|
|
164
|
+
const version = this.calculateVersion(type)
|
|
165
|
+
const validation = await this.projectManager.validateRelease(version)
|
|
166
|
+
|
|
167
|
+
if (!validation.ready && !options.ignoreFailTests) {
|
|
168
|
+
throw new Error(`Release blocked: ${validation.tasks.failed} tasks failed`)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
version,
|
|
173
|
+
validation,
|
|
174
|
+
published: await this.publish(version, options)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Calculate next version based on release type
|
|
180
|
+
* @param {string} type - Release type
|
|
181
|
+
* @returns {string} Next version string
|
|
182
|
+
*/
|
|
183
|
+
calculateVersion(type) {
|
|
184
|
+
// Version calculation logic would be implemented here
|
|
185
|
+
return '1.0.0'
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Publish release to package registry
|
|
190
|
+
* @param {string} version - Version to publish
|
|
191
|
+
* @param {Object} options - Publish options
|
|
192
|
+
* @returns {Promise<boolean>} Publish success status
|
|
193
|
+
*/
|
|
194
|
+
async publish(version, options) {
|
|
195
|
+
// Publishing logic would be implemented here
|
|
196
|
+
return true
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Task test suite definition following node:test format
|
|
202
|
+
* @extends TestSuite
|
|
203
|
+
*/
|
|
204
|
+
export class TaskTestSuite extends TestSuite {
|
|
205
|
+
/**
|
|
206
|
+
* Task ID this test suite validates
|
|
207
|
+
* @type {string}
|
|
208
|
+
*/
|
|
209
|
+
taskId
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Task description for human understanding
|
|
213
|
+
* @type {string}
|
|
214
|
+
*/
|
|
215
|
+
description
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Initialize task test suite
|
|
219
|
+
* @param {string} taskId - Unique task identifier
|
|
220
|
+
* @param {string} description - Human-readable task description
|
|
221
|
+
* @param {Function} testFunction - Test implementation function
|
|
222
|
+
*/
|
|
223
|
+
constructor(taskId, description, testFunction) {
|
|
224
|
+
super(taskId, testFunction)
|
|
225
|
+
this.taskId = taskId
|
|
226
|
+
this.description = description
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Execute task validation test
|
|
231
|
+
* @returns {Promise<Object>} Test execution result
|
|
232
|
+
*/
|
|
233
|
+
async run() {
|
|
234
|
+
// This would integrate with node:test runner infrastructure
|
|
235
|
+
return super.run()
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Changelog-based task management for LLM-assisted development
|
|
241
|
+
* @class ChangelogTaskManager
|
|
242
|
+
*/
|
|
243
|
+
export class ChangelogTaskManager {
|
|
244
|
+
/**
|
|
245
|
+
* Parse changelog to extract tasks and their statuses
|
|
246
|
+
* @param {string} changelogContent - Raw changelog markdown
|
|
247
|
+
* @returns {Array<Object>} Array of task objects
|
|
248
|
+
*/
|
|
249
|
+
parseChangelog(changelogContent) {
|
|
250
|
+
// Parse changelog to extract task definitions
|
|
251
|
+
// This would be implemented using @nan0web/markdown Changelog class
|
|
252
|
+
return []
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Generate task test files from changelog entries
|
|
257
|
+
* @param {Array<Object>} tasks - Array of task objects
|
|
258
|
+
* @returns {Promise<void>}
|
|
259
|
+
*/
|
|
260
|
+
async generateTaskTests(tasks) {
|
|
261
|
+
// Generate node:test files for each task
|
|
262
|
+
// This would create .test.js files in appropriate locations
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import { strict as assert } from 'node:assert'
|
|
3
|
+
import {
|
|
4
|
+
ProjectManagement,
|
|
5
|
+
ReleaseManager,
|
|
6
|
+
TaskTestSuite,
|
|
7
|
+
ChangelogTaskManager
|
|
8
|
+
} from './ProjectManagementAsCode.js'
|
|
9
|
+
|
|
10
|
+
describe('ProjectManagementAsCode Architecture', () => {
|
|
11
|
+
describe('ProjectManagement', () => {
|
|
12
|
+
it('should initialize with default configuration', () => {
|
|
13
|
+
const pm = new ProjectManagement()
|
|
14
|
+
assert.equal(pm.projectPath, '.')
|
|
15
|
+
assert.equal(pm.autoTrack, false)
|
|
16
|
+
assert.ok(pm.tasks instanceof Map)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should register tasks with their test files', () => {
|
|
20
|
+
const pm = new ProjectManagement()
|
|
21
|
+
pm.registerTask('task-1', './tests/task1.test.js')
|
|
22
|
+
pm.registerTask('task-2', './tests/task2.test.js')
|
|
23
|
+
|
|
24
|
+
assert.equal(pm.tasks.size, 2)
|
|
25
|
+
assert.equal(pm.tasks.get('task-1'), './tests/task1.test.js')
|
|
26
|
+
assert.equal(pm.tasks.get('task-2'), './tests/task2.test.js')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should validate project state by running task tests', async () => {
|
|
30
|
+
const pm = new ProjectManagement()
|
|
31
|
+
pm.registerTask('task-1', './tests/task1.test.js')
|
|
32
|
+
pm.registerTask('task-2', './tests/task2.test.js')
|
|
33
|
+
|
|
34
|
+
const results = await pm.validateProjectState()
|
|
35
|
+
assert.ok(results.passed.length >= 0)
|
|
36
|
+
assert.ok(results.failed.length >= 0)
|
|
37
|
+
assert.ok(results.pending.length >= 0)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('ReleaseManager', () => {
|
|
42
|
+
it('should initialize with project manager', () => {
|
|
43
|
+
const pm = new ProjectManagement()
|
|
44
|
+
const rm = new ReleaseManager(pm)
|
|
45
|
+
|
|
46
|
+
assert.equal(rm.projectManager, pm)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it.skip('should validate release with version calculation', async () => {
|
|
50
|
+
const pm = new ProjectManagement()
|
|
51
|
+
const rm = new ReleaseManager(pm)
|
|
52
|
+
|
|
53
|
+
const validation = await rm.validateRelease('1.0.0')
|
|
54
|
+
assert.equal(validation.version, '1.0.0')
|
|
55
|
+
assert.ok(validation.timestamp)
|
|
56
|
+
assert.ok(validation.tasks.total >= 0)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should execute release process with validation', async () => {
|
|
60
|
+
const pm = new ProjectManagement()
|
|
61
|
+
const rm = new ReleaseManager(pm)
|
|
62
|
+
|
|
63
|
+
const result = await rm.executeRelease('patch')
|
|
64
|
+
assert.ok(result.version)
|
|
65
|
+
assert.ok(result.validation)
|
|
66
|
+
assert.equal(typeof result.published, 'boolean')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should block release when tests fail and ignore option is not set', async () => {
|
|
70
|
+
const pm = new ProjectManagement()
|
|
71
|
+
const rm = new ReleaseManager(pm)
|
|
72
|
+
|
|
73
|
+
// Mock validation to simulate failed tasks
|
|
74
|
+
rm.projectManager.validateProjectState = async () => ({
|
|
75
|
+
passed: [],
|
|
76
|
+
failed: ['task-1'],
|
|
77
|
+
pending: []
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
await rm.executeRelease('patch')
|
|
82
|
+
assert.fail('Should have thrown an error')
|
|
83
|
+
} catch (error) {
|
|
84
|
+
assert.ok(error.message.includes('Release blocked'))
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('TaskTestSuite', () => {
|
|
90
|
+
it.skip('should extend TestSuite with task metadata', () => {
|
|
91
|
+
const testFunction = () => { }
|
|
92
|
+
const taskSuite = new TaskTestSuite('task-1', 'Test task description', testFunction)
|
|
93
|
+
|
|
94
|
+
assert.equal(taskSuite.taskId, 'task-1')
|
|
95
|
+
assert.equal(taskSuite.description, 'Test task description')
|
|
96
|
+
assert.equal(taskSuite.testFunction, testFunction)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('ChangelogTaskManager', () => {
|
|
101
|
+
it('should parse changelog content to extract tasks', () => {
|
|
102
|
+
const ctm = new ChangelogTaskManager()
|
|
103
|
+
const changelogContent = `
|
|
104
|
+
# Changelog
|
|
105
|
+
|
|
106
|
+
## [1.0.0] - 2024-01-01
|
|
107
|
+
### Added
|
|
108
|
+
- Initial release features
|
|
109
|
+
- Core functionality implementation
|
|
110
|
+
`
|
|
111
|
+
|
|
112
|
+
const tasks = ctm.parseChangelog(changelogContent)
|
|
113
|
+
assert.ok(Array.isArray(tasks))
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('should generate task tests from changelog entries', async () => {
|
|
117
|
+
const ctm = new ChangelogTaskManager()
|
|
118
|
+
const tasks = [
|
|
119
|
+
{ id: 'task-1', description: 'Implement feature A' },
|
|
120
|
+
{ id: 'task-2', description: 'Fix bug B' }
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
// This should not throw an error
|
|
124
|
+
await ctm.generateTaskTests(tasks)
|
|
125
|
+
assert.ok(true)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CommandMessage } from "@nan0web/co"
|
|
2
|
+
import AppCommandOptions from "./AppCommandOptions.js"
|
|
3
|
+
|
|
4
|
+
export default class AppCommandMessage extends CommandMessage {
|
|
5
|
+
/** @returns {AppCommandOptions} */
|
|
6
|
+
get opts() {
|
|
7
|
+
return /** @type {AppCommandOptions} */ (super.opts)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
set opts(value) {
|
|
11
|
+
super.opts = value
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {*} input
|
|
16
|
+
* @returns {AppCommandMessage}
|
|
17
|
+
*/
|
|
18
|
+
static from(input) {
|
|
19
|
+
if (input instanceof AppCommandMessage) return input
|
|
20
|
+
return new AppCommandMessage(input)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class AppCommandOptions {
|
|
2
|
+
/** @type {boolean} */
|
|
3
|
+
webui = false
|
|
4
|
+
/** @type {boolean} */
|
|
5
|
+
json = false
|
|
6
|
+
/** @type {boolean} */
|
|
7
|
+
quiet = false
|
|
8
|
+
/** @type {string} */
|
|
9
|
+
releaseDir = "./releases"
|
|
10
|
+
/** @type {boolean} */
|
|
11
|
+
write = false
|
|
12
|
+
/** @type {string} */
|
|
13
|
+
user = "anonymous"
|
|
14
|
+
/** @type {boolean} */
|
|
15
|
+
ignoreFailTests = false
|
|
16
|
+
/** @type {boolean} */
|
|
17
|
+
full = false
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {Object} input
|
|
21
|
+
*/
|
|
22
|
+
constructor(input = {}) {
|
|
23
|
+
Object.assign(this, input)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default AppCommandOptions
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import SubCommand from "./SubCommand.js"
|
|
2
|
+
import { appendFile, readFile } from "node:fs/promises"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
import AppCommandMessage from "../co/AppCommandMessage.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ChatCommand – writes a message to the release‑wide chat (chat/…/timestamp.username.md)
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* release chat write "your message"
|
|
11
|
+
*
|
|
12
|
+
* Options:
|
|
13
|
+
* --user <string> – username to embed in the filename (defaults to "anonymous")
|
|
14
|
+
*/
|
|
15
|
+
class ChatCommand extends SubCommand {
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
super(options)
|
|
18
|
+
this.addOption("user", String, "anonymous", "Chat username")
|
|
19
|
+
this.addOption("write", Boolean, false, "Write a new chat line")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Run the chat command.
|
|
24
|
+
*
|
|
25
|
+
* @param {AppCommandMessage} ctx – command context (contains args and opts)
|
|
26
|
+
*/
|
|
27
|
+
async run(ctx) {
|
|
28
|
+
if (!ctx.opts.write) {
|
|
29
|
+
this.logger.error("No action specified – use --write to add a message")
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
if (!this.db) {
|
|
33
|
+
this.logger.error("No DB – cannot locate chat folder")
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const message = ctx.args.join(" ")
|
|
38
|
+
if (!message) {
|
|
39
|
+
this.logger.error("Empty message – nothing to write")
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const now = Date.now()
|
|
44
|
+
const username = /** @type {string} */(ctx.opts.user)
|
|
45
|
+
const chatPath = join(this.db.cwd, "chat", "2025", "08", `${now}.${username}.md`)
|
|
46
|
+
|
|
47
|
+
// ensure directory exists
|
|
48
|
+
const dir = chatPath.split("/").slice(0, -1).join("/")
|
|
49
|
+
await import("node:fs/promises").then(m => m.mkdir(dir, { recursive: true }))
|
|
50
|
+
|
|
51
|
+
await appendFile(chatPath, message + "\n", "utf8")
|
|
52
|
+
this.logger.success(`Message written to ${chatPath}`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default ChatCommand
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import SubCommand from "./SubCommand.js"
|
|
2
|
+
import { createServer } from "node:http"
|
|
3
|
+
import { readFile } from "node:fs/promises"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* HostCommand – starts a minimal HTTP server that serves the current
|
|
8
|
+
* release markdown and a tiny JSON status endpoint.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* release host [--port <number>] [--webui]
|
|
12
|
+
*/
|
|
13
|
+
export default class HostCommand extends SubCommand {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
super(options)
|
|
16
|
+
this.addOption("port", Number, 3000, "Port for the HTTP server")
|
|
17
|
+
this.addOption("webui", Boolean, false, "Serve a simple web UI")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Run the host server.
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} ctx – command context
|
|
24
|
+
*/
|
|
25
|
+
async run(ctx) {
|
|
26
|
+
const port = ctx.opts.port
|
|
27
|
+
const releasePath = this.db?.cwd
|
|
28
|
+
if (!releasePath) {
|
|
29
|
+
this.logger.error("No release directory (db.cwd) – cannot host")
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const mdPath = join(releasePath, "release.md")
|
|
34
|
+
|
|
35
|
+
const server = createServer(async (req, res) => {
|
|
36
|
+
if (req.url === "/") {
|
|
37
|
+
// simple UI – just render markdown as preformatted text
|
|
38
|
+
try {
|
|
39
|
+
const md = await readFile(mdPath, "utf8")
|
|
40
|
+
const body = ctx.opts.webui
|
|
41
|
+
? `<html><body><pre>${md}</pre></body></html>`
|
|
42
|
+
: md
|
|
43
|
+
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" })
|
|
44
|
+
res.end(body)
|
|
45
|
+
} catch (e) {
|
|
46
|
+
res.writeHead(404)
|
|
47
|
+
res.end("release.md not found")
|
|
48
|
+
}
|
|
49
|
+
} else if (req.url === "/status") {
|
|
50
|
+
res.writeHead(200, { "Content-Type": "application/json" })
|
|
51
|
+
res.end(JSON.stringify({ version: ctx.opts.version || "unknown" }))
|
|
52
|
+
} else {
|
|
53
|
+
res.writeHead(404)
|
|
54
|
+
res.end("Not found")
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
server.listen(port, () => {
|
|
59
|
+
this.logger.success(`Release hosted at http://localhost:${port}`)
|
|
60
|
+
if (ctx.opts.webui) this.logger.info("Web UI enabled")
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import SubCommand from "./SubCommand.js"
|
|
2
|
+
import AppCommandMessage from "../co/AppCommandMessage.js"
|
|
3
|
+
import { CommandError } from "@nan0web/co"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* InitCommand – scaffolds a new release directory with the minimal set of
|
|
7
|
+
* files required for a functional release (release.js, release.md and a
|
|
8
|
+
* placeholder task folder). The command is deliberately tiny – it only
|
|
9
|
+
* creates the filesystem layout and writes a tiny starter release object.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* release init v1.0.0
|
|
13
|
+
*/
|
|
14
|
+
class InitCommand extends SubCommand {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
super(options)
|
|
17
|
+
this.addArgument("*", String, "", "Version for the new release")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Run the init process.
|
|
22
|
+
*
|
|
23
|
+
* @param {AppCommandMessage} msg – command context (contains parsed options)
|
|
24
|
+
*/
|
|
25
|
+
async run(msg) {
|
|
26
|
+
if (!this.db) {
|
|
27
|
+
throw new CommandError("Database (working directory) not set")
|
|
28
|
+
}
|
|
29
|
+
const version = msg.args[0]
|
|
30
|
+
if (!version) {
|
|
31
|
+
throw new CommandError("Version not defined", msg)
|
|
32
|
+
}
|
|
33
|
+
const db = this.db.extractVersion(version)
|
|
34
|
+
|
|
35
|
+
const files = [
|
|
36
|
+
["release.js", [
|
|
37
|
+
'import Release from "../../../src/Release.js"',
|
|
38
|
+
'export default new Release({',
|
|
39
|
+
` version: "${version}",`,
|
|
40
|
+
' createdAt: new Date(),',
|
|
41
|
+
' tasks: new Map()',
|
|
42
|
+
'})',
|
|
43
|
+
].join("\n")],
|
|
44
|
+
["release.md", [
|
|
45
|
+
`# ${version} - ${new Date().toISOString().split("T")[0]}`,
|
|
46
|
+
'## Overview',
|
|
47
|
+
'Initial release scaffold generated by `nan0release init`.',
|
|
48
|
+
].join("\n\n")]
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
for (const [file, content] of files) {
|
|
52
|
+
const jsExists = await db.loadDocument(file)
|
|
53
|
+
if (jsExists) {
|
|
54
|
+
this.logger.warn(file, "exists already")
|
|
55
|
+
} else {
|
|
56
|
+
await db.saveDocument(file, content)
|
|
57
|
+
this.logger.success(file, "created")
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.logger.success(`Release ${version} created`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default InitCommand
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import SubCommand from "./SubCommand.js"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
import { createReadStream } from "node:fs"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ListCommand – prints a concise list of releases found in the current
|
|
7
|
+
* working directory. It works against the {@link ReleaseDB} instance
|
|
8
|
+
* passed through the `db` option of the parent CLI.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* release list
|
|
12
|
+
*
|
|
13
|
+
* Options:
|
|
14
|
+
* --json – output JSON instead of a human‑readable list
|
|
15
|
+
*/
|
|
16
|
+
export default class ListCommand extends SubCommand {
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
super(options)
|
|
19
|
+
this.addOption("json", Boolean, false, "Output as JSON")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Execute the command.
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} ctx – command context (contains parsed args)
|
|
26
|
+
* @returns {Promise<void>}
|
|
27
|
+
*/
|
|
28
|
+
async run(ctx) {
|
|
29
|
+
if (!this.db) {
|
|
30
|
+
this.logger.warn("No database attached – nothing to list")
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const releases = []
|
|
35
|
+
for await (const entry of this.db.findStream("")) {
|
|
36
|
+
// Only keep top‑level release directories (e.g. 1/0/v1.0.0)
|
|
37
|
+
const parts = entry.file.path.split("/")
|
|
38
|
+
if (parts.length === 3 && /^\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) {
|
|
39
|
+
releases.push(entry.file.path)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (ctx.opts.json) {
|
|
44
|
+
this.logger.info(JSON.stringify(releases, null, "\t"))
|
|
45
|
+
} else {
|
|
46
|
+
if (releases.length === 0) {
|
|
47
|
+
this.logger.info("No releases found")
|
|
48
|
+
} else {
|
|
49
|
+
this.logger.info("Releases:")
|
|
50
|
+
for (const r of releases) {
|
|
51
|
+
this.logger.info(` • ${r}`)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|