@nan0web/release 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -42
- package/bin/nan0release.js +27 -42
- package/package.json +48 -42
- package/project.yaml +79 -0
- package/src/README.md.js +90 -93
- package/src/Release/Document.js +13 -19
- package/src/Release/MarkdownToTest.js +35 -33
- package/src/Release/Person.js +3 -8
- package/src/Release/Scanner.js +179 -0
- package/src/Release.js +19 -12
- package/src/Team.js +9 -9
- package/src/architecture/ProjectManagementAsCode.js +8 -8
- package/src/cli/index.js +9 -0
- package/src/co/AppCommandOptions.js +4 -6
- package/src/commands/CloseCommand.js +67 -0
- package/src/commands/Command.js +48 -0
- package/src/commands/DepsCommand.js +143 -0
- package/src/commands/InitCommand.js +48 -56
- package/src/commands/PublishCommand.js +80 -119
- package/src/commands/SpecCommand.js +41 -0
- package/src/commands/StatusCommand.js +26 -0
- package/src/db/ReleaseDB.js +5 -5
- package/src/index.js +18 -0
- package/src/ui/cli/ReleaseCLi.js +52 -0
- package/src/utils/runSpawn.js +0 -0
- package/types/Release/Document.d.ts +2 -2
- package/types/Release/Person.d.ts +3 -3
- package/types/Release/Scanner.d.ts +90 -0
- package/types/Release.d.ts +37 -33
- package/types/ReleaseCLI.d.ts +29 -28
- package/types/Team.d.ts +3 -3
- package/types/architecture/ProjectManagementAsCode.d.ts +1 -1
- package/types/cli/index.d.ts +2 -0
- package/types/co/AppCommandMessage.d.ts +10 -11
- package/types/co/AppCommandOptions.d.ts +1 -2
- package/types/commands/BaseCommand.d.ts +23 -0
- package/types/commands/ChatCommand.d.ts +9 -9
- package/types/commands/ChatMessage.d.ts +36 -0
- package/types/commands/CloseCommand.d.ts +25 -0
- package/types/commands/Command.d.ts +28 -0
- package/types/commands/DepsCommand.d.ts +32 -0
- package/types/commands/DepsMessage.d.ts +28 -0
- package/types/commands/HostCommand.d.ts +7 -7
- package/types/commands/HostMessage.d.ts +44 -0
- package/types/commands/InitCommand.d.ts +24 -19
- package/types/commands/InitMessage.d.ts +24 -0
- package/types/commands/ListCommand.d.ts +8 -8
- package/types/commands/ListMessage.d.ts +30 -0
- package/types/commands/PublishCommand.d.ts +36 -47
- package/types/commands/PublishMessage.d.ts +39 -0
- package/types/commands/SealCommand.d.ts +7 -7
- package/types/commands/SealMessage.d.ts +28 -0
- package/types/commands/ServeCommand.d.ts +7 -7
- package/types/commands/ServeMessage.d.ts +30 -0
- package/types/commands/ShowCommand.d.ts +7 -7
- package/types/commands/SpecCommand.d.ts +25 -0
- package/types/commands/StatusCommand.d.ts +9 -0
- package/types/commands/SubCommand.d.ts +9 -7
- package/types/commands/ValidateCommand.d.ts +7 -7
- package/types/commands/ValidateMessage.d.ts +20 -0
- package/types/db/ReleaseDB.d.ts +2 -2
- package/types/index.d.ts +10 -0
- package/types/ui/cli/ReleaseCLi.d.ts +10 -0
- package/types/ui/cli/index.d.ts +0 -0
- package/types/utils/runSpawn.d.ts +0 -0
- package/.datasets/README.dataset.jsonl +0 -19
- package/.editorconfig +0 -20
- package/CHANGELOG.md +0 -6
- package/CONTRIBUTING.md +0 -42
- package/bin/commands/InitCommand.js +0 -10
- package/bin/lib/functions.js +0 -240
- package/bin/lib/functions.test.js +0 -138
- package/bin/release.js +0 -12
- package/bin/release.test.js +0 -73
- package/bin/temp/package.json +0 -1
- package/docs/system.uk.md +0 -413
- package/docs/uk/README.md +0 -284
- package/legacy/PrepareCommand.js +0 -154
- package/legacy/PrepareCommand.test.js +0 -189
- package/legacy/changelog.js +0 -446
- package/releases/1/0/v1.0.0/models/Person.js +0 -26
- package/releases/1/0/v1.0.0/models/Person.test.js +0 -44
- package/releases/1/0/v1.0.0/models/company.js +0 -11
- package/releases/1/0/v1.0.0/models/members.js +0 -14
- package/releases/1/0/v1.0.0/models/members.test.js +0 -21
- package/releases/1/0/v1.0.0/models/teams/DevTeam.js +0 -6
- package/releases/1/0/v1.0.0/models/teams/DevTeam.test.js +0 -15
- package/releases/1/0/v1.0.0/models/teams/index.js +0 -5
- package/releases/1/0/v1.0.0/release.js +0 -12
- package/releases/1/0/v1.0.0/release.md +0 -121
- package/releases/1/0/v1.0.0/release.test.js +0 -36
- package/releases/1/0/v1.0.0/test/YaRaSLove/git-native.test.js +0 -14
- package/releases/current.js +0 -3
- package/releases/index.js +0 -11
- package/src/Release/MarkdownToTest.test.js +0 -60
- package/src/ReleaseCLI.js +0 -122
- package/src/architecture/ProjectManagementAsCode.test.js +0 -128
- package/src/co/AppCommandMessage.js +0 -22
- package/src/commands/ChatCommand.js +0 -56
- package/src/commands/HostCommand.js +0 -63
- package/src/commands/ListCommand.js +0 -56
- package/src/commands/SealCommand.js +0 -51
- package/src/commands/ServeCommand.js +0 -53
- package/src/commands/ShowCommand.js +0 -58
- package/src/commands/SubCommand.js +0 -21
- package/src/commands/ValidateCommand.js +0 -66
- package/system.md +0 -571
- package/tsconfig.json +0 -23
- /package/{bin/lib/temp/pnpm-lock.yaml → src/ui/cli/index.js} +0 -0
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# v1.0.0 - 2025-08-18
|
|
2
|
-
|
|
3
|
-
## PM as a code
|
|
4
|
-
|
|
5
|
-
Project management as a code is real if you believe and do this way.
|
|
6
|
-
|
|
7
|
-
### Team
|
|
8
|
-
|
|
9
|
-
#### developer
|
|
10
|
-
|
|
11
|
-
- YaRaSLove (ЯRаСлав) <support@yaro.page>
|
|
12
|
-
|
|
13
|
-
#### assistant
|
|
14
|
-
|
|
15
|
-
- Qwen-3-coder-480b <https://api.cerebras.ai/>
|
|
16
|
-
|
|
17
|
-
## CLI application
|
|
18
|
-
|
|
19
|
-
### 1. Git-Native & Version-Controlled Workflow [](./task/git-native.test.js)
|
|
20
|
-
|
|
21
|
-
- Treats project management like code: everything is versioned, auditable, reversible.
|
|
22
|
-
- No external tools or databases needed — just Git.
|
|
23
|
-
- Natural for developers; lowers barrier to entry for dev-centric teams.
|
|
24
|
-
|
|
25
|
-
#### Acceptance Criteria
|
|
26
|
-
|
|
27
|
-
- MUST verify that `git status` does not show any `releases/0/0/v0.0.0/*` files;
|
|
28
|
-
- MUST create all required files according to the structure:
|
|
29
|
-
```text
|
|
30
|
-
release.md ← Release notes (tasks, sections)
|
|
31
|
-
release.js ← Team, roles, config (class Release)
|
|
32
|
-
release.test.js ← Main test suite (CI entrypoint)
|
|
33
|
-
test/
|
|
34
|
-
{username}/ ← Optional scope
|
|
35
|
-
tasks.test.js ← User-specific tests
|
|
36
|
-
chat/ ← Release-wide chat
|
|
37
|
-
2025/08/18/
|
|
38
|
-
162000420.{username}.md ← Message (timestamp + user)
|
|
39
|
-
task/
|
|
40
|
-
ux.logo/ ← Task by slug
|
|
41
|
-
config.json ← { assignee, priority, dependsOn }
|
|
42
|
-
approved/
|
|
43
|
-
ceo.json ← Approval record (GPG-signed)
|
|
44
|
-
chat/
|
|
45
|
-
2025/08/18/
|
|
46
|
-
162000420.{username}.md ← Task-specific message
|
|
47
|
-
assets/ ← Designs, docs, deliverables
|
|
48
|
-
retro.md ← Final reflection (sealed)
|
|
49
|
-
.cache/ ← gitignored: read state, session
|
|
50
|
-
```
|
|
51
|
-
- MUST confirm with `git status` which files are ignored and which are tracked.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
### 2. Test-Driven Project Progress [](./test/YaRaSLove/test-driven.test.js)
|
|
56
|
-
- Tasks become testable units → Pass/fail defines completion.
|
|
57
|
-
- Automates status tracking via CI/CD: "Done" = test passes.
|
|
58
|
-
- Enables **automated QA gates in releases** — powerful for regulated environments.
|
|
59
|
-
|
|
60
|
-
#### Acceptance Criteria
|
|
61
|
-
- MUST write `release.md` inside the test folder `releases/0/0/v0.0.0/release.md` using proper formatting;
|
|
62
|
-
- MUST initialize the release with `nan0release init v0.0.0`;
|
|
63
|
-
- MUST check the generated tests and compare them against expected templates;
|
|
64
|
-
- MUST prepare tests generated from complete releases notes markdown file with command `nan0release prepare v0.0.0`
|
|
65
|
-
- MUST begin the workflow: all tests start in `todo`, transition states by modifying files and executing tests, validate each state (`todo`, `in progress`, `complete`, `approved`), verify release status and confirm `git tag` commands are executed once all tasks complete.
|
|
66
|
-
- MUST execute `nan0release [status] v0.0.0`
|
|
67
|
-
- MUST execute `nan0release todo v0.0.0`
|
|
68
|
-
- MUST execute `nan0release validate v0.0.0`
|
|
69
|
-
- MUST execute `nan0release seal v0.0.0`
|
|
70
|
-
|
|
71
|
-
### 3. Transparent & Predictable Progress Tracking [](./test/YaRaSLove/progress-tracking.test.js)
|
|
72
|
-
- Real-time progress %, ETA, and velocity derived from commit history.
|
|
73
|
-
- Immediate visibility through stats (pending/pass/fail).
|
|
74
|
-
- Eliminates manual updates in Jira/Trello → reduces noise and inaccuracies.
|
|
75
|
-
|
|
76
|
-
#### Acceptance Criteria
|
|
77
|
-
- MUST validate updates occur upon commits (these may be simulated or run within an isolated temporary `.git` environment); ensure CI/CD generates reports which are both testable and consumable by UIs.
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
### 4. Integrated Team Communication [](./test/YaRaSLove/chat.test.js)
|
|
82
|
-
- Structured chat per release and task embedded directly in file system.
|
|
83
|
-
- Chronologically ordered, searchable, and version-controlled conversations.
|
|
84
|
-
- `.cache` supports user-specific read/unread states without requiring a central server.
|
|
85
|
-
|
|
86
|
-
#### Acceptance Criteria
|
|
87
|
-
|
|
88
|
-
- MUST test by verifying created files after running the following commands:
|
|
89
|
-
- `nan0release chat write "msg"`
|
|
90
|
-
- `nan0release chat write ux.logo "help needed"`
|
|
91
|
-
- `nan0release chat read`
|
|
92
|
-
- `nan0release chat find "help"`
|
|
93
|
-
- `nan0release chat --sender member@example.com`
|
|
94
|
-
- `nan0release chat unread`
|
|
95
|
-
- MUST test both output and resulting file content.
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
### 5. Self-Documenting Releases [](./test/YaRaSLove/docs.test.js)
|
|
100
|
-
|
|
101
|
-
- Structured, machine-readable release notes, always current.
|
|
102
|
-
- Markdown enhanced with YAML frontmatter enables metadata (assignees, dates, etc.).
|
|
103
|
-
- Facilitates automatic generation of changelogs, roadmaps, dashboards.
|
|
104
|
-
|
|
105
|
-
#### Acceptance Criteria
|
|
106
|
-
|
|
107
|
-
- MUST validate success and failure outcomes of `nan0release validate` when processing valid and invalid release note formats. It must exit with status code 1 on a failure.
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
|
-
### 6. Extensible & Composable & Office-First & Decentralized by Design [ci-cd.test.js]
|
|
112
|
-
- Assets such as designs, configurations, and logs stored within the release for full context.
|
|
113
|
-
- Supports custom scripts, hooks, templates (e.g., `pre-release.js`, `post-release.sh`).
|
|
114
|
-
- Seamlessly integrates into CI/CD pipelines.
|
|
115
|
-
- Works offline and independently of external services.
|
|
116
|
-
- No vendor lock-in — all data remains in the repository.
|
|
117
|
-
|
|
118
|
-
#### Acceptance Criteria
|
|
119
|
-
|
|
120
|
-
- MUST confirm context preservation by adding test files to assets and validating output of `git status`.
|
|
121
|
-
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import { strict as assert } from "node:assert"
|
|
3
|
-
|
|
4
|
-
import Release from "./release.js"
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @todo
|
|
8
|
-
* It must validate the structure of every release.
|
|
9
|
-
* It must be in src/Release.validate().
|
|
10
|
-
* It must validate loaded markdown into document with the file structure
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
describe("Release v1.0.0", () => {
|
|
14
|
-
it("has a valid static version", () => {
|
|
15
|
-
assert.equal(Release.version, "v1.0.0")
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it("has a valid static date", () => {
|
|
19
|
-
assert.equal(Release.createdAt, "2025-08-18")
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it("references a valid Company with name", () => {
|
|
23
|
-
assert.ok(Release.company)
|
|
24
|
-
assert.equal(Release.company.name, "NaN•Web")
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it("has a DevTeam defined in teams", () => {
|
|
28
|
-
assert.ok(Release.teams.DevTeam)
|
|
29
|
-
assert.equal(Release.teams.DevTeam.name, "Software development team")
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it("includes all core models: Person, Contact, Message", () => {
|
|
33
|
-
assert.ok(typeof Person !== 'undefined' || typeof Release.members.YaRaSLove !== 'undefined')
|
|
34
|
-
// Note: these would be imported if external deps existed
|
|
35
|
-
})
|
|
36
|
-
})
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test"
|
|
2
|
-
import assert from "node:assert/strict"
|
|
3
|
-
|
|
4
|
-
describe("Git-Native & Version-Controlled Workflow", () => {
|
|
5
|
-
it("Treats project management like code: everything is versioned, auditable, reversible.", () => {
|
|
6
|
-
|
|
7
|
-
})
|
|
8
|
-
it("No external tools or databases needed — just Git.", () => {
|
|
9
|
-
|
|
10
|
-
})
|
|
11
|
-
it("Natural for developers; lowers barrier to entry for dev-centric teams.", () => {
|
|
12
|
-
|
|
13
|
-
})
|
|
14
|
-
})
|
package/releases/current.js
DELETED
package/releases/index.js
DELETED
|
@@ -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
|
-
}
|