@nan0web/release 3.3.0 → 3.5.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/README.md +0 -35
- package/bin/nan0release.js +33 -2
- package/bin/serve.js +73 -0
- package/package.json +46 -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 +33 -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/PMaCParser.js +418 -0
- package/src/domain/PMaCParser.story.js +152 -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/ValidateCommand.js +426 -0
- package/src/domain/ValidateCommand.story.js +92 -0
- 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/models/ReleaseIssueModel.js +27 -0
- package/src/domain/models/ReleaseMemberModel.js +21 -0
- package/src/domain/models/ReleaseModel.js +191 -0
- package/src/domain/models/ReleaseParityModel.js +27 -0
- package/src/domain/models/ReleaseTaskModel.js +33 -0
- package/src/domain/models/index.js +6 -0
- package/src/domain/templates/release.html +34 -0
- package/src/domain/templates/release.html.template.js +34 -0
- package/src/index.js +15 -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 +24 -27
- package/types/Release/MarkdownToTest.d.ts +1 -1
- package/types/Release/Person.d.ts +9 -7
- package/types/Release/Scanner.d.ts +41 -41
- package/types/Release.d.ts +42 -42
- package/types/architecture/ProjectManagementAsCode.d.ts +59 -64
- package/types/domain/App.d.ts +25 -7
- package/types/domain/BumpCommand.d.ts +11 -6
- package/types/domain/CheckCommand.d.ts +128 -16
- package/types/domain/CheckCommand.story.d.ts +1 -0
- package/types/domain/CloseCommand.d.ts +4 -6
- 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 +6 -6
- package/types/domain/InitCommand.d.ts +4 -6
- package/types/domain/ListCommand.d.ts +18 -0
- package/types/domain/PMaCParser.d.ts +64 -0
- package/types/domain/PMaCParser.story.d.ts +1 -0
- package/types/domain/PreviewCommand.d.ts +6 -0
- package/types/domain/PublishCommand.d.ts +8 -6
- 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 +4 -6
- package/types/domain/StatusCommand.d.ts +38 -3
- package/types/domain/ValidateCommand.d.ts +69 -0
- package/types/domain/ValidateCommand.story.d.ts +1 -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/models/ReleaseIssueModel.d.ts +35 -0
- package/types/domain/models/ReleaseMemberModel.d.ts +25 -0
- package/types/domain/models/ReleaseModel.d.ts +122 -0
- package/types/domain/models/ReleaseParityModel.d.ts +37 -0
- package/types/domain/models/ReleaseTaskModel.d.ts +46 -0
- package/types/domain/models/index.d.ts +5 -0
- package/types/domain/templates/release.html.template.d.ts +2 -0
- package/types/index.d.ts +21 -10
- package/types/inspectors/inspect-js.d.ts +2 -0
- package/types/utils/nodejs/GitTelemetry.d.ts +23 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Model } from '@nan0web/types'
|
|
2
|
+
import { ReleaseMemberModel } from './ReleaseMemberModel.js'
|
|
3
|
+
import { ReleaseTaskModel } from './ReleaseTaskModel.js'
|
|
4
|
+
import { ReleaseParityModel } from './ReleaseParityModel.js'
|
|
5
|
+
import { ReleaseIssueModel } from './ReleaseIssueModel.js'
|
|
6
|
+
|
|
7
|
+
export class ReleaseModel extends Model {
|
|
8
|
+
static version = { type: 'string', required: true }
|
|
9
|
+
static title = { type: 'string', required: true }
|
|
10
|
+
static status = { type: 'string', default: 'todo' } // 'todo' | 'work' | 'done' | 'fail'
|
|
11
|
+
static type = { type: 'string', default: 'minor' } // 'major' | 'minor' | 'patch' | 'hotfix' | 'chore'
|
|
12
|
+
static summary = { type: 'string' }
|
|
13
|
+
static epic = { type: 'string' }
|
|
14
|
+
static sprint = { type: 'string' }
|
|
15
|
+
static tracker = { type: 'string' }
|
|
16
|
+
|
|
17
|
+
// Sub-collections
|
|
18
|
+
static team = { type: 'array', itemType: ReleaseMemberModel, default: [] }
|
|
19
|
+
static tasks = { type: 'array', itemType: ReleaseTaskModel, default: [] }
|
|
20
|
+
static criteria = { type: 'array', itemType: ReleaseTaskModel, default: [] }
|
|
21
|
+
static parity = { type: 'array', itemType: ReleaseParityModel, default: [] }
|
|
22
|
+
static issues = { type: 'array', itemType: ReleaseIssueModel, default: [] }
|
|
23
|
+
static files = { type: 'array', default: [] }
|
|
24
|
+
static meta = { type: 'object', default: {} }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {Partial<ReleaseModel>} [data={}]
|
|
28
|
+
* @param {Partial<import('@nan0web/types').ModelOptions>} [options={}]
|
|
29
|
+
*/
|
|
30
|
+
constructor(data = {}, options = {}) {
|
|
31
|
+
super(data, options)
|
|
32
|
+
/** @type {string} Release version */
|
|
33
|
+
this.version
|
|
34
|
+
/** @type {string} Release title */
|
|
35
|
+
this.title
|
|
36
|
+
/** @type {'todo' | 'work' | 'done' | 'fail'} Release status */
|
|
37
|
+
this.status
|
|
38
|
+
/** @type {'major' | 'minor' | 'patch' | 'hotfix' | 'chore'} Release type */
|
|
39
|
+
this.type
|
|
40
|
+
/** @type {string} Release summary / description */
|
|
41
|
+
this.summary
|
|
42
|
+
/** @type {string} Epic identifier */
|
|
43
|
+
this.epic
|
|
44
|
+
/** @type {string} Sprint name */
|
|
45
|
+
this.sprint
|
|
46
|
+
/** @type {string} External tracker URL template */
|
|
47
|
+
this.tracker
|
|
48
|
+
/** @type {ReleaseMemberModel[]} Team members (ролі, імена, логіни) */
|
|
49
|
+
this.team
|
|
50
|
+
/** @type {ReleaseTaskModel[]} Tasks in scope */
|
|
51
|
+
this.tasks
|
|
52
|
+
/** @type {ReleaseTaskModel[]} Acceptance criteria (DoD) */
|
|
53
|
+
this.criteria
|
|
54
|
+
/** @type {ReleaseParityModel[]} Data parity verification items */
|
|
55
|
+
this.parity
|
|
56
|
+
/** @type {ReleaseIssueModel[]} Issues and risks */
|
|
57
|
+
this.issues
|
|
58
|
+
/** @type {Array<{file: string, size?: string, hash?: string, type?: string}>} Artifact files */
|
|
59
|
+
this.files
|
|
60
|
+
/** @type {Record<string, any>} Frontmatter / metadata */
|
|
61
|
+
this.meta
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static SECTIONS = {
|
|
65
|
+
summary: [
|
|
66
|
+
// EN (English)
|
|
67
|
+
'summary', 'objective', 'description', 'overview',
|
|
68
|
+
// UK (Українська)
|
|
69
|
+
'опис', 'мета', 'ціль', 'огляд',
|
|
70
|
+
// ES (Español)
|
|
71
|
+
'resumen', 'objetivo', 'descripcion',
|
|
72
|
+
// FR (Français)
|
|
73
|
+
'resume', 'objectif',
|
|
74
|
+
// DE (Deutsch)
|
|
75
|
+
'zusammenfassung', 'ziel', 'beschreibung',
|
|
76
|
+
// ZH (中文 - Chinese)
|
|
77
|
+
'摘要', '目标', '描述',
|
|
78
|
+
// RU (Російська - для сумісності з двомовними українцями)
|
|
79
|
+
'описание', 'цель', 'обзор',
|
|
80
|
+
],
|
|
81
|
+
team: [
|
|
82
|
+
// EN (English)
|
|
83
|
+
'team', 'members', 'contributors', 'roles',
|
|
84
|
+
// UK (Українська)
|
|
85
|
+
'команда', 'учасники', 'ролі',
|
|
86
|
+
// ES (Español)
|
|
87
|
+
'equipo', 'miembros',
|
|
88
|
+
// FR (Français)
|
|
89
|
+
'equipe', 'membres',
|
|
90
|
+
// DE (Deutsch)
|
|
91
|
+
'mitglieder',
|
|
92
|
+
// ZH (中文 - Chinese)
|
|
93
|
+
'团队', '成员',
|
|
94
|
+
// RU (Російська)
|
|
95
|
+
'участники', 'роли',
|
|
96
|
+
],
|
|
97
|
+
tasks: [
|
|
98
|
+
// EN (English)
|
|
99
|
+
'tasks', 'scope', 'work plan', 'wbs', 'backlog', 'plan',
|
|
100
|
+
// UK (Українська)
|
|
101
|
+
'план', 'задачі', 'робочий план', 'обсяг',
|
|
102
|
+
// ES (Español)
|
|
103
|
+
'tareas', 'plan de trabajo',
|
|
104
|
+
// FR (Français)
|
|
105
|
+
'taches', 'plan de travail',
|
|
106
|
+
// DE (Deutsch)
|
|
107
|
+
'aufgaben', 'arbeitsplan',
|
|
108
|
+
// ZH (中文 - Chinese)
|
|
109
|
+
'任务', '工作计划',
|
|
110
|
+
// RU (Російська)
|
|
111
|
+
'задачи', 'рабочий план',
|
|
112
|
+
],
|
|
113
|
+
criteria: [
|
|
114
|
+
// EN (English)
|
|
115
|
+
'acceptance', 'acceptance criteria', 'dod', 'definition of done', 'requirements',
|
|
116
|
+
// UK (Українська)
|
|
117
|
+
'критерії', 'критерії прийнятності', 'вимоги',
|
|
118
|
+
// ES (Español)
|
|
119
|
+
'criterios', 'criterios de aceptacion', 'requisitos',
|
|
120
|
+
// FR (Français)
|
|
121
|
+
'criteres', 'criteres d acceptation', 'exigences',
|
|
122
|
+
// DE (Deutsch)
|
|
123
|
+
'akzeptanzkriterien', 'anforderungen',
|
|
124
|
+
// ZH (中文 - Chinese)
|
|
125
|
+
'验收标准', '准入条件', '要求',
|
|
126
|
+
// RU (Російська)
|
|
127
|
+
'критерии', 'критерии приемки', 'требования',
|
|
128
|
+
],
|
|
129
|
+
parity: [
|
|
130
|
+
// EN (English)
|
|
131
|
+
'parity', 'data parity', 'audit', 'verification',
|
|
132
|
+
// UK (Українська)
|
|
133
|
+
'паритет', 'аудит', 'паритет даних', 'верифікація',
|
|
134
|
+
// ES (Español)
|
|
135
|
+
'paridad', 'auditoria',
|
|
136
|
+
// FR (Français)
|
|
137
|
+
'parite',
|
|
138
|
+
// DE (Deutsch)
|
|
139
|
+
'datenparitaet',
|
|
140
|
+
// ZH (中文 - Chinese)
|
|
141
|
+
'数据对齐', '数据一致性', '审计',
|
|
142
|
+
// RU (Російська)
|
|
143
|
+
'паритет данных', 'верификация',
|
|
144
|
+
],
|
|
145
|
+
issues: [
|
|
146
|
+
// EN (English)
|
|
147
|
+
'issues', 'risks', 'blockers', 'problems', 'notes',
|
|
148
|
+
// UK (Українська)
|
|
149
|
+
'ризики', 'зауваження', 'блокери', 'проблеми',
|
|
150
|
+
// ES (Español)
|
|
151
|
+
'riesgos', 'problemas',
|
|
152
|
+
// FR (Français)
|
|
153
|
+
'risques',
|
|
154
|
+
// DE (Deutsch)
|
|
155
|
+
'risiken', 'hinweise',
|
|
156
|
+
// ZH (中文 - Chinese)
|
|
157
|
+
'风险', '问题', '注意事项',
|
|
158
|
+
// RU (Російська)
|
|
159
|
+
'риски', 'замечания', 'блокеры',
|
|
160
|
+
],
|
|
161
|
+
artifacts: [
|
|
162
|
+
// EN (English)
|
|
163
|
+
'artifacts', 'files', 'provenance', 'deliverables',
|
|
164
|
+
// UK (Українська)
|
|
165
|
+
'артефакти', 'файли',
|
|
166
|
+
// ES (Español)
|
|
167
|
+
'artefactos', 'archivos',
|
|
168
|
+
// FR (Français)
|
|
169
|
+
'artefacts', 'fichiers',
|
|
170
|
+
// DE (Deutsch)
|
|
171
|
+
'artefakte', 'dateien',
|
|
172
|
+
// ZH (中文 - Chinese)
|
|
173
|
+
'产物', '文件',
|
|
174
|
+
// RU (Російська)
|
|
175
|
+
'артефакты',
|
|
176
|
+
],
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Extend section synonyms dictionary with custom locale mappings.
|
|
181
|
+
* @param {Record<string, string[]>} localeMap
|
|
182
|
+
*/
|
|
183
|
+
static addLocaleSections(localeMap) {
|
|
184
|
+
for (const [section, words] of Object.entries(localeMap)) {
|
|
185
|
+
if (this.SECTIONS[section]) {
|
|
186
|
+
this.SECTIONS[section].push(...words)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Model } from '@nan0web/types'
|
|
2
|
+
|
|
3
|
+
export class ReleaseParityModel extends Model {
|
|
4
|
+
static collection = { type: 'string', required: true }
|
|
5
|
+
static expected = { type: 'number', required: true }
|
|
6
|
+
static actual = { type: 'number', required: true }
|
|
7
|
+
static synced = { type: 'boolean', default: true }
|
|
8
|
+
static comment = { type: 'string' }
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {Partial<ReleaseParityModel>} [data={}]
|
|
12
|
+
* @param {Partial<import('@nan0web/types').ModelOptions>} [options={}]
|
|
13
|
+
*/
|
|
14
|
+
constructor(data = {}, options = {}) {
|
|
15
|
+
super(data, options)
|
|
16
|
+
/** @type {string} Collection name */
|
|
17
|
+
this.collection
|
|
18
|
+
/** @type {number} Expected count */
|
|
19
|
+
this.expected
|
|
20
|
+
/** @type {number} Actual count */
|
|
21
|
+
this.actual
|
|
22
|
+
/** @type {boolean} Synced flag */
|
|
23
|
+
this.synced
|
|
24
|
+
/** @type {string} Audit comment */
|
|
25
|
+
this.comment
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Model } from '@nan0web/types'
|
|
2
|
+
|
|
3
|
+
export class ReleaseTaskModel extends Model {
|
|
4
|
+
static id = { type: 'string', required: true }
|
|
5
|
+
static title = { type: 'string', required: true }
|
|
6
|
+
static status = { type: 'string', default: 'todo' } // 'todo' | 'work' | 'done' | 'fail'
|
|
7
|
+
static assignee = { type: 'string' }
|
|
8
|
+
static estimateHours = { type: 'number' }
|
|
9
|
+
static spentHours = { type: 'number' }
|
|
10
|
+
static notes = { type: 'string' }
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {Partial<ReleaseTaskModel>} [data={}]
|
|
14
|
+
* @param {Partial<import('@nan0web/types').ModelOptions>} [options={}]
|
|
15
|
+
*/
|
|
16
|
+
constructor(data = {}, options = {}) {
|
|
17
|
+
super(data, options)
|
|
18
|
+
/** @type {string} Task ID */
|
|
19
|
+
this.id
|
|
20
|
+
/** @type {string} Task Title */
|
|
21
|
+
this.title
|
|
22
|
+
/** @type {'todo' | 'work' | 'done' | 'fail'} Task Status */
|
|
23
|
+
this.status
|
|
24
|
+
/** @type {string} Assignee */
|
|
25
|
+
this.assignee
|
|
26
|
+
/** @type {number} Estimated Hours */
|
|
27
|
+
this.estimateHours
|
|
28
|
+
/** @type {number} Spent Hours */
|
|
29
|
+
this.spentHours
|
|
30
|
+
/** @type {string} Notes */
|
|
31
|
+
this.notes
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ReleaseMemberModel } from './ReleaseMemberModel.js'
|
|
2
|
+
export { ReleaseTaskModel } from './ReleaseTaskModel.js'
|
|
3
|
+
export { ReleaseParityModel } from './ReleaseParityModel.js'
|
|
4
|
+
export { ReleaseIssueModel } from './ReleaseIssueModel.js'
|
|
5
|
+
export { ReleaseModel } from './ReleaseModel.js'
|
|
6
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="uk" data-bs-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Releases Catalog — NaN•Web</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #0f172a;
|
|
10
|
+
--surface: #1e293b;
|
|
11
|
+
--accent: #38bdf8;
|
|
12
|
+
--text: #f8fafc;
|
|
13
|
+
--text-dim: #94a3b8;
|
|
14
|
+
--border: #334155;
|
|
15
|
+
}
|
|
16
|
+
body { font-family: system-ui, -apple-system, sans-serif; background: var(--bg); color: var(--text); margin: 0; padding: 2.5rem 1.5rem; display: flex; justify-content: center; line-height: 1.6; }
|
|
17
|
+
.container { max-width: 880px; width: 100%; }
|
|
18
|
+
h1, h2, h3 { color: #fff; font-weight: 700; margin-top: 1.5rem; }
|
|
19
|
+
h1 { font-size: 2rem; border-bottom: 1px solid var(--border); padding-bottom: 0.8rem; }
|
|
20
|
+
a { color: var(--accent); text-decoration: none; }
|
|
21
|
+
a:hover { text-decoration: underline; }
|
|
22
|
+
table { width: 100%; border-collapse: collapse; margin: 1.5rem 0; background: var(--surface); border-radius: 8px; overflow: hidden; }
|
|
23
|
+
th, td { padding: 0.85rem 1rem; text-align: left; border-bottom: 1px solid var(--border); }
|
|
24
|
+
th { background: rgba(0,0,0,0.2); font-weight: 600; color: var(--accent); }
|
|
25
|
+
pre { background: var(--surface); padding: 1rem; border-radius: 8px; border: 1px solid var(--border); overflow-x: auto; color: #a5b4fc; font-family: monospace; }
|
|
26
|
+
blockquote { border-left: 4px solid var(--accent); margin: 1rem 0; padding: 0.5rem 1rem; background: var(--surface); border-radius: 0 8px 8px 0; }
|
|
27
|
+
</style>
|
|
28
|
+
</head>
|
|
29
|
+
<body>
|
|
30
|
+
<div class="container">
|
|
31
|
+
{{CONTENT}}
|
|
32
|
+
</div>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export default `<!DOCTYPE html>
|
|
2
|
+
<html lang="uk" data-bs-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Releases Catalog — NaN•Web</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #0f172a;
|
|
10
|
+
--surface: #1e293b;
|
|
11
|
+
--accent: #38bdf8;
|
|
12
|
+
--text: #f8fafc;
|
|
13
|
+
--text-dim: #94a3b8;
|
|
14
|
+
--border: #334155;
|
|
15
|
+
}
|
|
16
|
+
body { font-family: system-ui, -apple-system, sans-serif; background: var(--bg); color: var(--text); margin: 0; padding: 2.5rem 1.5rem; display: flex; justify-content: center; line-height: 1.6; }
|
|
17
|
+
.container { max-width: 880px; width: 100%; }
|
|
18
|
+
h1, h2, h3 { color: #fff; font-weight: 700; margin-top: 1.5rem; }
|
|
19
|
+
h1 { font-size: 2rem; border-bottom: 1px solid var(--border); padding-bottom: 0.8rem; }
|
|
20
|
+
a { color: var(--accent); text-decoration: none; }
|
|
21
|
+
a:hover { text-decoration: underline; }
|
|
22
|
+
table { width: 100%; border-collapse: collapse; margin: 1.5rem 0; background: var(--surface); border-radius: 8px; overflow: hidden; }
|
|
23
|
+
th, td { padding: 0.85rem 1rem; text-align: left; border-bottom: 1px solid var(--border); }
|
|
24
|
+
th { background: rgba(0,0,0,0.2); font-weight: 600; color: var(--accent); }
|
|
25
|
+
pre { background: var(--surface); padding: 1rem; border-radius: 8px; border: 1px solid var(--border); overflow-x: auto; color: #a5b4fc; font-family: monospace; }
|
|
26
|
+
blockquote { border-left: 4px solid var(--accent); margin: 1rem 0; padding: 0.5rem 1rem; background: var(--surface); border-radius: 0 8px 8px 0; }
|
|
27
|
+
</style>
|
|
28
|
+
</head>
|
|
29
|
+
<body>
|
|
30
|
+
<div class="container">
|
|
31
|
+
{{CONTENT}}
|
|
32
|
+
</div>
|
|
33
|
+
</body>
|
|
34
|
+
</html>`
|
package/src/index.js
CHANGED
|
@@ -30,3 +30,18 @@ export { default as DepsCommand } from './domain/DepsCommand.js'
|
|
|
30
30
|
export { default as PublishCommand } from './domain/PublishCommand.js'
|
|
31
31
|
export { default as SpecCommand } from './domain/SpecCommand.js'
|
|
32
32
|
export { default as StatusCommand } from './domain/StatusCommand.js'
|
|
33
|
+
export { default as WebCommand } from './domain/WebCommand.js'
|
|
34
|
+
export { default as PreviewCommand } from './domain/PreviewCommand.js'
|
|
35
|
+
export { RegistryModel } from './domain/Registry/RegistryModel.js'
|
|
36
|
+
export { DashboardModel } from './domain/Dashboard/DashboardModel.js'
|
|
37
|
+
export { ReleaseAuditor } from './domain/inspect/ReleaseAuditor.js'
|
|
38
|
+
export {
|
|
39
|
+
ReleaseModel,
|
|
40
|
+
ReleaseMemberModel,
|
|
41
|
+
ReleaseTaskModel,
|
|
42
|
+
ReleaseParityModel,
|
|
43
|
+
ReleaseIssueModel,
|
|
44
|
+
} from './domain/models/index.js'
|
|
45
|
+
export { PMaCParser, resolveTrackerUrl } from './domain/PMaCParser.js'
|
|
46
|
+
export { ValidateCommand } from './domain/ValidateCommand.js'
|
|
47
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Inspector: checks JavaScript/TypeScript files for syntax errors using `node --check`.
|
|
3
|
+
// Uses git to obtain list of changed files in the current HEAD.
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
function getChangedFiles() {
|
|
7
|
+
try {
|
|
8
|
+
const output = execSync('git diff --name-only --diff-filter=ACM HEAD', { encoding: 'utf8' });
|
|
9
|
+
return output.split(/\n/).filter(f => f.match(/\.(js|ts)$/));
|
|
10
|
+
} catch (e) {
|
|
11
|
+
console.error('Failed to obtain changed files:', e.message);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function checkFile(file) {
|
|
17
|
+
console.log(`🔎 Checking ${file}`);
|
|
18
|
+
try {
|
|
19
|
+
execSync(`node --check ${file}`, { stdio: 'inherit' });
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error(`❌ Syntax error in ${file}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const files = getChangedFiles();
|
|
27
|
+
if (files.length === 0) {
|
|
28
|
+
console.log('✅ No JavaScript/TypeScript files changed.');
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
files.forEach(checkFile);
|
|
32
|
+
process.exit(0);
|
|
@@ -9,7 +9,7 @@ describe('Release v3.0.0 - Dependency Alignment', () => {
|
|
|
9
9
|
const pkgPath = join(process.cwd(), 'package.json')
|
|
10
10
|
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
11
11
|
|
|
12
|
-
assert.ok(/^3
|
|
12
|
+
assert.ok(/^3\.\d+\.\d+$/.test(pkg.version), 'Version should start with 3.x')
|
|
13
13
|
assert.ok(/^(\^1\.8\.0|workspace:\*)$/.test(pkg.dependencies['@nan0web/ui']), '@nan0web/ui should be ^1.8.0 or workspace:*')
|
|
14
14
|
assert.ok(/^(\^2\.9\.0|workspace:\*)$/.test(pkg.dependencies['@nan0web/ui-cli']), '@nan0web/ui-cli should be ^2.9.0 or workspace:*')
|
|
15
15
|
})
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import { execSync } from 'node:child_process'
|
|
3
|
+
|
|
4
|
+
let rootGitStatusCache = null
|
|
5
|
+
let rootGitBranchCache = null
|
|
6
|
+
const gitInfoCache = new Map()
|
|
7
|
+
const gitTelemetryCache = new Map()
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reset internal Git caches (useful for testing or cache invalidation)
|
|
11
|
+
*/
|
|
12
|
+
export function resetGitCache() {
|
|
13
|
+
rootGitStatusCache = null
|
|
14
|
+
rootGitBranchCache = null
|
|
15
|
+
gitInfoCache.clear()
|
|
16
|
+
gitTelemetryCache.clear()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Retrieve git branch and workspace status (clean / dirty)
|
|
21
|
+
* @param {string} [projectDir]
|
|
22
|
+
* @returns {{ branch: string, gitStatus: 'clean' | 'dirty' }}
|
|
23
|
+
*/
|
|
24
|
+
export function getGitInfo(projectDir) {
|
|
25
|
+
const key = projectDir || process.cwd()
|
|
26
|
+
const isMonorepoInternal =
|
|
27
|
+
!projectDir ||
|
|
28
|
+
projectDir === '.' ||
|
|
29
|
+
(!projectDir.startsWith('../') && !projectDir.startsWith('/'))
|
|
30
|
+
|
|
31
|
+
if (isMonorepoInternal) {
|
|
32
|
+
if (rootGitStatusCache === null) {
|
|
33
|
+
try {
|
|
34
|
+
rootGitBranchCache =
|
|
35
|
+
execSync('git rev-parse --abbrev-ref HEAD', {
|
|
36
|
+
cwd: process.cwd(),
|
|
37
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
38
|
+
encoding: 'utf8',
|
|
39
|
+
}).trim() || 'main'
|
|
40
|
+
rootGitStatusCache = execSync('git status --porcelain', {
|
|
41
|
+
cwd: process.cwd(),
|
|
42
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
}).trim()
|
|
45
|
+
} catch {
|
|
46
|
+
rootGitBranchCache = 'main'
|
|
47
|
+
rootGitStatusCache = ''
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const normPrefix =
|
|
51
|
+
projectDir && projectDir !== '.'
|
|
52
|
+
? `${projectDir.replace(/\/+$/, '')}/`
|
|
53
|
+
: ''
|
|
54
|
+
const isDirty = normPrefix
|
|
55
|
+
? rootGitStatusCache
|
|
56
|
+
.split('\n')
|
|
57
|
+
.some((line) => line.slice(3).trim().startsWith(normPrefix))
|
|
58
|
+
: rootGitStatusCache.length > 0
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
branch: rootGitBranchCache,
|
|
62
|
+
gitStatus: isDirty ? 'dirty' : 'clean',
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (gitInfoCache.has(key)) return gitInfoCache.get(key)
|
|
67
|
+
if (fs.existsSync && !fs.existsSync(key)) {
|
|
68
|
+
const res = { branch: 'main', gitStatus: 'clean' }
|
|
69
|
+
gitInfoCache.set(key, res)
|
|
70
|
+
return res
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
75
|
+
cwd: key,
|
|
76
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
77
|
+
encoding: 'utf8',
|
|
78
|
+
}).trim()
|
|
79
|
+
const statusOutput = execSync('git status --porcelain', {
|
|
80
|
+
cwd: key,
|
|
81
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
82
|
+
encoding: 'utf8',
|
|
83
|
+
}).trim()
|
|
84
|
+
const res = {
|
|
85
|
+
branch: branch || 'main',
|
|
86
|
+
gitStatus: statusOutput.length > 0 ? 'dirty' : 'clean',
|
|
87
|
+
}
|
|
88
|
+
gitInfoCache.set(key, res)
|
|
89
|
+
return res
|
|
90
|
+
} catch {
|
|
91
|
+
const res = { branch: 'main', gitStatus: 'clean' }
|
|
92
|
+
gitInfoCache.set(key, res)
|
|
93
|
+
return res
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Retrieve development hours and iterations from git commit log
|
|
99
|
+
* @param {string} [projectDir]
|
|
100
|
+
* @param {string} [releasePath]
|
|
101
|
+
* @returns {{ hours: number, iterations: number }}
|
|
102
|
+
*/
|
|
103
|
+
export function getGitCommitTelemetry(projectDir, releasePath) {
|
|
104
|
+
const targetDir = releasePath || projectDir
|
|
105
|
+
if (!targetDir) return { hours: 0, iterations: 0 }
|
|
106
|
+
if (gitTelemetryCache.has(targetDir)) return gitTelemetryCache.get(targetDir)
|
|
107
|
+
if (fs.existsSync && !fs.existsSync(targetDir)) {
|
|
108
|
+
const res = { hours: 0, iterations: 0 }
|
|
109
|
+
gitTelemetryCache.set(targetDir, res)
|
|
110
|
+
return res
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const logOutput = execSync(
|
|
115
|
+
`git log --format="%at" -n 50 -- "${targetDir}"`,
|
|
116
|
+
{
|
|
117
|
+
cwd: process.cwd(),
|
|
118
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
119
|
+
encoding: 'utf8',
|
|
120
|
+
}
|
|
121
|
+
).trim()
|
|
122
|
+
|
|
123
|
+
if (!logOutput) {
|
|
124
|
+
const res = { hours: 0, iterations: 0 }
|
|
125
|
+
gitTelemetryCache.set(targetDir, res)
|
|
126
|
+
return res
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const timestamps = logOutput
|
|
130
|
+
.split('\n')
|
|
131
|
+
.map((t) => parseInt(t.trim(), 10))
|
|
132
|
+
.filter((t) => !isNaN(t))
|
|
133
|
+
|
|
134
|
+
if (timestamps.length < 2) {
|
|
135
|
+
const res = { hours: 0.5, iterations: timestamps.length }
|
|
136
|
+
gitTelemetryCache.set(targetDir, res)
|
|
137
|
+
return res
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const minT = Math.min(...timestamps)
|
|
141
|
+
const maxT = Math.max(...timestamps)
|
|
142
|
+
const diffHours = Math.round(((maxT - minT) / 3600) * 10) / 10
|
|
143
|
+
const hours = diffHours > 0 ? diffHours : 0.5
|
|
144
|
+
const iterations = timestamps.length
|
|
145
|
+
|
|
146
|
+
const res = { hours, iterations }
|
|
147
|
+
gitTelemetryCache.set(targetDir, res)
|
|
148
|
+
return res
|
|
149
|
+
} catch {
|
|
150
|
+
const res = { hours: 0, iterations: 0 }
|
|
151
|
+
gitTelemetryCache.set(targetDir, res)
|
|
152
|
+
return res
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import Person from './Person.js';
|
|
1
|
+
export default ReleaseDocument;
|
|
3
2
|
declare class ReleaseDocument extends Markdown {
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
version: string;
|
|
10
|
-
/** @type {Date | undefined} */
|
|
11
|
-
date: Date | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* @param {*} input
|
|
5
|
+
* @returns {ReleaseDocument}
|
|
6
|
+
*/
|
|
7
|
+
static from(input: any): ReleaseDocument;
|
|
12
8
|
/**
|
|
13
9
|
* @param {Object} options
|
|
14
10
|
* @param {Array} [options.team] - Release team members
|
|
@@ -17,22 +13,23 @@ declare class ReleaseDocument extends Markdown {
|
|
|
17
13
|
* @param {string} [options.date] - Release date
|
|
18
14
|
*/
|
|
19
15
|
constructor(options?: {
|
|
20
|
-
team?: any[];
|
|
21
|
-
roles?: Map<any, any> |
|
|
22
|
-
version?: string;
|
|
23
|
-
date?: string;
|
|
16
|
+
team?: any[] | undefined;
|
|
17
|
+
roles?: any[] | Map<any, any> | undefined;
|
|
18
|
+
version?: string | undefined;
|
|
19
|
+
date?: string | undefined;
|
|
24
20
|
});
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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;
|
|
29
|
+
sections: {
|
|
30
|
+
title: string;
|
|
31
|
+
tasks: any[];
|
|
32
|
+
}[] | undefined;
|
|
37
33
|
}
|
|
38
|
-
|
|
34
|
+
import Markdown from '@nan0web/markdown';
|
|
35
|
+
import Person from './Person.js';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export default MarkdownToTest;
|
|
1
2
|
/**
|
|
2
3
|
* MarkdownToTest - generates node:test code from markdown release notes
|
|
3
4
|
* Enables writing release notes first, then generating executable tests
|
|
@@ -44,4 +45,3 @@ declare class MarkdownToTest {
|
|
|
44
45
|
tasks: any[];
|
|
45
46
|
}>): string;
|
|
46
47
|
}
|
|
47
|
-
export default MarkdownToTest;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { HumanName, HumanContact, HumanGender } from '@nan0web/logos';
|
|
2
1
|
export default class Person {
|
|
2
|
+
/**
|
|
3
|
+
* @param {*} input
|
|
4
|
+
* @returns {Person}
|
|
5
|
+
*/
|
|
6
|
+
static from(input: any): Person;
|
|
7
|
+
constructor(input?: {});
|
|
3
8
|
/** @type {HumanName} */
|
|
4
9
|
name: HumanName;
|
|
5
10
|
/** @type {HumanGender} */
|
|
6
11
|
gender: HumanGender;
|
|
7
12
|
/** @type {HumanContact[]} */
|
|
8
13
|
contacts: HumanContact[];
|
|
9
|
-
constructor(input?: {});
|
|
10
|
-
/**
|
|
11
|
-
* @param {*} input
|
|
12
|
-
* @returns {Person}
|
|
13
|
-
*/
|
|
14
|
-
static from(input: any): Person;
|
|
15
14
|
}
|
|
15
|
+
import { HumanName } from '@nan0web/logos';
|
|
16
|
+
import { HumanGender } from '@nan0web/logos';
|
|
17
|
+
import { HumanContact } from '@nan0web/logos';
|