@nan0web/release 1.0.1 → 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/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/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
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strictEqual } from "node:assert"
|
|
3
|
-
import MarkdownToTest from "./MarkdownToTest.js"
|
|
4
|
-
|
|
5
|
-
describe.skip("MarkdownToTest", () => {
|
|
6
|
-
it("should generate node:test code from markdown release notes", () => {
|
|
7
|
-
const parser = new MarkdownToTest()
|
|
8
|
-
const markdown = `# v1.1.0 - 2025-08-19
|
|
9
|
-
|
|
10
|
-
## CLI application
|
|
11
|
-
### **Treats project management like code: everything is versioned, auditable, reversible.** [treats-project-management-like-code-everything-is-versioned-auditable-reversible]
|
|
12
|
-
### Draft **No external tools or databases needed — just Git.** [no-external-tools-or-databases-needed-just-git]
|
|
13
|
-
### **Natural for developers; lowers barrier to entry for dev-centric teams.** [natural-for-developers-lowers-barrier-to-entry-for-dev-centric-teams]
|
|
14
|
-
|
|
15
|
-
## UI application
|
|
16
|
-
### **Drag-and-drop interface** [drag-and-drop-interface]
|
|
17
|
-
### **Visual tree structure** [visual-tree-structure]`
|
|
18
|
-
|
|
19
|
-
const expected = `import { describe, it } from "node:test"
|
|
20
|
-
import { ok, strictEqual } from "node:assert"
|
|
21
|
-
|
|
22
|
-
describe("v1.1.0 - 2025-08-19", () => {
|
|
23
|
-
describe("CLI application", () => {
|
|
24
|
-
it("Treats project management like code: everything is versioned, auditable, reversible.", () => {
|
|
25
|
-
ok(true, "Task completed successfully")
|
|
26
|
-
})
|
|
27
|
-
it.skip("No external tools or databases needed — just Git.", () => {
|
|
28
|
-
ok(false, "Draft task - not yet implemented")
|
|
29
|
-
})
|
|
30
|
-
it("Natural for developers; lowers barrier to entry for dev-centric teams.", () => {
|
|
31
|
-
ok(true, "Task completed successfully")
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
describe("UI application", () => {
|
|
35
|
-
it("Drag-and-drop interface", () => {
|
|
36
|
-
ok(true, "Task completed successfully")
|
|
37
|
-
})
|
|
38
|
-
it("Visual tree structure", () => {
|
|
39
|
-
ok(true, "Task completed successfully")
|
|
40
|
-
})
|
|
41
|
-
})
|
|
42
|
-
})`
|
|
43
|
-
|
|
44
|
-
const result = parser.generateTests(markdown)
|
|
45
|
-
strictEqual(result.trim(), expected.trim())
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it("should throw error if markdown doesn't have version header", () => {
|
|
49
|
-
const parser = new MarkdownToTest()
|
|
50
|
-
const markdown = `## CLI application
|
|
51
|
-
### **Treats project management like code** [treats-project-management-like-code]`
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
parser.generateTests(markdown)
|
|
55
|
-
throw new Error("Should have thrown an error")
|
|
56
|
-
} catch (error) {
|
|
57
|
-
strictEqual(error.message, 'Markdown must start with version header: # vX.Y.Z - YYYY-MM-DD')
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
})
|
package/src/ReleaseCLI.js
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from '@nan0web/co'
|
|
4
|
-
import Logger from '@nan0web/log'
|
|
5
|
-
import FS from "@nan0web/db-fs"
|
|
6
|
-
import DB from "./db/ReleaseDB.js"
|
|
7
|
-
import Release from './Release.js'
|
|
8
|
-
import ChatCommand from './commands/ChatCommand.js'
|
|
9
|
-
import HostCommand from './commands/HostCommand.js'
|
|
10
|
-
import InitCommand from './commands/InitCommand.js'
|
|
11
|
-
import ListCommand from './commands/ListCommand.js'
|
|
12
|
-
import SealCommand from './commands/SealCommand.js'
|
|
13
|
-
import ServeCommand from './commands/ServeCommand.js'
|
|
14
|
-
import ShowCommand from './commands/ShowCommand.js'
|
|
15
|
-
import AppCommandMessage from './co/AppCommandMessage.js'
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* CLI application for release management
|
|
19
|
-
*/
|
|
20
|
-
export default class ReleaseCLI extends Command {
|
|
21
|
-
static Message = AppCommandMessage
|
|
22
|
-
/** @type {Logger} */
|
|
23
|
-
logger
|
|
24
|
-
/** @type {DB?} */
|
|
25
|
-
db
|
|
26
|
-
/** @type {FS} */
|
|
27
|
-
fs
|
|
28
|
-
/** @type {Record<string, Release>} */
|
|
29
|
-
releases = {}
|
|
30
|
-
/** @type {Release?} */
|
|
31
|
-
current = null
|
|
32
|
-
|
|
33
|
-
constructor() {
|
|
34
|
-
super({
|
|
35
|
-
name: 'release',
|
|
36
|
-
help: 'Release management CLI tool'
|
|
37
|
-
})
|
|
38
|
-
this.logger = new Logger({ icons: true })
|
|
39
|
-
this.fs = new FS()
|
|
40
|
-
this.db = null
|
|
41
|
-
this.addOption('releaseDir', String, '.', 'Release dir')
|
|
42
|
-
this.addOption('version', String, '0.0.0', 'Release version')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* @param {string[] | string} argv
|
|
47
|
-
* @returns {AppCommandMessage}
|
|
48
|
-
*/
|
|
49
|
-
parse(argv = []) {
|
|
50
|
-
return AppCommandMessage.from(super.parse(argv))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Run CLI application
|
|
55
|
-
* @param {string[]} argv - Command line arguments
|
|
56
|
-
* @returns {Promise<void>}
|
|
57
|
-
*/
|
|
58
|
-
async run(argv) {
|
|
59
|
-
const msg = this.parse(argv)
|
|
60
|
-
await this.fs.connect()
|
|
61
|
-
|
|
62
|
-
const rootDb = this.fs.extract(/** @type {string} */(msg.opts.releaseDir))
|
|
63
|
-
|
|
64
|
-
const indexStat = await rootDb.statDocument("index.js")
|
|
65
|
-
const currentStat = await rootDb.statDocument("current.js")
|
|
66
|
-
if (indexStat.exists && currentStat.exists) {
|
|
67
|
-
const indexJs = rootDb.absolute("index.js")
|
|
68
|
-
const currentJs = rootDb.absolute("current.js")
|
|
69
|
-
try {
|
|
70
|
-
this.releases = (await import(indexJs)).default
|
|
71
|
-
this.current = (await import(currentJs)).default
|
|
72
|
-
this.logger.info(Object.keys(this.releases).length, "release(s) found")
|
|
73
|
-
} catch (e) {
|
|
74
|
-
this.logger.error("Failed to load release database", e)
|
|
75
|
-
this.current = null
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
this.logger.info("No releases initialized. Use 'release init <version>' to create your first release.")
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const globalOpts = { logger: this.logger }
|
|
82
|
-
this.addSubcommand(new ListCommand(globalOpts))
|
|
83
|
-
this.addSubcommand(new HostCommand(globalOpts))
|
|
84
|
-
this.addSubcommand(new ServeCommand(globalOpts))
|
|
85
|
-
|
|
86
|
-
if (this.current) {
|
|
87
|
-
this.logger.info("Current release", this.current.version)
|
|
88
|
-
this.db = DB.from(rootDb.extract(this.current.path))
|
|
89
|
-
await this.db.connect()
|
|
90
|
-
const document = await this.db.loadDocument("release.md")
|
|
91
|
-
const release = new Release({ document, version: this.current.version })
|
|
92
|
-
const currentOpts = { ...globalOpts, db: this.db }
|
|
93
|
-
this.addSubcommand(new InitCommand(currentOpts))
|
|
94
|
-
this.addSubcommand(new ShowCommand(currentOpts))
|
|
95
|
-
this.addSubcommand(new ChatCommand(currentOpts))
|
|
96
|
-
this.addSubcommand(new SealCommand(currentOpts))
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
this.logger.info("Provide a release version")
|
|
100
|
-
this.logger.info(this.generateHelp())
|
|
101
|
-
}
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async loadReleaseFiles() {
|
|
106
|
-
if (!this.db) return
|
|
107
|
-
const stream = this.db.findStream("")
|
|
108
|
-
const started = Date.now()
|
|
109
|
-
for await (const entry of stream) {
|
|
110
|
-
const spent = ((Date.now() - started) / 1000).toFixed(3)
|
|
111
|
-
this.logger.clearLine(this.logger.cursorUp())
|
|
112
|
-
this.logger.info("Loading ..", spent + "s", this.db.data.size, entry.file.path)
|
|
113
|
-
}
|
|
114
|
-
const spent = ((Date.now() - started) / 1000).toFixed(3)
|
|
115
|
-
this.logger.clearLine(this.logger.cursorUp(/** @type {DB} */(this.db).data.size & 1))
|
|
116
|
-
const info = [
|
|
117
|
-
["Loaded in", spent + "s", /** @type {DB} */(this.db).data.size, "document(s)"],
|
|
118
|
-
["", "", /** @type {DB} */(this.db).releases.length, "release(s)"],
|
|
119
|
-
]
|
|
120
|
-
this.logger.table(info, [], { padding: 1 })
|
|
121
|
-
}
|
|
122
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
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
|
-
})
|
|
@@ -1,22 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
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
|
|
@@ -1,63 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import SubCommand from "./SubCommand.js"
|
|
2
|
-
import { mkdir, writeFile } from "node:fs/promises"
|
|
3
|
-
import { join } from "node:path"
|
|
4
|
-
import Release from "../../src/Release.js"
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* SealCommand – finalises a release by writing a `retro.md` file
|
|
8
|
-
* and making the release directory read‑only. This is a *light* implementation
|
|
9
|
-
* that merely creates a placeholder retro file; a real implementation would
|
|
10
|
-
* also set file system permissions and maybe create a Git tag.
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* release seal [--message <text>]
|
|
14
|
-
*
|
|
15
|
-
* Options:
|
|
16
|
-
* --message – optional retro‑reflection text
|
|
17
|
-
*/
|
|
18
|
-
export default class SealCommand extends SubCommand {
|
|
19
|
-
constructor(options = {}) {
|
|
20
|
-
super(options)
|
|
21
|
-
this.addOption("message", String, "", "Retro‑reflection text")
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Seal the current release.
|
|
26
|
-
*
|
|
27
|
-
* @param {Object} ctx – command context
|
|
28
|
-
*/
|
|
29
|
-
async run(ctx) {
|
|
30
|
-
if (!this.db) {
|
|
31
|
-
this.logger.error("No DB – cannot seal release")
|
|
32
|
-
return
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const retroPath = join(this.db.cwd, "retro.md")
|
|
36
|
-
const content = `# Retro – ${new Date().toISOString().split("T")[0]}
|
|
37
|
-
|
|
38
|
-
${ctx.opts.message || "*No retro‑reflection provided.*"}\n`
|
|
39
|
-
|
|
40
|
-
await writeFile(retroPath, content, "utf8")
|
|
41
|
-
this.logger.success(`retro.md written to ${retroPath}`)
|
|
42
|
-
|
|
43
|
-
// make the folder read‑only (best‑effort – ignore errors on non‑POSIX FS)
|
|
44
|
-
try {
|
|
45
|
-
await mkdir(this.db.cwd, { mode: 0o444 })
|
|
46
|
-
this.logger.info("Release directory set to read‑only")
|
|
47
|
-
} catch (_) {
|
|
48
|
-
// not critical – just a hint
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
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
|
-
* ServeCommand – runs a tiny static file server that serves all files
|
|
8
|
-
* inside the current release folder. It is useful for quick local
|
|
9
|
-
* inspection of assets, markdown, and generated test files.
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* release serve [--port <number>]
|
|
13
|
-
*/
|
|
14
|
-
export default class ServeCommand extends SubCommand {
|
|
15
|
-
constructor(options = {}) {
|
|
16
|
-
super(options)
|
|
17
|
-
this.addOption("port", Number, 8080, "Port for static server")
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Start the static server.
|
|
22
|
-
*
|
|
23
|
-
* @param {Object} ctx – command context
|
|
24
|
-
*/
|
|
25
|
-
async run(ctx) {
|
|
26
|
-
if (!this.db) {
|
|
27
|
-
this.logger.error("No DB – cannot serve files")
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const root = this.db.cwd
|
|
32
|
-
const port = ctx.opts.port
|
|
33
|
-
|
|
34
|
-
const server = createServer(async (req, res) => {
|
|
35
|
-
const safePath = req.url?.split("?")[0] || "/"
|
|
36
|
-
const filePath = join(root, safePath)
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
const data = await readFile(filePath)
|
|
40
|
-
res.writeHead(200)
|
|
41
|
-
res.end(data)
|
|
42
|
-
} catch (_) {
|
|
43
|
-
res.writeHead(404)
|
|
44
|
-
res.end("Not found")
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
server.listen(port, () => {
|
|
49
|
-
this.logger.success(`Static server listening on http://localhost:${port}`)
|
|
50
|
-
this.logger.info(`Serving ${root}`)
|
|
51
|
-
})
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import SubCommand from "./SubCommand.js"
|
|
2
|
-
import { readFile } from "node:fs/promises"
|
|
3
|
-
import { join } from "node:path"
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* ShowCommand – prints basic information about the current release
|
|
7
|
-
* (version, creation date, and a short excerpt of the markdown notes).
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* release show
|
|
11
|
-
*
|
|
12
|
-
* Options:
|
|
13
|
-
* --full – dump the whole release.md file
|
|
14
|
-
*/
|
|
15
|
-
export default class ShowCommand extends SubCommand {
|
|
16
|
-
constructor(options = {}) {
|
|
17
|
-
super(options)
|
|
18
|
-
this.addOption("full", Boolean, false, "Show complete release.md")
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Render the release information.
|
|
23
|
-
*
|
|
24
|
-
* @param {Object} ctx – command context
|
|
25
|
-
*/
|
|
26
|
-
async run(ctx) {
|
|
27
|
-
if (!this.db) {
|
|
28
|
-
this.logger.error("No DB – cannot locate current release")
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// `cwd` points to the current release directory (see CLI)
|
|
33
|
-
const releaseJsPath = join(this.db.cwd, "release.js")
|
|
34
|
-
const releaseMdPath = join(this.db.cwd, "release.md")
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const { default: release } = await import(releaseJsPath)
|
|
38
|
-
this.logger.info(`Version : ${release.version}`)
|
|
39
|
-
this.logger.info(`Created at : ${release.createdAt.toISOString()}`)
|
|
40
|
-
this.logger.info(`Path : ${this.db.cwd}`)
|
|
41
|
-
} catch (/** @type {any} */ e) {
|
|
42
|
-
this.logger.warn(`Unable to import release.js – ${e.message}`)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const md = await readFile(releaseMdPath, "utf8")
|
|
47
|
-
if (ctx.opts.full) {
|
|
48
|
-
this.logger.info("\n=== release.md ===\n")
|
|
49
|
-
this.logger.info(md)
|
|
50
|
-
} else {
|
|
51
|
-
const firstLine = md.split("\n")[0]
|
|
52
|
-
this.logger.info(`Release note : ${firstLine}`)
|
|
53
|
-
}
|
|
54
|
-
} catch (/** @type {any} */ e) {
|
|
55
|
-
this.logger.warn(`release.md not found – ${e.message}`)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Command } from '@nan0web/co'
|
|
2
|
-
import ReleaseDB from '../db/ReleaseDB.js'
|
|
3
|
-
import Logger from '@nan0web/log'
|
|
4
|
-
|
|
5
|
-
class SubCommand extends Command {
|
|
6
|
-
/** @type {Logger} */
|
|
7
|
-
logger
|
|
8
|
-
/** @type {ReleaseDB?} */
|
|
9
|
-
db
|
|
10
|
-
constructor(options = {}) {
|
|
11
|
-
const {
|
|
12
|
-
logger = new Logger(),
|
|
13
|
-
db,
|
|
14
|
-
} = options
|
|
15
|
-
super(options)
|
|
16
|
-
this.logger = Logger.from(logger)
|
|
17
|
-
this.db = db ? ReleaseDB.from(db) : null
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default SubCommand
|