@nan0web/release 3.1.1 → 3.2.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.
@@ -0,0 +1,81 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import FS from '@nan0web/db-fs'
4
+ import { existsSync, writeFileSync, unlinkSync, mkdirSync, readFileSync } from 'node:fs'
5
+ import { resolve, dirname } from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
7
+
8
+ import main from './release.js'
9
+
10
+ const __filename = fileURLToPath(import.meta.url)
11
+ const __dirname = dirname(__filename)
12
+ const tempDir = resolve(__dirname, 'temp')
13
+ const tempPackageJson = resolve(tempDir, 'package.json')
14
+
15
+ const fs = new FS()
16
+
17
+ describe('release.js', async () => {
18
+ await fs.connect()
19
+ const tempDir = 'tmp/release'
20
+ const db = fs.extract(tempDir)
21
+ await db.writeDocument('package.json', {
22
+ name: 'test-package',
23
+ version: '0.0.1-test',
24
+ })
25
+
26
+ // Mock process.cwd to return tempDir
27
+ const originalCwd = process.cwd
28
+ process.cwd = () => tempDir
29
+
30
+ it('main function processes release command', async () => {
31
+ // Test with ignore flags to avoid actual git operations
32
+ const result = await main(['--ignore-uncommitted', '--ignore-fail-tests', '--ignore-tag'])
33
+ assert.ok(result.includes('Release completed'))
34
+ })
35
+
36
+ it('main function processes prepare command', async () => {
37
+ const resultP = await main(['prepare'])
38
+ assert.ok(result.includes('Prepare completed'))
39
+ })
40
+
41
+ it('ReleaseCommand handles package.json validation', async () => {
42
+ // Create a package.json without required scripts
43
+ writeFileSync(
44
+ tempPackageJson,
45
+ JSON.stringify({
46
+ name: 'test-package',
47
+ version: '0.0.1-test',
48
+ }),
49
+ )
50
+
51
+ // Should exit with error when missing scripts
52
+ try {
53
+ await main(['--ignore-uncommitted', '--ignore-fail-tests', '--ignore-tag'])
54
+ assert.fail('Should have exited with error')
55
+ } catch (err) {
56
+ assert.ok(err.message.includes('Missing required scripts'))
57
+ }
58
+
59
+ // Should fix when --fix flag is used
60
+ const result = await main([
61
+ '--ignore-uncommitted',
62
+ '--ignore-fail-tests',
63
+ '--ignore-tag',
64
+ '--fix',
65
+ ])
66
+ assert.ok(result.includes('Release completed'))
67
+
68
+ // Verify package.json was fixed
69
+ const pkg = JSON.parse(readFileSync(tempPackageJson, 'utf8'))
70
+ assert.ok(pkg.scripts.dev)
71
+ assert.ok(pkg.scripts.build)
72
+ assert.ok(pkg.scripts.preview)
73
+ assert.ok(pkg.scripts.test)
74
+ assert.ok(pkg.scripts.clean)
75
+ assert.ok(pkg.devDependencies.husky)
76
+ })
77
+
78
+ // Cleanup
79
+ process.cwd = originalCwd
80
+ await db.dropDocument('package.json')
81
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nan0web/release",
3
- "version": "3.1.1",
3
+ "version": "3.2.0",
4
4
  "description": "Project Management as Code, Git-Native, GPG-Signed, Test-Driven",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -10,23 +10,31 @@
10
10
  "release": "./bin/nan0release.js"
11
11
  },
12
12
  "dependencies": {
13
- "@nan0web/co": "3.1.0",
14
- "@nan0web/log": "3.1.0",
15
- "@nan0web/db-fs": "3.1.0",
16
- "@nan0web/types": "3.1.0",
17
- "@nan0web/db": "3.1.0",
18
- "@nan0web/ui": "3.1.1",
19
- "@nan0web/markdown": "3.1.0",
20
- "@nan0web/ui-cli": "3.1.1",
21
- "@nan0web/changelog": "3.1.0",
22
- "@nan0web/logos": "3.1.0"
13
+ "@nan0web/co": "3.2.0",
14
+ "@nan0web/db": "3.2.0",
15
+ "@nan0web/ui": "3.2.0",
16
+ "@nan0web/types": "3.2.0",
17
+ "@nan0web/db-fs": "3.2.0",
18
+ "@nan0web/log": "3.2.0",
19
+ "@nan0web/markdown": "3.2.0",
20
+ "@nan0web/changelog": "3.2.0",
21
+ "@nan0web/ui-cli": "3.2.0",
22
+ "@nan0web/logos": "3.2.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^25.6.0",
26
26
  "husky": "^9.1.7",
27
27
  "knip": "^5.86.0",
28
- "@nan0web/test": "3.1.0"
28
+ "@nan0web/test": "3.2.0"
29
29
  },
30
+ "files": [
31
+ "bin",
32
+ "src",
33
+ "types",
34
+ "data",
35
+ "LICENSE",
36
+ "README.md"
37
+ ],
30
38
  "scripts": {
31
39
  "build": "tsc",
32
40
  "clean": "rm -rf .cache/ && rm -rf dist/",
@@ -0,0 +1,60 @@
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
+ })
@@ -0,0 +1,91 @@
1
+ import { describe, it, beforeEach, afterEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs'
4
+ import { join } from 'node:path'
5
+ import { tmpdir } from 'node:os'
6
+
7
+ import Scanner from './Scanner.js'
8
+
9
+ const TMP = join(tmpdir(), 'nan0release-close-test-' + Date.now())
10
+
11
+ describe('Scanner.close()', () => {
12
+ beforeEach(() => {
13
+ mkdirSync(TMP, { recursive: true })
14
+ })
15
+
16
+ afterEach(() => {
17
+ rmSync(TMP, { recursive: true, force: true })
18
+ })
19
+
20
+ it('renames spec → test for a specific version', () => {
21
+ const dir = join(TMP, 'releases/2/0/v2.0.0')
22
+ mkdirSync(dir, { recursive: true })
23
+ writeFileSync(join(dir, 'task.spec.js'), '// contract')
24
+
25
+ const scanner = new Scanner(TMP)
26
+ const result = scanner.close('v2.0.0')
27
+
28
+ assert.equal(result.closed, 1)
29
+ assert.ok(!existsSync(join(dir, 'task.spec.js')), 'spec should be gone')
30
+ assert.ok(existsSync(join(dir, 'task.test.js')), 'test should exist')
31
+ })
32
+
33
+ it('renames multiple spec files in the same version', () => {
34
+ const dir = join(TMP, 'releases/2/0/v2.0.0')
35
+ mkdirSync(dir, { recursive: true })
36
+ writeFileSync(join(dir, 'task.spec.js'), '// contract 1')
37
+ writeFileSync(join(dir, 'api.spec.js'), '// contract 2')
38
+
39
+ const scanner = new Scanner(TMP)
40
+ const result = scanner.close('v2.0.0')
41
+
42
+ assert.equal(result.closed, 2)
43
+ assert.ok(existsSync(join(dir, 'task.test.js')))
44
+ assert.ok(existsSync(join(dir, 'api.test.js')))
45
+ })
46
+
47
+ it('returns 0 when no specs found for version', () => {
48
+ mkdirSync(join(TMP, 'releases/1/0/v1.0.0'), { recursive: true })
49
+ writeFileSync(join(TMP, 'releases/1/0/v1.0.0/task.test.js'), '// already closed')
50
+
51
+ const scanner = new Scanner(TMP)
52
+ const result = scanner.close('v1.0.0')
53
+
54
+ assert.equal(result.closed, 0)
55
+ })
56
+
57
+ it('throws when version not found', () => {
58
+ mkdirSync(join(TMP, 'releases'), { recursive: true })
59
+
60
+ const scanner = new Scanner(TMP)
61
+ assert.throws(() => scanner.close('v9.9.9'), /not found/i)
62
+ })
63
+
64
+ it('works with flat structure (releases/v2.0.0/)', () => {
65
+ const dir = join(TMP, 'releases/v2.0.0')
66
+ mkdirSync(dir, { recursive: true })
67
+ writeFileSync(join(dir, 'task.spec.js'), '// flat')
68
+
69
+ const scanner = new Scanner(TMP)
70
+ const result = scanner.close('v2.0.0')
71
+
72
+ assert.equal(result.closed, 1)
73
+ assert.ok(existsSync(join(dir, 'task.test.js')))
74
+ })
75
+
76
+ it('close --all renames all specs across versions', () => {
77
+ const v1 = join(TMP, 'releases/1/0/v1.0.0')
78
+ const v2 = join(TMP, 'releases/2/0/v2.0.0')
79
+ mkdirSync(v1, { recursive: true })
80
+ mkdirSync(v2, { recursive: true })
81
+ writeFileSync(join(v1, 'task.spec.js'), '// v1')
82
+ writeFileSync(join(v2, 'task.spec.js'), '// v2')
83
+
84
+ const scanner = new Scanner(TMP)
85
+ const result = scanner.close()
86
+
87
+ assert.equal(result.closed, 2)
88
+ assert.ok(existsSync(join(v1, 'task.test.js')))
89
+ assert.ok(existsSync(join(v2, 'task.test.js')))
90
+ })
91
+ })
@@ -0,0 +1,134 @@
1
+ import { describe, it, beforeEach, afterEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { mkdirSync, writeFileSync, rmSync } from 'node:fs'
4
+ import { join } from 'node:path'
5
+ import { tmpdir } from 'node:os'
6
+
7
+ import Scanner from './Scanner.js'
8
+
9
+ const TMP = join(tmpdir(), 'nan0release-scanner-test-' + Date.now())
10
+
11
+ describe('Scanner', () => {
12
+ beforeEach(() => {
13
+ mkdirSync(TMP, { recursive: true })
14
+ })
15
+
16
+ afterEach(() => {
17
+ rmSync(TMP, { recursive: true, force: true })
18
+ })
19
+
20
+ describe('nested version structure', () => {
21
+ it('finds spec files in nested directories (releases/1/0/v1.0.0/)', () => {
22
+ const dir = join(TMP, 'releases/1/0/v1.0.0')
23
+ mkdirSync(dir, { recursive: true })
24
+ writeFileSync(join(dir, 'task.spec.js'), '// spec')
25
+
26
+ const scanner = new Scanner(TMP)
27
+ const specs = scanner.findSpecs()
28
+
29
+ assert.equal(specs.length, 1)
30
+ assert.equal(specs[0].version, 'v1.0.0')
31
+ assert.ok(specs[0].path.endsWith('task.spec.js'))
32
+ })
33
+
34
+ it('finds test files (closed contracts)', () => {
35
+ const dir = join(TMP, 'releases/1/0/v1.0.0')
36
+ mkdirSync(dir, { recursive: true })
37
+ writeFileSync(join(dir, 'task.test.js'), '// test')
38
+
39
+ const scanner = new Scanner(TMP)
40
+ const tests = scanner.findTests()
41
+
42
+ assert.equal(tests.length, 1)
43
+ assert.equal(tests[0].version, 'v1.0.0')
44
+ })
45
+
46
+ it('finds flat structure too (releases/v2.0.0/)', () => {
47
+ const dir = join(TMP, 'releases/v2.0.0')
48
+ mkdirSync(dir, { recursive: true })
49
+ writeFileSync(join(dir, 'task.spec.js'), '// spec')
50
+
51
+ const scanner = new Scanner(TMP)
52
+ const specs = scanner.findSpecs()
53
+
54
+ assert.equal(specs.length, 1)
55
+ assert.equal(specs[0].version, 'v2.0.0')
56
+ })
57
+ })
58
+
59
+ describe('multiple versions', () => {
60
+ it('finds all specs across multiple versions', () => {
61
+ const v1 = join(TMP, 'releases/1/0/v1.0.0')
62
+ const v2 = join(TMP, 'releases/2/0/v2.0.0')
63
+ mkdirSync(v1, { recursive: true })
64
+ mkdirSync(v2, { recursive: true })
65
+ writeFileSync(join(v1, 'task.spec.js'), '// v1 spec')
66
+ writeFileSync(join(v2, 'task.spec.js'), '// v2 spec')
67
+
68
+ const scanner = new Scanner(TMP)
69
+ const specs = scanner.findSpecs()
70
+
71
+ assert.equal(specs.length, 2)
72
+ const versions = specs.map((s) => s.version).sort()
73
+ assert.deepEqual(versions, ['v1.0.0', 'v2.0.0'])
74
+ })
75
+
76
+ it('separates specs from tests', () => {
77
+ const v1 = join(TMP, 'releases/1/0/v1.0.0')
78
+ const v2 = join(TMP, 'releases/2/0/v2.0.0')
79
+ mkdirSync(v1, { recursive: true })
80
+ mkdirSync(v2, { recursive: true })
81
+ writeFileSync(join(v1, 'task.test.js'), '// closed')
82
+ writeFileSync(join(v2, 'task.spec.js'), '// wip')
83
+
84
+ const scanner = new Scanner(TMP)
85
+ assert.equal(scanner.findSpecs().length, 1)
86
+ assert.equal(scanner.findTests().length, 1)
87
+ })
88
+ })
89
+
90
+ describe('filter by version', () => {
91
+ it('finds specs for a specific version', () => {
92
+ const v1 = join(TMP, 'releases/1/0/v1.0.0')
93
+ const v2 = join(TMP, 'releases/2/0/v2.0.0')
94
+ mkdirSync(v1, { recursive: true })
95
+ mkdirSync(v2, { recursive: true })
96
+ writeFileSync(join(v1, 'task.spec.js'), '// v1')
97
+ writeFileSync(join(v2, 'task.spec.js'), '// v2')
98
+
99
+ const scanner = new Scanner(TMP)
100
+ const specs = scanner.findSpecs('v2.0.0')
101
+
102
+ assert.equal(specs.length, 1)
103
+ assert.equal(specs[0].version, 'v2.0.0')
104
+ })
105
+ })
106
+
107
+ describe('status', () => {
108
+ it('returns status for all versions', () => {
109
+ const v1 = join(TMP, 'releases/1/0/v1.0.0')
110
+ const v2 = join(TMP, 'releases/2/0/v2.0.0')
111
+ mkdirSync(v1, { recursive: true })
112
+ mkdirSync(v2, { recursive: true })
113
+ writeFileSync(join(v1, 'task.test.js'), '// closed')
114
+ writeFileSync(join(v2, 'task.spec.js'), '// wip')
115
+
116
+ const scanner = new Scanner(TMP)
117
+ const status = scanner.status()
118
+
119
+ assert.equal(status.length, 2)
120
+ const v1s = status.find((s) => s.version === 'v1.0.0')
121
+ const v2s = status.find((s) => s.version === 'v2.0.0')
122
+ assert.equal(v1s.state, 'closed')
123
+ assert.equal(v2s.state, 'wip')
124
+ })
125
+ })
126
+
127
+ describe('empty releases', () => {
128
+ it('returns empty arrays when no releases directory', () => {
129
+ const scanner = new Scanner(TMP)
130
+ assert.deepEqual(scanner.findSpecs(), [])
131
+ assert.deepEqual(scanner.findTests(), [])
132
+ })
133
+ })
134
+ })
@@ -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
+ })
@@ -63,7 +63,7 @@ export default class BumpCommand extends ModelAsApp {
63
63
  t(BumpCommand.UI.detectingChangedPackages, {
64
64
  sinceRef,
65
65
  targetVersion,
66
- }),
66
+ })
67
67
  )
