@nan0web/release 1.0.3 → 3.1.1

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 (58) hide show
  1. package/README.md +42 -17
  2. package/bin/nan0release.js +3 -39
  3. package/knip.json +21 -0
  4. package/nan0web.nan0 +16 -0
  5. package/package.json +14 -14
  6. package/src/README.md.js +19 -21
  7. package/src/Release/Person.js +1 -1
  8. package/src/agents/inspectors/git-reviewer.md +35 -0
  9. package/src/agents/inspectors/package-hygiene.md +63 -0
  10. package/src/agents/workflows/commit.md +28 -0
  11. package/src/agents/workflows/npm-release.md +40 -0
  12. package/src/agents/workflows/release.md +205 -0
  13. package/src/agents/workflows/zero-tolerance-git.md +21 -0
  14. package/src/domain/App.js +36 -0
  15. package/src/domain/BumpCommand.js +137 -0
  16. package/src/domain/CheckCommand.js +193 -0
  17. package/src/domain/CloseCommand.js +68 -0
  18. package/src/domain/DepsCommand.js +104 -0
  19. package/src/domain/InitCommand.js +57 -0
  20. package/src/domain/PublishCommand.js +89 -0
  21. package/src/domain/SpecCommand.js +43 -0
  22. package/src/domain/StatusCommand.js +25 -0
  23. package/src/index.js +16 -2
  24. package/types/Release/Person.d.ts +3 -3
  25. package/types/Team.d.ts +3 -3
  26. package/types/commands/CheckCommand.d.ts +24 -0
  27. package/types/commands/CloseCommand.d.ts +2 -1
  28. package/types/commands/Command.d.ts +6 -1
  29. package/types/commands/DepsCommand.d.ts +2 -1
  30. package/types/commands/InitCommand.d.ts +2 -1
  31. package/types/commands/PublishCommand.d.ts +2 -1
  32. package/types/commands/SpecCommand.d.ts +2 -1
  33. package/types/domain/App.d.ts +17 -0
  34. package/types/domain/BumpCommand.d.ts +32 -0
  35. package/types/domain/CheckCommand.d.ts +24 -0
  36. package/types/domain/CloseCommand.d.ts +17 -0
  37. package/types/domain/DepsCommand.d.ts +22 -0
  38. package/types/domain/InitCommand.d.ts +18 -0
  39. package/types/domain/PublishCommand.d.ts +27 -0
  40. package/types/domain/SpecCommand.d.ts +17 -0
  41. package/types/domain/StatusCommand.d.ts +7 -0
  42. package/types/index.d.ts +8 -0
  43. package/types/ui/cli/index.d.ts +1 -0
  44. package/types/utils/runSpawn.d.ts +1 -0
  45. package/src/Team.js +0 -35
  46. package/src/cli/index.js +0 -9
  47. package/src/co/AppCommandOptions.js +0 -25
  48. package/src/commands/CloseCommand.js +0 -67
  49. package/src/commands/Command.js +0 -48
  50. package/src/commands/DepsCommand.js +0 -143
  51. package/src/commands/InitCommand.js +0 -57
  52. package/src/commands/PublishCommand.js +0 -105
  53. package/src/commands/SpecCommand.js +0 -41
  54. package/src/commands/StatusCommand.js +0 -26
  55. package/src/db/ReleaseDB.js +0 -54
  56. package/src/ui/cli/ReleaseCLi.js +0 -52
  57. package/src/ui/cli/index.js +0 -0
  58. package/src/utils/runSpawn.js +0 -0
