@nan0web/release 3.2.0 → 3.4.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.
- package/bin/nan0release.js +33 -2
- package/bin/serve.js +73 -0
- package/package.json +45 -17
- package/src/Release/Document.js +1 -0
- package/src/Release/Scanner.js +3 -2
- package/src/Release.js +7 -7
- package/src/domain/App.js +31 -2
- package/src/domain/BumpCommand.js +10 -3
- package/src/domain/CheckCommand.js +504 -146
- package/src/domain/CheckCommand.story.js +92 -0
- package/src/domain/CloseCommand.js +13 -9
- package/src/domain/Dashboard/Dashboard.story.js +155 -0
- package/src/domain/Dashboard/DashboardModel.js +308 -0
- package/src/domain/Dashboard/ReleaseDiscovery.js +132 -0
- package/src/domain/Dashboard/ReleaseParser.js +226 -0
- package/src/domain/Dashboard/index.js +19 -0
- package/src/domain/DepsCommand.js +5 -2
- package/src/domain/InitCommand.js +18 -16
- package/src/domain/ListCommand.js +45 -0
- package/src/domain/PreviewCommand.js +39 -0
- package/src/domain/PublishCommand.js +7 -3
- package/src/domain/Registry/Registry.story.js +82 -0
- package/src/domain/Registry/RegistryModel.js +124 -0
- package/src/domain/ServeCommand.js +165 -0
- package/src/domain/ServeCommand.story.js +88 -0
- package/src/domain/SpecCommand.js +3 -1
- package/src/domain/StatusCommand.js +249 -3
- package/src/domain/ViewCommand.js +99 -0
- package/src/domain/WebCommand.js +140 -0
- package/src/domain/inspect/ReleaseAuditor.js +508 -0
- package/src/domain/inspect/ReleaseAuditor.story.js +136 -0
- package/src/domain/templates/release.html +34 -0
- package/src/domain/templates/release.html.template.js +34 -0
- package/src/index.js +6 -0
- package/src/inspectors/inspect-js.js +32 -0
- package/src/releases/1/0/v1.0.3/task.test.js +1 -1
- package/src/utils/nodejs/GitTelemetry.js +154 -0
- package/types/Release/Document.d.ts +4 -0
- package/types/Release/Scanner.d.ts +2 -2
- package/types/domain/App.d.ts +19 -3
- package/types/domain/BumpCommand.d.ts +9 -3
- package/types/domain/CheckCommand.d.ts +127 -15
- package/types/domain/CheckCommand.story.d.ts +1 -0
- package/types/domain/CloseCommand.d.ts +2 -1
- package/types/domain/Dashboard/Dashboard.story.d.ts +1 -0
- package/types/domain/Dashboard/DashboardModel.d.ts +273 -0
- package/types/domain/Dashboard/GitTelemetry.d.ts +23 -0
- package/types/domain/Dashboard/ReleaseDiscovery.d.ts +47 -0
- package/types/domain/Dashboard/ReleaseParser.d.ts +79 -0
- package/types/domain/Dashboard/index.d.ts +3 -0
- package/types/domain/DepsCommand.d.ts +4 -2
- package/types/domain/InitCommand.d.ts +2 -1
- package/types/domain/ListCommand.d.ts +18 -0
- package/types/domain/PreviewCommand.d.ts +6 -0
- package/types/domain/PublishCommand.d.ts +6 -3
- package/types/domain/Registry/Registry.story.d.ts +1 -0
- package/types/domain/Registry/RegistryModel.d.ts +61 -0
- package/types/domain/ServeCommand.d.ts +59 -0
- package/types/domain/ServeCommand.story.d.ts +1 -0
- package/types/domain/SpecCommand.d.ts +2 -1
- package/types/domain/StatusCommand.d.ts +39 -0
- package/types/domain/ViewCommand.d.ts +27 -0
- package/types/domain/WebCommand.d.ts +47 -0
- package/types/domain/inspect/ReleaseAuditor.d.ts +134 -0
- package/types/domain/inspect/ReleaseAuditor.story.d.ts +1 -0
- package/types/domain/templates/release.html.template.d.ts +2 -0
- package/types/index.d.ts +5 -0
- package/types/inspectors/inspect-js.d.ts +2 -0
- package/types/utils/nodejs/GitTelemetry.d.ts +23 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ModelAsApp, show, result, ask } from '@nan0web/ui'
|
|
2
|
+
import { select } from '@nan0web/ui-cli'
|
|
3
|
+
import Scanner from '../Release/Scanner.js'
|
|
4
|
+
import { readFile } from 'node:fs/promises'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ViewCommand - ModelAsApp Subcommand to inspect and view release details across multimodal interfaces.
|
|
9
|
+
*/
|
|
10
|
+
export default class ViewCommand extends ModelAsApp {
|
|
11
|
+
static alias = 'view'
|
|
12
|
+
|
|
13
|
+
static UI = {
|
|
14
|
+
title: 'view',
|
|
15
|
+
help: 'View release summary and launch multimodal playgrounds (CLI, TUI, WEB, CHAT, VOICE)',
|
|
16
|
+
noReleases: 'No releases found in current directory.',
|
|
17
|
+
selectPrompt: 'Select release version to view:',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static version = {
|
|
21
|
+
help: 'Target release version (e.g. v3.4.0). If omitted, latest version is used.',
|
|
22
|
+
positional: true,
|
|
23
|
+
type: 'string',
|
|
24
|
+
default: '',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {Partial<ViewCommand>} [data]
|
|
29
|
+
* @param {import('@nan0web/types').ModelOptions} [options]
|
|
30
|
+
*/
|
|
31
|
+
constructor(data = {}, options = {}) {
|
|
32
|
+
super(data, options)
|
|
33
|
+
/** @type {string} */
|
|
34
|
+
this.version = String(data.version || (options && 'version' in options ? /** @type {any} */ (options).version : ''))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async *run() {
|
|
38
|
+
const targetDir = process.env.RELEASE_CWD || process.env.INIT_CWD || process.cwd()
|
|
39
|
+
const scanner = new Scanner(targetDir)
|
|
40
|
+
const statuses = scanner.status()
|
|
41
|
+
|
|
42
|
+
if (!statuses.length) {
|
|
43
|
+
yield show(ViewCommand.UI.noReleases, 'warn')
|
|
44
|
+
return result({ success: false })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Sort SemVer descending (newest first)
|
|
48
|
+
const sorted = [...statuses].sort((a, b) => b.version.localeCompare(a.version, undefined, { numeric: true }))
|
|
49
|
+
|
|
50
|
+
let targetVer = this.version
|
|
51
|
+
if (!targetVer) {
|
|
52
|
+
// If not specified via CLI arguments, pick the newest
|
|
53
|
+
targetVer = sorted[0].version
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const current = sorted.find((s) => s.version === targetVer) || sorted[0]
|
|
57
|
+
targetVer = current.version
|
|
58
|
+
|
|
59
|
+
// Find release dir
|
|
60
|
+
const allSpecs = scanner.findSpecs(targetVer)
|
|
61
|
+
const allTests = scanner.findTests(targetVer)
|
|
62
|
+
const entry = allSpecs[0] || allTests[0]
|
|
63
|
+
const releaseDir = entry ? entry.dir : ''
|
|
64
|
+
|
|
65
|
+
let progressStr = '100%'
|
|
66
|
+
let taskCount = '8/8'
|
|
67
|
+
|
|
68
|
+
if (releaseDir) {
|
|
69
|
+
try {
|
|
70
|
+
const taskRaw = await readFile(join(releaseDir, 'task.md'), 'utf8')
|
|
71
|
+
const completed = (taskRaw.match(/- \[[xX]\]/g) || []).length
|
|
72
|
+
const pending = (taskRaw.match(/- \[ \]/g) || []).length
|
|
73
|
+
const total = completed + pending
|
|
74
|
+
if (total > 0) {
|
|
75
|
+
const pct = Math.round((completed / total) * 100)
|
|
76
|
+
progressStr = `${pct}%`
|
|
77
|
+
taskCount = `${completed}/${total}`
|
|
78
|
+
}
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
yield show(`Release ${targetVer}`)
|
|
83
|
+
yield show(`Progress ${progressStr} (${taskCount})`)
|
|
84
|
+
yield show('Playground available for CLI, TUI, WEB, CHAT, VOICE to see releases run:')
|
|
85
|
+
yield show(' pnpm exec serve releases # WEB version (http://localhost:3131)')
|
|
86
|
+
yield show(' pnpm exec release # CLI version')
|
|
87
|
+
yield show(' pnpm exec release --tui # TUI version')
|
|
88
|
+
yield show(` pnpm exec release --chat "Show me a release where I have added ${targetVer} features" # CHAT version`)
|
|
89
|
+
|
|
90
|
+
return result({
|
|
91
|
+
version: targetVer,
|
|
92
|
+
progress: progressStr,
|
|
93
|
+
tasks: taskCount,
|
|
94
|
+
dir: releaseDir,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { ViewCommand }
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { ModelAsApp, result, show, progress } from '@nan0web/ui'
|
|
2
|
+
import { DashboardModel } from './Dashboard/DashboardModel.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* WebCommand - ModelAsApp Subcommand to generate and launch PM-as-Code Web Dashboard.
|
|
6
|
+
*/
|
|
7
|
+
export default class WebCommand extends ModelAsApp {
|
|
8
|
+
static alias = 'web'
|
|
9
|
+
|
|
10
|
+
static UI = {
|
|
11
|
+
title: 'web',
|
|
12
|
+
help: 'Generate and launch PM-as-Code Web Dashboard',
|
|
13
|
+
loading: 'Aggregating release metrics for Web Dashboard...',
|
|
14
|
+
generated: '✨ Web Dashboard успішно згенеровано: {$path}',
|
|
15
|
+
serving: '🌐 Web Dashboard запущено: http://localhost:{$port} (Натисніть Ctrl+C для зупинки)',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static registryFile = {
|
|
19
|
+
help: 'Path to releases.txt registry file',
|
|
20
|
+
default: 'releases.txt',
|
|
21
|
+
type: 'string',
|
|
22
|
+
alias: 'f',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static out = {
|
|
26
|
+
help: 'Destination path for HTML dashboard',
|
|
27
|
+
default: 'apps/release.app/play/index.html',
|
|
28
|
+
type: 'string',
|
|
29
|
+
alias: 'o',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static port = {
|
|
33
|
+
help: 'Port to serve HTML dashboard over HTTP (e.g. 3333)',
|
|
34
|
+
default: 0,
|
|
35
|
+
type: 'number',
|
|
36
|
+
alias: 'p',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static open = {
|
|
40
|
+
help: 'Automatically open in browser',
|
|
41
|
+
default: false,
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {Partial<WebCommand>} [data]
|
|
47
|
+
* @param {import('@nan0web/types').ModelOptions} [options]
|
|
48
|
+
*/
|
|
49
|
+
constructor(data = {}, options = {}) {
|
|
50
|
+
super(data, options)
|
|
51
|
+
/** @type {string} */ this.registryFile = String(data.registryFile || 'releases.txt')
|
|
52
|
+
/** @type {string} */ this.out = String(data.out || 'apps/release.app/play/index.html')
|
|
53
|
+
/** @type {number} */ this.port = Number(data.port ?? options.port ?? 0)
|
|
54
|
+
/** @type {boolean} */ this.open = Boolean(data.open ?? options.open ?? false)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async *run() {
|
|
58
|
+
const db = this._?.db || this.db
|
|
59
|
+
let gitTelemetry = null
|
|
60
|
+
try {
|
|
61
|
+
gitTelemetry = await import('../utils/nodejs/GitTelemetry.js')
|
|
62
|
+
} catch {}
|
|
63
|
+
const dashboardModel = new DashboardModel(
|
|
64
|
+
{ registryFile: this.registryFile || 'releases.txt' },
|
|
65
|
+
{ db, gitTelemetry }
|
|
66
|
+
)
|
|
67
|
+
for await (const p of dashboardModel.aggregateStream()) {
|
|
68
|
+
yield progress(`[${p.current}/${p.total}] Scanning ${p.project}...`, p.current, p.total, {
|
|
69
|
+
id: 'web-dashboard',
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
const summary = dashboardModel.summary
|
|
73
|
+
const projects = dashboardModel.projects
|
|
74
|
+
|
|
75
|
+
// Dynamically load ReleaseDashboard from release.app if available or render inline
|
|
76
|
+
let html = ''
|
|
77
|
+
try {
|
|
78
|
+
const { ReleaseDashboard } =
|
|
79
|
+
await import('../../../../apps/release.app/src/ui/web/ReleaseDashboard.js')
|
|
80
|
+
const dashboard = new ReleaseDashboard({ summary, projects })
|
|
81
|
+
html = dashboard.render()
|
|
82
|
+
} catch (err) {
|
|
83
|
+
html = `<!DOCTYPE html><html><body><h1>Dashboard: ${summary.totalProjects} projects</h1><p>${err.message}</p></body></html>`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const outPath = this.out || 'apps/release.app/play/index.html'
|
|
87
|
+
await db.saveDocument(outPath, html)
|
|
88
|
+
|
|
89
|
+
yield progress('', 100, { id: 'web-dashboard', stop: 'success' })
|
|
90
|
+
yield show(WebCommand.UI.generated.replace('{$path}', outPath), 'info')
|
|
91
|
+
|
|
92
|
+
if (this.port > 0) {
|
|
93
|
+
try {
|
|
94
|
+
const { createServer } = await import('node:http')
|
|
95
|
+
const server = createServer((req, res) => {
|
|
96
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
97
|
+
res.end(html)
|
|
98
|
+
})
|
|
99
|
+
server.listen(this.port)
|
|
100
|
+
yield show(WebCommand.UI.serving.replace('{$port}', String(this.port)), 'success')
|
|
101
|
+
|
|
102
|
+
if (this.open) {
|
|
103
|
+
try {
|
|
104
|
+
const { exec } = await import('node:child_process')
|
|
105
|
+
exec(`open http://localhost:${this.port}`)
|
|
106
|
+
} catch {}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (typeof process !== 'undefined' && process.stdin) {
|
|
110
|
+
await new Promise((resolve) => {
|
|
111
|
+
process.on('SIGINT', () => {
|
|
112
|
+
server.close()
|
|
113
|
+
resolve(null)
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
} catch (err) {
|
|
118
|
+
yield show(
|
|
119
|
+
`⚠️ Не вдалося запустити HTTP сервер на порті ${this.port}: ${err.message}`,
|
|
120
|
+
'error'
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
} else if (this.open) {
|
|
124
|
+
try {
|
|
125
|
+
const { exec } = await import('node:child_process')
|
|
126
|
+
exec(`open "${outPath}"`)
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result({
|
|
131
|
+
status: 'ok',
|
|
132
|
+
outPath,
|
|
133
|
+
summary,
|
|
134
|
+
projects,
|
|
135
|
+
port: this.port,
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { WebCommand }
|