@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.
Files changed (108) hide show
  1. package/README.md +24 -42
  2. package/bin/nan0release.js +27 -42
  3. package/package.json +21 -14
  4. package/src/Release/Document.js +13 -19
  5. package/src/Release/MarkdownToTest.js +35 -33
  6. package/src/Release/Person.js +3 -8
  7. package/src/Release/Scanner.js +179 -0
  8. package/src/Release.js +19 -12
  9. package/src/Team.js +9 -9
  10. package/src/architecture/ProjectManagementAsCode.js +8 -8
  11. package/src/cli/index.js +9 -0
  12. package/src/co/AppCommandOptions.js +4 -6
  13. package/src/commands/CloseCommand.js +67 -0
  14. package/src/commands/Command.js +48 -0
  15. package/src/commands/DepsCommand.js +143 -0
  16. package/src/commands/InitCommand.js +48 -56
  17. package/src/commands/PublishCommand.js +80 -119
  18. package/src/commands/SpecCommand.js +41 -0
  19. package/src/commands/StatusCommand.js +26 -0
  20. package/src/db/ReleaseDB.js +5 -5
  21. package/src/index.js +18 -0
  22. package/src/ui/cli/ReleaseCLi.js +52 -0
  23. package/src/utils/runSpawn.js +0 -0
  24. package/types/Release/Document.d.ts +2 -2
  25. package/types/Release/Person.d.ts +3 -3
  26. package/types/Release/Scanner.d.ts +90 -0
  27. package/types/Release.d.ts +37 -33
  28. package/types/ReleaseCLI.d.ts +29 -28
  29. package/types/Team.d.ts +3 -3
  30. package/types/architecture/ProjectManagementAsCode.d.ts +1 -1
  31. package/types/cli/index.d.ts +2 -0
  32. package/types/co/AppCommandMessage.d.ts +10 -11
  33. package/types/co/AppCommandOptions.d.ts +1 -2
  34. package/types/commands/BaseCommand.d.ts +23 -0
  35. package/types/commands/ChatCommand.d.ts +9 -9
  36. package/types/commands/ChatMessage.d.ts +36 -0
  37. package/types/commands/CloseCommand.d.ts +25 -0
  38. package/types/commands/Command.d.ts +28 -0
  39. package/types/commands/DepsCommand.d.ts +32 -0
  40. package/types/commands/DepsMessage.d.ts +28 -0
  41. package/types/commands/HostCommand.d.ts +7 -7
  42. package/types/commands/HostMessage.d.ts +44 -0
  43. package/types/commands/InitCommand.d.ts +24 -19
  44. package/types/commands/InitMessage.d.ts +24 -0
  45. package/types/commands/ListCommand.d.ts +8 -8
  46. package/types/commands/ListMessage.d.ts +30 -0
  47. package/types/commands/PublishCommand.d.ts +36 -47
  48. package/types/commands/PublishMessage.d.ts +39 -0
  49. package/types/commands/SealCommand.d.ts +7 -7
  50. package/types/commands/SealMessage.d.ts +28 -0
  51. package/types/commands/ServeCommand.d.ts +7 -7
  52. package/types/commands/ServeMessage.d.ts +30 -0
  53. package/types/commands/ShowCommand.d.ts +7 -7
  54. package/types/commands/SpecCommand.d.ts +25 -0
  55. package/types/commands/StatusCommand.d.ts +9 -0
  56. package/types/commands/SubCommand.d.ts +9 -7
  57. package/types/commands/ValidateCommand.d.ts +7 -7
  58. package/types/commands/ValidateMessage.d.ts +20 -0
  59. package/types/db/ReleaseDB.d.ts +2 -2
  60. package/types/index.d.ts +10 -0
  61. package/types/ui/cli/ReleaseCLi.d.ts +10 -0
  62. package/types/ui/cli/index.d.ts +0 -0
  63. package/types/utils/runSpawn.d.ts +0 -0
  64. package/.datasets/README.dataset.jsonl +0 -19
  65. package/.editorconfig +0 -20
  66. package/CHANGELOG.md +0 -6
  67. package/CONTRIBUTING.md +0 -42
  68. package/bin/commands/InitCommand.js +0 -10
  69. package/bin/lib/functions.js +0 -240
  70. package/bin/lib/functions.test.js +0 -138
  71. package/bin/release.js +0 -12
  72. package/bin/release.test.js +0 -73
  73. package/bin/temp/package.json +0 -1
  74. package/docs/system.uk.md +0 -413
  75. package/docs/uk/README.md +0 -284
  76. package/legacy/PrepareCommand.js +0 -154
  77. package/legacy/PrepareCommand.test.js +0 -189
  78. package/legacy/changelog.js +0 -446
  79. package/releases/1/0/v1.0.0/models/Person.js +0 -26
  80. package/releases/1/0/v1.0.0/models/Person.test.js +0 -44
  81. package/releases/1/0/v1.0.0/models/company.js +0 -11
  82. package/releases/1/0/v1.0.0/models/members.js +0 -14
  83. package/releases/1/0/v1.0.0/models/members.test.js +0 -21
  84. package/releases/1/0/v1.0.0/models/teams/DevTeam.js +0 -6
  85. package/releases/1/0/v1.0.0/models/teams/DevTeam.test.js +0 -15
  86. package/releases/1/0/v1.0.0/models/teams/index.js +0 -5
  87. package/releases/1/0/v1.0.0/release.js +0 -12
  88. package/releases/1/0/v1.0.0/release.md +0 -121
  89. package/releases/1/0/v1.0.0/release.test.js +0 -36
  90. package/releases/1/0/v1.0.0/test/YaRaSLove/git-native.test.js +0 -14
  91. package/releases/current.js +0 -3
  92. package/releases/index.js +0 -11
  93. package/src/README.md.js +0 -468
  94. package/src/Release/MarkdownToTest.test.js +0 -60
  95. package/src/ReleaseCLI.js +0 -122
  96. package/src/architecture/ProjectManagementAsCode.test.js +0 -128
  97. package/src/co/AppCommandMessage.js +0 -22
  98. package/src/commands/ChatCommand.js +0 -56
  99. package/src/commands/HostCommand.js +0 -63
  100. package/src/commands/ListCommand.js +0 -56
  101. package/src/commands/SealCommand.js +0 -51
  102. package/src/commands/ServeCommand.js +0 -53
  103. package/src/commands/ShowCommand.js +0 -58
  104. package/src/commands/SubCommand.js +0 -21
  105. package/src/commands/ValidateCommand.js +0 -66
  106. package/system.md +0 -571
  107. package/tsconfig.json +0 -23
  108. /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