@@ -0,0 +1,17 @@
1
+ export default class SpecCommand extends ModelAsApp {
2
+ static version: {
3
+ help: string;
4
+ default: string;
5
+ };
6
+ static UI: {
7
+ title: string;
8
+ help: string;
9
+ };
10
+ /**
11
+ * @param {Partial<SpecCommand>} [data]
12
+ * @param {Partial<import('@nan0web/ui').ModelAsAppOptions>} [options]
13
+ */
14
+ constructor(data?: Partial<SpecCommand>, options?: Partial<import("@nan0web/ui").ModelAsAppOptions>);
15
+ /** @type {string} Release version filter */ version: string;
16
+ }
17
+ import { ModelAsApp } from '@nan0web/ui';
@@ -0,0 +1,7 @@
1
+ export default class StatusCommand extends ModelAsApp {
2
+ static UI: {
3
+ title: string;
4
+ help: string;
5
+ };
6
+ }
7
+ import { ModelAsApp } from '@nan0web/ui';
package/types/index.d.ts CHANGED
@@ -1,3 +1,11 @@
1
+ export { ReleaseApp } from "./domain/App.js";
2
+ export { default as InitCommand } from "./domain/InitCommand.js";
3
+ export { default as CheckCommand } from "./domain/CheckCommand.js";
4
+ export { default as CloseCommand } from "./domain/CloseCommand.js";
5
+ export { default as DepsCommand } from "./domain/DepsCommand.js";
6
+ export { default as PublishCommand } from "./domain/PublishCommand.js";
7
+ export { default as SpecCommand } from "./domain/SpecCommand.js";
8
+ export { default as StatusCommand } from "./domain/StatusCommand.js";
1
9
  import Release from './Release.js';
2
10
  import ReleaseDocument from './Release/Document.js';
3
11
  import Person from './Release/Person.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/src/Team.js DELETED