68
68
 
69
69
  const allFiles = new Set()
@@ -113,7 +113,7 @@ export default class BumpCommand extends ModelAsApp {
113
113
  version: targetVersion,
114
114
  dir: pkgDir,
115
115
  }),
116
- 'info',
116
+ 'info'
117
117
  )
118
118
  continue
119
119
  }
@@ -128,7 +128,7 @@ export default class BumpCommand extends ModelAsApp {
128
128
  targetVersion,
129
129
  dir: pkgDir,
130
130
  }),
131
- this.dryRun ? 'warn' : 'success',
131
+ this.dryRun ? 'warn' : 'success'
132
132
  )
133
133
  }
134
134
  }
@@ -0,0 +1,6 @@
1
+ import test from 'node:test'
2
+ import assert from 'node:assert'
3
+
4
+ test('Sample spec', () => {
5
+ assert.ok(true)
6
+ })
@@ -0,0 +1,16 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { readFile } from 'node:fs/promises'
4
+ import { join } from 'node:path'
5
+ import process from 'node:process'
6
+
7
+ describe('Release v3.0.0 - Dependency Alignment', () => {
8
+ it('should have correct versions for @nan0web packages', async () => {
9
+ const pkgPath = join(process.cwd(), 'package.json')
10
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
11
+
12
+ assert.ok(/^3\.1\.\d+$/.test(pkg.version), 'Version should start with 3.1')
13
+ assert.ok(/^(\^1\.8\.0|workspace:\*)$/.test(pkg.dependencies['@nan0web/ui']), '@nan0web/ui should be ^1.8.0 or workspace:*')
14
+ assert.ok(/^(\^2\.9\.0|workspace:\*)$/.test(pkg.dependencies['@nan0web/ui-cli']), '@nan0web/ui-cli should be ^2.9.0 or workspace:*')
15
+ })
16
+ })
@@ -12,10 +12,16 @@ export default class BumpCommand extends ModelAsApp {
12
12
  default: string;
13
13
  positional: boolean;
14
14
  };
