@nan0web/release 3.1.1 → 3.3.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.3.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/db-fs": "3.3.0",
14
+ "@nan0web/db": "3.3.0",
15
+ "@nan0web/types": "3.3.0",
16
+ "@nan0web/markdown": "3.3.0",
17
+ "@nan0web/log": "3.3.0",
18
+ "@nan0web/co": "3.3.0",
19
+ "@nan0web/changelog": "3.3.0",
20
+ "@nan0web/logos": "3.3.0",
21
+ "@nan0web/ui": "3.3.0",
22
+ "@nan0web/ui-cli": "3.3.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.3.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
+ })
@@ -1,10 +1,14 @@
1
- export default ReleaseDocument;
1
+ import Markdown, { MDElement } from '@nan0web/markdown';
2
+ import Person from './Person.js';
2
3
  declare class ReleaseDocument extends Markdown {
3
- /**
4
- * @param {*} input
5
- * @returns {ReleaseDocument}
6
- */
7
- static from(input: any): ReleaseDocument;
4
+ /** @type {Person[]} */
5
+ team: Person[];
6
+ /** @type {Map<string, Person[]>} */
7
+ roles: Map<string, Person[]>;
8
+ /** @type {string} */
9
+ version: string;
10
+ /** @type {Date | undefined} */
11
+ date: Date | undefined;
8
12
  /**
9
13
  * @param {Object} options
10
14
  * @param {Array} [options.team] - Release team members
@@ -13,19 +17,22 @@ declare class ReleaseDocument extends Markdown {
13
17
  * @param {string} [options.date] - Release date
14
18
  */
15
19
  constructor(options?: {
16
- team?: any[] | undefined;
17
- roles?: any[] | Map<any, any> | undefined;
18
- version?: string | undefined;
19
- date?: string | undefined;
20
+ team?: any[];
21
+ roles?: Map<any, any> | any[];
22
+ version?: string;
23
+ date?: string;
20
24
  });
21
- /** @type {Person[]} */
22
- team: Person[];
23
- /** @type {Map<string, Person[]>} */
24
- roles: Map<string, Person[]>;
25
- /** @type {string} */
26
- version: string;
27
- /** @type {Date | undefined} */
28
- date: Date | undefined;
25
+ /**
26
+ * Parse release document content
27
+ * Extracts version and date from H1 heading
28
+ * @param {string} input - Markdown content
29
+ * @returns {MDElement[]}
30
+ */
31
+ parse(input: string): MDElement[];
32
+ /**
33
+ * @param {*} input
34
+ * @returns {ReleaseDocument}
35
+ */
36
+ static from(input: any): ReleaseDocument;
29
37
  }
30
- import Markdown from '@nan0web/markdown';
31
- import Person from './Person.js';
38
+ export default ReleaseDocument;
@@ -1,4 +1,3 @@
1
- export default MarkdownToTest;
2
1
  /**
3
2
  * MarkdownToTest - generates node:test code from markdown release notes
4
3
  * Enables writing release notes first, then generating executable tests
@@ -45,3 +44,4 @@ declare class MarkdownToTest {
45
44
  tasks: any[];
46
45
  }>): string;
47
46
  }
47
+ export default MarkdownToTest;