@@ -1,35 +0,0 @@
1
- import { HumanName, HumanGender, HumanContact } from '@nan0web/verse'
2
-
3
- class Person {
4
- /** @type {HumanName} */
5
- name
6
- /** @type {HumanGender} */
7
- gender
8
- /** @type {HumanContact[]} */
9
- contacts
10
-
11
- constructor(name) {
12
- this.name = new HumanName(name)
13
- this.gender = new HumanGender(-1)
14
- this.contacts = []
15
- }
16
- }
17
-
18
- class CLevelTeam {
19
- static name = 'C Level'
20
- static ceo = new Person(['Yaroslav', 'Wise'])
21
- static cfo = new Person(['Chingis'])
22
- }
23
-
24
- class UXTeam {
25
- static name = 'UX'
26
- static designer = new Person(['Taras', 'Shevchenko'])
27
- }
28
-
29
- class Company {
30
- static name = 'NaN•Web'
31
- static c = CLevelTeam
32
- static ux = UXTeam
33
- }
34
-
35
- export default Company
package/src/cli/index.js DELETED
@@ -1,9 +0,0 @@
1
- /**
2
- * Export the UI‑CLI based release CLI.
3
- *
4
- * @module release/cli
5
- */
6
-
7
- import createReleaseCLI from '../ui/cli/ReleaseCLi.js'
8
-
9
- export default createReleaseCLI
@@ -1,25 +0,0 @@
1
- export default class AppCommandOptions {
2
- /** @type {boolean} */
3
- webui = false
4
- /** @type {boolean} */
5
- json = false
6
- /** @type {boolean} */
7
- quiet = false
8
- /** @type {string} */
9
- releaseDir = './releases'
10
- /** @type {boolean} */
11
- write = false
12
- /** @type {string} */
13
- user = 'anonymous'
14
- /** @type {boolean} */
15
- ignoreFailTests = false
16
- /** @type {boolean} */
17
- full = false
18
-
19
- /**
20
- * @param {Object} input
21
- */
22
- constructor(input = {}) {
23
- Object.assign(this, input)
24
- }
25
- }
@@ -1,67 +0,0 @@
1
- import { OutputMessage } from '@nan0web/co'
2
- import Command from './Command.js'
3
- import process from 'node:process'
4
- import Scanner from '../Release/Scanner.js'
5
-
6
- class CloseBody {
7
- static version = { help: 'Release version', defaultValue: '' }
8
- /** @type {string} */
9
- version = ''
10
- constructor({ version = '' } = {}) {
11
- this.version = String(version)
12
- }
13
- }
14
-
15
- export default class CloseCommand extends Command {
16
- static name = 'close'
17
- static help = 'Close a release and move tests to src/'
18
- static Body = CloseBody
19
-
20
- /** @param {Partial<Command> & { body?: Partial<CloseBody> }} [input={}] */
21
- constructor(input = {}) {
22
- super(input)
23
- this.body = new CloseBody(/** @type {*} */ (input))
24
- }
25
-
26
- async *run() {
27
- const v = this.body.version || ''
28
-
29
- yield new OutputMessage(`🛜 nan0release close ${v || 'all'}`)
30
-
31
- const scanner = new Scanner(process.cwd())
32
- const specs = scanner.findSpecs(v)
33
-
34
- if (specs.length === 0) {
35
- yield new OutputMessage(`No pending specs found for version ${v || 'all'}`)
36
- return
37
- }
38
-
39
- yield new OutputMessage(`Running ${specs.length} pending specs...`)
40
-
41
- const files = specs.map((s) => s.path)
42
- await this._run('node', ['--test', ...files], 'Cannot close: Specs failed')
43
-
44
- yield new OutputMessage(`✅ Specs passed. Closing...`)
45
-
46
- let movedCount = 0
47
- const { renameSync, mkdirSync } = await import('node:fs')
48
- const { join, dirname } = await import('node:path')
49
-
50
- for (const spec of specs) {
51
- const relativePath = spec.path.substring(process.cwd().length + 1)
52
- const destRelative = relativePath
53
- .replace(/^releases\//, 'src/releases/')
54
- .replace('.spec.js', '.test.js')
55
- const destPath = join(process.cwd(), destRelative)
56
-
57
- mkdirSync(dirname(destPath), { recursive: true })
58
- renameSync(spec.path, destPath)
59
- movedCount++
60
- }
61
-
62
- yield new OutputMessage({
63
- content: [`✅ Successfully closed ${movedCount} specs and moved to src/releases/`],
64
- type: OutputMessage.TYPES.SUCCESS,
65
- })
66
- }
67
- }
@@ -1,48 +0,0 @@
1
- import DBFS from '@nan0web/db-fs'
2
- import { OutputMessage } from '@nan0web/co'
3
- import Logger from '@nan0web/log'
4
- import { runSpawn } from '@nan0web/test'
5
- import { UiMessage } from '@nan0web/ui'
6
-
7
- /** @typedef {import("@nan0web/test/types/exec/runSpawn").SpawnResult} SpawnResult */
8
-
9
- export default class Command extends UiMessage {
10
- /** @type {DBFS} */
11
- fs
12
- /** @type {Logger} */
13
- logger
14
-
15
- /**
16
- * @param {Partial<UiMessage> & { }} [input={}]
17
- */
18
- constructor(input = {}) {
19
- super(input)
20
- const { fs, logger } = UiMessage.parseBody(input, this.Body)
21
- this.fs = fs ?? DBFS.from({})
22
- this.logger = Logger.from(logger ?? {})
23
- }
24
- get Body() {
25
- return /** @type {typeof Command} */ (this.constructor).Body
26
- }
27
- /**
28
- * Run the command.
29
- * @param {string} cmd
30
- * @param {string[]} [args=[]]
31
- * @param {string} [fail=""]
32
- * @returns {Promise<SpawnResult>}
33
- * @throws {Error}
34
- */
35
- async _run(cmd, args = [], fail = '') {
36
- const result = await runSpawn(cmd, args)
37
- if (0 !== result.code) {
38
- this.logger.error(result.error)
39
- throw new Error(fail)
40
- }
41
- return result
42
- }
43
-
44
- /** @returns {AsyncGenerator<OutputMessage>} */
45
- async *run() {
46
- throw new Error('Must be extended')
47
- }
48
- }
@@ -1,143 +0,0 @@
1
- /**
2
- * DepsCommand – Message‑based version of the old `DepsCommand`.
3
- *
4
- * Checks and optionally fixes workspace / link dependencies.
5
- *
6
- * Body schema:
7
- * fix – boolean – whether to apply fixes (default: false)
8
- *
9
- * @module release/commands/DepsCommand
10
- */
11
-
12
- import { Message, OutputMessage } from '@nan0web/co'
13
-
14
- import Command from './Command.js'
15
-
16
- class DepsBody {
17
- /** @type {boolean} */
18
- fix = false
19
- static fix = {
20
- help: 'Apply fixes automatically',
21
- defaultValue: false,
22
- }
23
- /** @type {boolean} */
24
- latest = false
25
- static latest = {
26
- help: 'Update @nan0web packages to the latests available versions',
27
- defaultValue: false,
28
- }
29
- constructor(input = {}) {
30
- const { fix = this.fix, latest = this.latest } = Message.parseBody(input, DepsBody)
31
- this.fix = Boolean(fix)
32
- this.latest = Boolean(latest)
33
- }
34
- }
35
-
36
- export default class DepsCommand extends Command {
37
- static name = 'deps'
38
- static help = 'Dependencies manager'
39
- static Body = DepsBody
40
- /** @type {DepsBody} */
41
- body
42
-
43
- /** @param {Partial<DepsBody>} [input={}] */
44
- constructor(input = {}) {
45
- super({})
46
- this.body = Command.parseBody(input, DepsBody)
47
- }
48
-
49
- /**
50
- * @param {DepsCommand} [msg]
51
- * @returns {AsyncGenerator<OutputMessage>}
52
- */
53
- async *run(msg = new DepsCommand()) {
54
- const pkg = await this.fs.loadDocument('package.json', {})
55
- yield new OutputMessage(['Loaded package.json', JSON.stringify(pkg).length + ' bytes'])
56
- const deps = {
57
- devDependencies: pkg?.devDependencies ?? {},
58
- dependencies: pkg?.dependencies ?? {},
59
- peerDependencies: pkg?.peerDependencies ?? {},
60
- }
61
- const fixes = new Map()
62
- const fails = new Set()
63
-
64
- const isLocal = (ver) =>
65
- ['workspace:', 'link:'].some((p) => String(ver).toLowerCase().startsWith(p))
66
-
67
- const changes = new Map()
68
-
69
- for (const [group, map] of Object.entries(deps)) {
70
- yield new OutputMessage(`${group} (${Object.keys(map).length} items)`)
71
- for (const [dep, ver] of Object.entries(map ?? {})) {
72
- if (isLocal(ver) || msg.body.latest) {
73
- const result = await this._run(
74
- 'npm',
75
- ['info', dep, '--json'],
76
- 'Cannot retrieve npm package info',
77
- )
78
- try {
79
- const data = JSON.parse(result.text)
80
- if (data.version) {
81
- fixes.set(dep, data.version)
82
- yield new OutputMessage({
83
- content: [` ${dep}: ${ver} → ${data.version}`],
84
- type: OutputMessage.TYPES.WARNING,
85
- })
86
- } else {
87
- fails.add(dep)
88
- yield new OutputMessage({
89
- content: [` ${dep}: 404 — not found`],
90
- type: OutputMessage.TYPES.ERROR,
91
- })
92
- }
93
- } catch (/** @type {any} */ err) {
94
- yield new OutputMessage(err)
95
- }
96
- } else {
97
- yield new OutputMessage({
98
- content: [` ${dep}: ${ver}`],
99
- type: OutputMessage.TYPES.SUCCESS,
100
- })
101
- }
102
- }
103
- }
104
-
105
- if (fails.size) {
106
- yield new OutputMessage(
107
- new Error(`Some dependencies are not yet published on npm: ${[...fails].join(', ')}`),
108
- )
109
- return
110
- }
111
-
112
- if ((msg.body.fix || msg.body.latest) && fixes.size) {
113
- for (const [dep, ver] of fixes) {
114
- // naive fix – replace in package.json
115
- const args = ['install']
116
- if (pkg.devDependencies?.[dep]) {
117
- pkg.devDependencies[dep] = ver
118
- args.push('--save-dev')
119
- }
120
- if (pkg.dependencies?.[dep]) pkg.dependencies[dep] = ver
121
- args.push(dep + '@latest')
122
- if (msg.body.latest) {
123
- yield new OutputMessage([`Installing dependency`])
124
- await this._run('npm', args)
125
- }
126
- changes.set(dep, `${dep}: ${ver}`)
127
- yield new OutputMessage({
128
- content: [`Fixed ${dep} → ${ver}`],
129
- type: OutputMessage.TYPES.SUCCESS,
130
- })
131
- }
132
- await this.fs.saveDocument('package.json', pkg)
133
- yield new OutputMessage({
134
- content: [`Fixes saved (${changes.size} changes)`],
135
- type: OutputMessage.TYPES.SUCCESS,
136
- })
137
- yield new OutputMessage({ content: ['Dependencies fixed'] })
138
- } else {
139
- yield new OutputMessage(`Run with --fix to apply ${fixes.size} available fixes`)
140
- yield new OutputMessage({ content: ['Dependencies check completed'] })
141
- }
142
- }
143
- }
@@ -1,57 +0,0 @@
1
- import { OutputMessage } from '@nan0web/co'
2
- import Command from './Command.js'
3
- import process from 'node:process'
4
-
5
- class InitBody {
6
- static version = { help: 'Release version', defaultValue: '' }
7
- /** @type {string} */
8
- version = ''
9
- constructor({ version = '' } = {}) {
10
- this.version = String(version)
11
- }
12
- }
13
-
14
- export default class InitCommand extends Command {
15
- static name = 'init'
16
- static help = 'Initialize a new release structure'
17
- static Body = InitBody
18
-
19
- /** @param {Partial<Command> & { body?: Partial<InitBody> }} [input={}] */
20
- constructor(input = {}) {
21
- super(input)
22
- this.body = new InitBody(/** @type {*} */ (input))
23
- }
24
-
25
- async *run() {
26
- let v = this.body.version
27
- if (!v) {
28
- throw new Error('Version is required (e.g. --version=1.0.0 or --version=v1.0.0)')
29
- }
30
- if (!v.startsWith('v')) v = 'v' + v
31
-
32
- const parts = v.replace('v', '').split('.')
33
- const major = parts[0] || '0'
34
- const minor = parts[1] || '0'
35
-
36
- const { mkdirSync, writeFileSync } = await import('node:fs')
37
- const { join } = await import('node:path')
38
-
39
- const dir = join(process.cwd(), 'releases', major, minor, v)
40
- mkdirSync(dir, { recursive: true })
41
-
42
- const taskPath = join(dir, 'task.md')
43
- writeFileSync(taskPath, `# Release ${v}\n\n- [ ] Task 1`, 'utf8')
44
-
45
- const specPath = join(dir, 'test.spec.js')
46
- writeFileSync(
47
- specPath,
48
- `import test from 'node:test'\nimport assert from 'node:assert'\n\ntest('Sample spec', () => {\n\tassert.ok(true)\n})\n`,
49
- 'utf8',
50
- )
51
-
52
- yield new OutputMessage({
53
- content: [`✅ Initialized release ${v} in ${dir}`],
54
- type: OutputMessage.TYPES.SUCCESS,
55
- })
56
- }
57
- }
@@ -1,105 +0,0 @@
1
- /**
2
- * PublishCommand – Message‑based version of the old `PublishCommand`.
3
- *
4
- * Publishes the current package to NPM and creates a Git tag.
5
- *
6
- * Body schema:
7
- * major | minor | patch – optional version bump flag.
8
- *
9
- * @module release/commands/PublishCommand
10
- */
11
-
12
- import { Message, OutputMessage } from '@nan0web/co'
13
-
14
- import Command from './Command.js'
15
-
16
- class PublishBody {
17
- static major = { help: 'Bump major version', defaultValue: false }
18
- static minor = { help: 'Bump minor version', defaultValue: false }
19
- static patch = { help: 'Bump patch version', defaultValue: false }
20
-
21
- /** @type {boolean} */
22
- major = false
23
- /** @type {boolean} */
24
- minor = false
25
- /** @type {boolean} */
26
- patch = false
27
-
28
- constructor({ major = false, minor = false, patch = false } = {}) {
29
- this.major = Boolean(major)
30
- this.minor = Boolean(minor)
31
- this.patch = Boolean(patch)
32
- }
33
- }
34
-
35
- export default class PublishCommand extends Command {
36
- static name = 'publish'
37
- static help = 'Publish npm packages'
38
- static Body = PublishBody
39
-
40
- /** @type {PublishBody} */
41
- body
42
-
43
- /** @param {Partial<Command> & { body?: Partial<PublishBody> }} [input={}] */
44
- constructor(input = {}) {
45
- super(input)
46
- const { body = new PublishBody() } = Message.parseBody(input, PublishBody)
47
- this.body = new PublishBody(body)
48
- }
49
-
50
- /** @returns {AsyncGenerator<OutputMessage>} */
51
- async *run() {
52
- const bump = this.body.major
53
- ? 'major'
54
- : this.body.minor
55
- ? 'minor'
56
- : this.body.patch
57
- ? 'patch'
58
- : null
59
- if (bump) {
60
- await this._run('npm', ['version', bump], 'Failed to bump version')
61
- }
62
- const pkg = await this.fs.loadDocument('package.json', {})
63
- const tag = `v${pkg.version}`
64
-
65
- yield new OutputMessage(`🛜 nan0release: publishing @${pkg.name}@${pkg.version}`)
66
-
67
- await this._run('git', ['diff', '--quiet'], 'Uncommitted changes found')
68
-
69
- await this._run('git', ['pull'], 'Failed to pull latest changes')
70
- await this._run('npm', ['run', 'clean'], 'Clean failed')
71
- await this._run('npm', ['run', 'build'], 'Build failed')
72
- await this._run('npm', ['test'], 'Tests failed')
73
-
74
- return
75
-
76
- const tagsResult = await this._run('git', ['tag'], 'Failed to get tags')
77
- const tags = tagsResult.text.split('\n').filter(Boolean)
78
-
79
- if (!tags.includes(tag)) {
80
- await this._run(
81
- 'git',
82
- ['tag', '-a', tag, '-m', `Release ${pkg.version}`],
83
- 'Tag creation failed',
84
- )
85
- yield new OutputMessage({
86
- content: [`Tag ${tag} created`],
87
- type: OutputMessage.TYPES.SUCCESS,
88
- })
89
- } else {
90
- yield new OutputMessage({
91
- content: [`Tag ${tag} already exists`],
92
- type: OutputMessage.TYPES.WARNING,
93
- })
94
- }
95
-
96
- await this._run('npm', ['publish', '--access', 'public'], 'Publish to npm failed')
97
- await this._run('git', ['push', 'origin', 'main', '--no-verify'], 'Git push failed')
98
- await this._run('git', ['push', 'origin', '--tags', '--no-verify'], 'Tag push failed')
99
-
100
- yield new OutputMessage({
101
- content: [`${pkg.name}@${pkg.version} published.`],
102
- type: OutputMessage.TYPES.SUCCESS,
103
- })
104
- }
105
- }
@@ -1,41 +0,0 @@
1
- import { OutputMessage } from '@nan0web/co'
2
- import Command from './Command.js'
3
- import process from 'node:process'
4
- import Scanner from '../Release/Scanner.js'
5
-
6
- class SpecBody {
7
- static version = { help: 'Release version filter', defaultValue: '' }
8
- /** @type {string} */
9
- version = ''
10
- constructor({ version = '' } = {}) {
11
- this.version = String(version)
12
- }
13
- }
14
-
15
- export default class SpecCommand extends Command {
16
- static name = 'spec'
17
- static help = 'Run spec testing for the active release'
18
- static Body = SpecBody
19
-
20
- /** @param {Partial<Command> & { body?: Partial<SpecBody> }} [input={}] */
21
- constructor(input = {}) {
22
- super(input)
23
- this.body = new SpecBody(/** @type {*} */ (input))
24
- }
25
-
26
- async *run() {
27
- yield new OutputMessage(`🛜 nan0release spec ${this.body.version || 'all'}`)
28
- const scanner = new Scanner(process.cwd())
29
- const specs = scanner.findSpecs(this.body.version)
30
- if (specs.length === 0) {
31
- yield new OutputMessage(`No specs found.`)
32
- return
33
- }
34
- const files = specs.map((s) => s.path)
35
- await this._run('node', ['--test', ...files], 'Specs failed')
36
- yield new OutputMessage({
37
- content: [`✅ Specs passed`],
38
- type: OutputMessage.TYPES.SUCCESS,
39
- })
40
- }
41
- }
@@ -1,26 +0,0 @@
1
- import { OutputMessage } from '@nan0web/co'
2
- import Command from './Command.js'
3
- import process from 'node:process'
4
- import Scanner from '../Release/Scanner.js'
5
-
6
- export default class StatusCommand extends Command {
7
- static name = 'status'
8
- static help = 'Show release status'
9
-
10
- constructor(input = {}) {
11
- super(input)
12
- this.body = {}
13
- }
14
-
15
- async *run() {
16
- const scanner = new Scanner(process.cwd())
17
- const statuses = scanner.status()
18
-
19
- yield new OutputMessage(`🛜 nan0release status`)
20
- for (const st of statuses) {
21
- yield new OutputMessage(
22
- `${st.version.padEnd(10)} | ${st.state.padEnd(6)} | Specs: ${st.specs.length}, Tests: ${st.tests.length}`,
23
- )
24
- }
25
- }
26
- }
@@ -1,54 +0,0 @@
1
- import DBFS from '@nan0web/db-fs'
2
-
3
- /**
4
- * ReleaseDB – thin wrapper around DBFS that adds a convenient `releases`
5
- * getter returning all release directories discovered under the root.
6
- *
7
- * The underlying DBFS stores a `Map` called `data` where each key is a
8
- * relative file path (e.g. "1/0/v1.0.0/release.js").
9
- */
10
- class ReleaseDB extends DBFS {
11
- /**
12
- * @param {string} uri
13
- * @returns {ReleaseDB}
14
- */
15
- extract(uri) {
16
- return new ReleaseDB(super.extract(uri))
17
- }
18
- /**
19
- * @param {string} version
20
- * @returns {ReleaseDB}
21
- */
22
- extractVersion(version) {
23
- const found = version.match(/^v(\d+)\.(\d+)\.(\d+)$/)
24
- if (!found) {
25
- throw new Error('Incorrect version')
26
- }
27
- const arr = version.slice(1).split('.')
28
- const path = [arr[0], arr[1], version, ''].join('/')
29
- return ReleaseDB.from(this.extract(path))
30
- }
31
- /**
32
- * @returns {Array<string>} List of discovered release root paths
33
- */
34
- get releases() {
35
- const paths = new Set()
36
- for (const key of this.data.keys()) {
37
- // Expected structure: major/minor/vX.Y.Z/…
38
- const parts = key.split('/')
39
- if (parts.length > 2 && /^\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) {
40
- paths.add(`${parts[0]}/${parts[1]}/${parts[2]}`)
41
- }
42
- }
43
- return Array.from(paths)
44
- }
45
- /**
46
- * @param {object} input
47
- * @returns {ReleaseDB}
48
- */
49
- static from(input) {
50
- if (input instanceof ReleaseDB) return input
51
- return new ReleaseDB(input)
52
- }
53
- }
54
- export default ReleaseDB