15
+ static dryRun: {
16
+ alias: string;
17
+ default: boolean;
18
+ help: string;
19
+ };
15
20
  static UI: {
16
21
  title: string;
17
22
  help: string;
18
23
  bumping: string;
24
+ skippingAlreadyAtVersion: string;
19
25
  foundChangedPackages: string;
20
26
  detectingChangedPackages: string;
21
27
  noChangesDetected: string;
@@ -28,5 +34,6 @@ export default class BumpCommand extends ModelAsApp {
28
34
  constructor(data?: Partial<BumpCommand>, options?: Partial<import("@nan0web/ui").ModelAsAppOptions>);
29
35
  /** @type {string} Target version */ version: string;
30
36
  /** @type {string} Since reference */ since: string;
37
+ /** @type {boolean} Dry run */ dryRun: boolean;
31
38
  }
32
39
  import { ModelAsApp } from '@nan0web/ui';
package/knip.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "$schema": "https://unpkg.com/knip@5/schema.json",
3
- "entry": [
4
- "src/index.js",
5
- "bin/nan0release.js",
6
- "src/README.md.js"
7
- ],
8
- "project": ["src/**/*.js", "bin/*.js"],
9
- "ignoreDependencies": [
10
- "@nan0web/log",
11
- "@nan0web/ui",
12
- "@nan0web/ui-cli",
13
- "@nan0web/logos",
14
- "@nan0web/markdown",
15
- "@nan0web/db",
16
- "@nan0web/db-fs",
17
- "@nan0web/types",
18
- "@nan0web/co",
19
- "@nan0web/changelog"
20
- ]
21
- }
package/nan0web.nan0 DELETED
@@ -1,16 +0,0 @@
1
- name: "@nan0web/release"
2
- agents:
3
- - id: 'release-manager'
4
- description: 'Release & NPM Publication Manager'
5
- workflows:
6
- - 'src/agents/workflows/release.md'
7
- - 'src/agents/workflows/npm-release.md'
8
- inspectors:
9
- - 'src/agents/inspectors/package-hygiene.md'
10
- - id: 'git-reviewer'
11
- description: 'Architectural Git Commit Validator'
12
- workflows:
13
- - 'src/agents/workflows/commit.md'
14
- - 'src/agents/workflows/zero-tolerance-git.md'
15
- inspectors:
16
- - 'src/agents/inspectors/git-reviewer.md'
package/project.yaml DELETED
@@ -1,79 +0,0 @@
1
- ---
2
- metadata:
3
- description: ""
4
- tags: []
5
- status: planned
6
- locale: uk
7
- sections:
8
- - title: "🧭 Фаза 1: Філософія та Абстракція (The Seed)"
9
- content:
10
- - "**Місія:** Управління проєктами як код (Project Management as Code). Ми відмовляємось від зовнішніх трекерів задач (Jira, Trello). Проєкт — це набір технічних **контрактів**, які проходять шлях від наміру до імплементації, забезпечуючи реальний статус прогресу автоматично."
11
- - "**Базова Абстракція:** Пакет відповідає за логіку та CLI, яка читає Markdown, генерує контракти (`.spec.js`), а потім закриває їх як тести (`.test.js`) з подальшою вічною регресією (тести копіюються в кодову базу)."
12
- - "**Глосарій:**"
13
- - "- **Release Document**: `release.md` з ТЗ та відомостями про команду."
14
- - "- **WIP Контракт**: `*.spec.js`, який падає/у стані `todo`, доки задачу не взяли і не зробили. Оновлює прогрес автоматично при виконанні."
15
- - "- **Закритий Контракт**: `*.test.js` (результат команди `close`). Він стає законом і гарантує регресію через `pnpm test`."
16
- - "---"
17
- tasks: []
18
- - title: "📐 Фаза 2: Доменне Моделювання (Data-Driven Models)"
19
- content:
20
- - "Сутності і моделі, які керують процесом релізу, не мають жорсткої прив'язки до вебу, але описуються як чисті об'єкти:"
21
- - "- `Release`: Координує стан (версія, час початку, час завершення)."
22
- - "- `ReleaseDocument`: Структурує Markdown у модель (дістає задачі)."
23
- - "- `Person`: Типізована перевірка того, хто схвалив дії (через GPG або JSON підпис)."
24
- - "- `Scanner`: Абстракція доступу до файлової системи (`releases/`)."
25
- - "Статуси відстежуються динамічно через статус тестів: `it.todo` -> `it.skip` -> `it()`."
26
- - "---"
27
- tasks: []
28
- - title: "🛠 Фаза 3: Верифікація Логіки (CLI-First)"
29
- content:
30
- - "Уся бізнес-логіка @nan0web/release 100% покривається Unit-тестами та CLI командами. Жодного веб-коду. Використовуємо Message-based архітектуру `@nan0web/ui-cli`."
31
- - "1. **Команди CLI (`bin/nan0release.js`):**"
32
- - "- `deps`, `publish` (реалізовані)."
33
- - "- Заплановані (`init`, `spec`, `close`, `status`) інкапсулюють керування `Scanner` та `Release` моделями."
34
- - "2. **Contract Lifecycle (Автоматизація прогресу):**"
35
- - "- На старті `.spec.js` мають `it.todo()` → задача в беклозі."
36
- - "- Коли розробник бере задачу → генерує логіку, змінює на `it()`. При коміті реальний прогрес проекту видно \"зеленим\"."
37
- - "- `nan0release close` перейменовує `spec → test` і переносить його в `src/releases/` для 100% автоматичної регресії."
38
- - "---"
39
- tasks: []
40
- - title: "🪐 Фаза 4: Sovereign Workbench (The Master IDE)"
41
- content:
42
- - "_(Для цього пакету застосовується опціонально, оскільки тут CLI-First)_"
43
- - "Пакет `release` є постачальником даних для інших Sandbox-середовищ, де може виводитись:"
44
- - "- Dashboard релізу (таблиці статусів `wip/closed/mixed`)."
45
- - "- Для цього ми генеруємо `.datasets/README.dataset.jsonl` за допомогою `test:docs`, щоб створювати машиночитабельні знання для LLM-агентів для донавчання або можна користуватись README.md як індексу у реальному часі."
46
- - "---"
47
- tasks: []
48
- - title: "🎨 Фаза 5: Тематизація та Інтерфейс (Theming)"
49
- content:
50
- - "_(Може застосовуватися при запуску HTML звітів про реліз або Dashboard)_"
51
- - "Логіка `release UI` (якщо така з'явиться у додатку `@nan0web/release.app`), буде імпортувати компоненти з `@nan0web/ui-lit` зі строгим слідуванням CSS-змінним (напр. `--co` для кольорів бренду) без створення кастомних стилів."
52
- - "---"
53
- tasks: []
54
- - title: "🏁 Чек-лист готовності пакету (Definition of Done)"
55
- content: []
56
- tasks:
57
- - title: "General"
58
- content: []
59
- items:
60
- - text: "Визначена місія та філософія у `system.md` та `project.md`."
61
- completed: true
62
- - text: "Робота з Markdown-нотатками та базова доменна логіка (`Scanner`, `Release`)."
63
- completed: true
64
- - text: "Атомарна операція закриття контрактів (`Scanner.close()`)."
65
- completed: true
66
- - text: "Всі існуючі специфікації проходять через CLI та `node:test` (22/24 pass, 2 skip)."
67
- completed: true
68
- - text: "Прогрес автоматизується зміною статусу `spec.js` (todo/skip/pass)."
69
- completed: true
70
- - text: "Усі CLI сценарії для AGRP (spec, close, init, status) завершені."
71
- completed: false
72
- - text: "Створені API-ендпоінти для відображення релізного прогресу у React-Dashboard."
73
- completed: false
74
- total: 7
75
- completed: 5
76
- stats:
77
- totalTasks: 7
78
- completedTasks: 5
79
- progress: 71