@nan0web/release 1.0.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.
Files changed (79) hide show
  1. package/.datasets/README.dataset.jsonl +19 -0
  2. package/.editorconfig +20 -0
  3. package/CHANGELOG.md +6 -0
  4. package/CONTRIBUTING.md +42 -0
  5. package/LICENSE +15 -0
  6. package/README.md +284 -0
  7. package/bin/commands/InitCommand.js +10 -0
  8. package/bin/lib/functions.js +240 -0
  9. package/bin/lib/functions.test.js +138 -0
  10. package/bin/lib/temp/pnpm-lock.yaml +0 -0
  11. package/bin/nan0release.js +57 -0
  12. package/bin/release.js +12 -0
  13. package/bin/release.test.js +73 -0
  14. package/bin/temp/package.json +1 -0
  15. package/docs/system.uk.md +413 -0
  16. package/docs/uk/README.md +284 -0
  17. package/legacy/PrepareCommand.js +154 -0
  18. package/legacy/PrepareCommand.test.js +189 -0
  19. package/legacy/changelog.js +446 -0
  20. package/package.json +43 -0
  21. package/releases/1/0/v1.0.0/models/Person.js +26 -0
  22. package/releases/1/0/v1.0.0/models/Person.test.js +44 -0
  23. package/releases/1/0/v1.0.0/models/company.js +11 -0
  24. package/releases/1/0/v1.0.0/models/members.js +14 -0
  25. package/releases/1/0/v1.0.0/models/members.test.js +21 -0
  26. package/releases/1/0/v1.0.0/models/teams/DevTeam.js +6 -0
  27. package/releases/1/0/v1.0.0/models/teams/DevTeam.test.js +15 -0
  28. package/releases/1/0/v1.0.0/models/teams/index.js +5 -0
  29. package/releases/1/0/v1.0.0/release.js +12 -0
  30. package/releases/1/0/v1.0.0/release.md +121 -0
  31. package/releases/1/0/v1.0.0/release.test.js +36 -0
  32. package/releases/1/0/v1.0.0/test/YaRaSLove/git-native.test.js +14 -0
  33. package/releases/current.js +3 -0
  34. package/releases/index.js +11 -0
  35. package/src/README.md.js +468 -0
  36. package/src/Release/Document.js +110 -0
  37. package/src/Release/MarkdownToTest.js +137 -0
  38. package/src/Release/MarkdownToTest.test.js +60 -0
  39. package/src/Release/Person.js +29 -0
  40. package/src/Release.js +132 -0
  41. package/src/ReleaseCLI.js +122 -0
  42. package/src/Team.js +35 -0
  43. package/src/architecture/ProjectManagementAsCode.js +264 -0
  44. package/src/architecture/ProjectManagementAsCode.test.js +128 -0
  45. package/src/co/AppCommandMessage.js +22 -0
  46. package/src/co/AppCommandOptions.js +27 -0
  47. package/src/commands/ChatCommand.js +56 -0
  48. package/src/commands/HostCommand.js +63 -0
  49. package/src/commands/InitCommand.js +65 -0
  50. package/src/commands/ListCommand.js +56 -0
  51. package/src/commands/PublishCommand.js +144 -0
  52. package/src/commands/SealCommand.js +51 -0
  53. package/src/commands/ServeCommand.js +53 -0
  54. package/src/commands/ShowCommand.js +58 -0
  55. package/src/commands/SubCommand.js +21 -0
  56. package/src/commands/ValidateCommand.js +66 -0
  57. package/src/db/ReleaseDB.js +54 -0
  58. package/system.md +571 -0
  59. package/tsconfig.json +23 -0
  60. package/types/Release/Document.d.ts +31 -0
  61. package/types/Release/MarkdownToTest.d.ts +47 -0
  62. package/types/Release/Person.d.ts +17 -0
  63. package/types/Release.d.ts +89 -0
  64. package/types/ReleaseCLI.d.ts +33 -0
  65. package/types/Team.d.ts +27 -0
  66. package/types/architecture/ProjectManagementAsCode.d.ts +159 -0
  67. package/types/co/AppCommandMessage.d.ts +12 -0
  68. package/types/co/AppCommandOptions.d.ts +23 -0
  69. package/types/commands/ChatCommand.d.ts +20 -0
  70. package/types/commands/HostCommand.d.ts +16 -0
  71. package/types/commands/InitCommand.d.ts +20 -0
  72. package/types/commands/ListCommand.d.ts +21 -0
  73. package/types/commands/PublishCommand.d.ts +49 -0
  74. package/types/commands/SealCommand.d.ts +21 -0
  75. package/types/commands/ServeCommand.d.ts +17 -0
  76. package/types/commands/ShowCommand.d.ts +19 -0
  77. package/types/commands/SubCommand.d.ts +8 -0
  78. package/types/commands/ValidateCommand.d.ts +19 -0
  79. package/types/db/ReleaseDB.d.ts +30 -0
@@ -0,0 +1,138 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { existsSync, writeFileSync, unlinkSync, mkdirSync, readFileSync } from 'node:fs'
4
+ import { resolve, dirname } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { spawnSync } from 'node:child_process'
7
+
8
+ import {
9
+ getPackageManagerCommands,
10
+ checkPackageJson,
11
+ checkPreCommits,
12
+ runCommand,
13
+ checkUncommitted,
14
+ tagExists,
15
+ tagRelease,
16
+ detectPackageManager,
17
+ } from './functions.js'
18
+
19
+ const __filename = fileURLToPath(import.meta.url)
20
+ const __dirname = dirname(__filename)
21
+ const tempDir = resolve(__dirname, 'temp')
22
+ const tempPackageJson = resolve(tempDir, 'package.json')
23
+
24
+ describe('functions.js', () => {
25
+ // Setup temporary directory and package.json for testing
26
+ if (!existsSync(tempDir)) mkdirSync(tempDir)
27
+ writeFileSync(tempPackageJson, JSON.stringify({ name: 'test-package' }))
28
+
29
+ it('detectPackageManager returns correct package manager', () => {
30
+ // Create temporary lock files to simulate package managers
31
+ const pnpmLock = resolve(tempDir, 'pnpm-lock.yaml')
32
+ const yarnLock = resolve(tempDir, 'yarn.lock')
33
+
34
+ writeFileSync(pnpmLock, '')
35
+ assert.strictEqual(detectPackageManager(), 'pnpm')
36
+ unlinkSync(pnpmLock)
37
+
38
+ writeFileSync(yarnLock, '')
39
+ assert.strictEqual(detectPackageManager(), 'yarn')
40
+ unlinkSync(yarnLock)
41
+
42
+ // Default case (npm)
43
+ assert.strictEqual(detectPackageManager(), 'npm')
44
+ })
45
+
46
+ it('getPackageManagerCommands returns correct commands', () => {
47
+ const pnpmCommands = getPackageManagerCommands('pnpm')
48
+ assert.ok(pnpmCommands.cacheClean)
49
+ assert.ok(pnpmCommands.install)
50
+ assert.ok(pnpmCommands.update)
51
+ assert.ok(pnpmCommands.prune)
52
+ assert.ok(pnpmCommands.outdated)
53
+ assert.ok(pnpmCommands.build)
54
+ assert.ok(pnpmCommands.lint)
55
+ assert.ok(pnpmCommands.test)
56
+ assert.ok(pnpmCommands.list)
57
+ assert.ok(pnpmCommands.storeStatus)
58
+ })
59
+
60
+ it('checkPackageJson validates required scripts and dependencies', () => {
61
+ const result = checkPackageJson(tempPackageJson, true)
62
+ assert.strictEqual(result, 0)
63
+
64
+ const pkg = JSON.parse(readFileSync(tempPackageJson, 'utf8'))
65
+ assert.ok(pkg.scripts.dev)
66
+ assert.ok(pkg.scripts.build)
67
+ assert.ok(pkg.scripts.preview)
68
+ assert.ok(pkg.scripts.test)
69
+ assert.ok(pkg.scripts.clean)
70
+ assert.ok(pkg.devDependencies.husky)
71
+ })
72
+
73
+ it('checkPreCommits handles missing pre-commit file', () => {
74
+ const result = checkPreCommits(tempPackageJson, true)
75
+ assert.strictEqual(result, 0)
76
+
77
+ const huskyDir = resolve(tempDir, '.husky')
78
+ const preCommitFile = resolve(huskyDir, 'pre-commit')
79
+ assert.ok(existsSync(preCommitFile))
80
+ })
81
+
82
+ it('runCommand executes shell commands', () => {
83
+ // This test will pass if the command runs successfully
84
+ assert.doesNotThrow(() => {
85
+ runCommand('echo "test"', 'Failed to echo')
86
+ })
87
+ })
88
+
89
+ it('checkUncommitted validates git status', () => {
90
+ // This test assumes no uncommitted changes in the test environment
91
+ assert.doesNotThrow(() => {
92
+ checkUncommitted({ devDependencies: {}, dependencies: {} })
93
+ })
94
+ })
95
+
96
+ it('tagExists checks for existing git tags', () => {
97
+ // Assuming no tags exist initially
98
+ assert.strictEqual(tagExists('v0.0.0'), false)
99
+ })
100
+
101
+ it('tagRelease creates and pushes git tags', () => {
102
+ // Mock version for testing
103
+ const version = '0.0.1-test'
104
+ const tag = `v${version}`
105
+
106
+ // Create tag
107
+ spawnSync('git', ['tag', '-a', tag, '-m', `Release version ${version}`], { cwd: tempDir })
108
+
109
+ // Test tag exists
110
+ assert.strictEqual(tagExists(tag), true)
111
+
112
+ // Clean up tag
113
+ spawnSync('git', ['tag', '-d', tag], { cwd: tempDir })
114
+ })
115
+
116
+ it('tagRelease handles existing tags', () => {
117
+ const version = '0.0.2-test'
118
+ const tag = `v${version}`
119
+
120
+ // Create tag first
121
+ spawnSync('git', ['tag', '-a', tag, '-m', `Release version ${version}`], { cwd: tempDir })
122
+
123
+ // Should not create tag again
124
+ const logger = new Logger({ level: 'info' })
125
+ const originalLog = logger.warn
126
+ let warnCalled = false
127
+ logger.warn = () => { warnCalled = true }
128
+
129
+ tagRelease(version)
130
+ assert.ok(warnCalled)
131
+
132
+ // Clean up tag
133
+ spawnSync('git', ['tag', '-d', tag], { cwd: tempDir })
134
+ })
135
+
136
+ // Cleanup
137
+ unlinkSync(tempPackageJson)
138
+ })
File without changes
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+
3
+ import process from "node:process"
4
+ import { Command } from "@nan0web/co"
5
+ import Logger from '@nan0web/log'
6
+ import PublishCommand from "../src/commands/PublishCommand.js"
7
+
8
+ class PrefixedLogger extends Logger {
9
+ static PREFIX = Logger.style("nan0release", { bgColor: "magenta", color: "white" })
10
+ get PREFIX() {
11
+ return /** @type {typeof PrefixedLogger} */ (this.constructor).PREFIX
12
+ }
13
+ info(...args) {
14
+ return super.info(this.PREFIX, ...args)
15
+ }
16
+ error(...args) {
17
+ return super.error(this.PREFIX, ...args)
18
+ }
19
+ warn(...args) {
20
+ return super.warn(this.PREFIX, ...args)
21
+ }
22
+ success(...args) {
23
+ return super.success(this.PREFIX, ...args)
24
+ }
25
+ debug(...args) {
26
+ return super.debug(this.PREFIX, ...args)
27
+ }
28
+ }
29
+
30
+ const logger = new PrefixedLogger(Logger.detectLevel(process.argv))
31
+
32
+ const mainCommand = new Command({
33
+ name: "nan0release",
34
+ help: "Release utilities for nan0web packages",
35
+ logger,
36
+ subcommands: [
37
+ new PublishCommand({ logger }),
38
+ ]
39
+ })
40
+
41
+ async function main(argv = []) {
42
+ const msg = mainCommand.parse(argv.slice(2))
43
+ logger.log(Logger.style(Logger.LOGO, { color: "magenta" }))
44
+
45
+ if (msg.subCommand) {
46
+ const cmd = mainCommand.getCommand(msg.subCommand)
47
+ await cmd.run(msg.subCommandMessage)
48
+ } else {
49
+ logger.info(mainCommand.runHelp())
50
+ }
51
+ }
52
+
53
+ main(process.argv).catch(err => {
54
+ logger.error("❌ Unhandled error:", err.message || err)
55
+ if (err.stack) logger.debug(err.stack)
56
+ process.exit(1)
57
+ })
package/bin/release.js ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+
3
+ import ReleaseCLI from '../src/ReleaseCLI.js'
4
+
5
+ /**
6
+ * Entry point for the release CLI application
7
+ */
8
+ const cli = new ReleaseCLI()
9
+ cli.run(process.argv.slice(2)).catch(error => {
10
+ console.error(error)
11
+ process.exit(1)
12
+ })
@@ -0,0 +1,73 @@
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(tempPackageJson, JSON.stringify({
44
+ name: 'test-package',
45
+ version: '0.0.1-test'
46
+ }))
47
+
48
+ // Should exit with error when missing scripts
49
+ try {
50
+ await main(['--ignore-uncommitted', '--ignore-fail-tests', '--ignore-tag'])
51
+ assert.fail('Should have exited with error')
52
+ } catch (err) {
53
+ assert.ok(err.message.includes('Missing required scripts'))
54
+ }
55
+
56
+ // Should fix when --fix flag is used
57
+ const result = await main(['--ignore-uncommitted', '--ignore-fail-tests', '--ignore-tag', '--fix'])
58
+ assert.ok(result.includes('Release completed'))
59
+
60
+ // Verify package.json was fixed
61
+ const pkg = JSON.parse(readFileSync(tempPackageJson, 'utf8'))
62
+ assert.ok(pkg.scripts.dev)
63
+ assert.ok(pkg.scripts.build)
64
+ assert.ok(pkg.scripts.preview)
65
+ assert.ok(pkg.scripts.test)
66
+ assert.ok(pkg.scripts.clean)
67
+ assert.ok(pkg.devDependencies.husky)
68
+ })
69
+
70
+ // Cleanup
71
+ process.cwd = originalCwd
72
+ await db.dropDocument("package.json")
73
+ })
@@ -0,0 +1 @@
1
+ {"name":"test-package","version":"0.0.1-test"}
@@ -0,0 +1,413 @@
1
+ # 🤙 `system.md` — специфікація проєкту `@nan0web/release`
2
+
3
+ <blockquote style="padding: 2rem 1rem; background-image: linear-gradient(33deg, #00f6, #ff03)">
4
+ <ul>
5
+ <li>Прогрес — це пройдені тести.</li>
6
+ <li>Правда знаходиться в Git.</li>
7
+ <li>Ідентичність підтверджена підписом.</li>
8
+ <li>Дисципліна — це любов.</li>
9
+ </ul>
10
+ </blockquote>
11
+
12
+ Це канонічна специфікація для **`nan0web/release`**:
13
+ Лаконічна, орієнтована на тести, система управління проєктами, створена для Git.
14
+
15
+ Створена для розумних команд, які цінують **правду, чіткість і завершеність**.
16
+
17
+ ---
18
+
19
+ ## 🎯 Ціль
20
+
21
+ Замінити припущення в управлінні проєктами на **виконувану правду**.
22
+
23
+ Визначати кожне завдання, команду та схвалення в коді — а потім дозволити тестам і підписам **підтвердити прогрес**.
24
+
25
+ Без нарад. Без оновлень. Без слів.
26
+
27
+ Лише те, що пройшло, що підписано і що закомічено.
28
+
29
+ ---
30
+
31
+ ## 🧘‍♂️ Філософія
32
+
33
+ 1. **Лаконічність > Складність**
34
+ Мінімальна структура. Максимальна чіткість.
35
+
36
+ 2. **Код > Завдання**
37
+ Завдання живуть в Git. Якщо не протестовано — не виконано.
38
+
39
+ 3. **Прогрес через тести**
40
+ Статус завдання = результат тесту. Ніяких ручних оновлень.
41
+
42
+ 4. **Асинхронна комунікація**
43
+ Чат є архівним. Виживають лише рішення.
44
+
45
+ 5. **Робота офлайн, працюємо з Git**
46
+ Працюємо без інтернету. Випускаємо з інтегритетом.
47
+
48
+ 6. **Ідентичність доведена**
49
+ Ти — той, за кого ти підписуєшся — не той, ким ти себе називаєш.
50
+
51
+ 7. **Команда – це клас**
52
+ Ролі, контакти, очікування — все визначено в коді.
53
+
54
+ 8. **Ти — те, що ти випускаєш**
55
+ Твій результат — це твоя репутація. Git це доводить.
56
+
57
+ > _"Будь правдивим. Роби. Вчись. Повторюй."_
58
+
59
+ ---
60
+
61
+ ## 📁 Структура даних
62
+
63
+ ```
64
+ releases/
65
+ 1/
66
+ 0/
67
+ v1.0.0/ ← Корінь релізу
68
+ release.md ← Нотатки до релізу (завдання, розділи)
69
+ release.js ← Команда, ролі, конфігурація (клас Release)
70
+ release.test.js ← Набір тестів (вхідна точка CI)
71
+ test/
72
+ {username}/ ← Опціональний простір
73
+ tasks.test.js ← Користувацькі тести
74
+ chat/ ← Обговорення релізу
75
+ 2025/08/18/
76
+ 162000420.{username}.md ← Повідомлення (часова мітка + користувач)
77
+ task/
78
+ ux.logo/ ← Завдання за slug
79
+ config.json ← { assignee, priority, dependsOn }
80
+ approved/
81
+ ceo.json ← Запис схвалення (GPG-підписаний)
82
+ chat/
83
+ 2025/08/18/
84
+ 162000420.{username}.md ← Повідомлення по конкретному завданню
85
+ assets/ ← Дизайни, документи, результати
86
+ retro.md ← Фінальне спостереження (запечатаний)
87
+ .cache/ ← gitignored: стан читання, сесія
88
+
89
+ .recover/{username}/ ← Оголошення втрати ключа
90
+ .well-known/keys.json ← Реєстр публічних ключів
91
+ .nanorc.js ← Конфігурація проєкту (необов'язкова)
92
+ ```
93
+
94
+ > ✅ Усі дані під контролем версій, перевіряються, повторювані.
95
+
96
+ ---
97
+
98
+ ## 🔄 Робочий процес
99
+
100
+ ```mermaid
101
+ flowchart TD
102
+ A[Початок релізу] --> B[Визначення команди в release.js]
103
+ B --> C[Написання release.md з завданнями]
104
+ C --> D[Написання release.test.js або test/@user/tasks.test.js]
105
+ D --> E[Commit → CI запускає тести]
106
+ E --> F{Тести пройшли?}
107
+ F -->|Ні| G[Блокування CI, невиконаний реліз]
108
+ F -->|Так| H[Завдання виконане]
109
+ H --> I[Оновити поступ, ETA]
110
+ I --> J[Чат: Обговорення заклинених завдань асинхронно]
111
+ J --> K[Схвалення: Файл в approved/ceo.json]
112
+ K --> L[Усі тести пройшли → Тег v1.0.0]
113
+ L --> M[Запечатати реліз, написати retro.md]
114
+ ```
115
+
116
+ Основні правила:
117
+
118
+ - Немає завдання "виконане" доки тесту не пройшов
119
+ - Немає випуску без тегу
120
+ - Немає схвалення без `.json` файлу і підтвердженням ідентичності
121
+
122
+ ---
123
+
124
+ ## 🔐 Ідентичність та GPG Підпис
125
+
126
+ ### 🔑 Чому GPG?
127
+
128
+ GPG дає **криптографічне підтвердження** того, хто зробив коміт чи схвалення.
129
+
130
+ Це не опціонально.
131
+ Це **основа довіри**.
132
+
133
+ ### ✅ Налаштування GPG Ключа (в кожному repo)
134
+
135
+ 1. **Генерація GPG ключа**
136
+
137
+ ```bash
138
+ gpg --full-generate-key
139
+ ```
140
+
141
+ - Type: `1` (RSA and RSA)
142
+ - Size: `4096`
143
+ - Expires: `0` (ніколи)
144
+ - Name: `UserName`
145
+ - Email: `email@example.com`
146
+
147
+ 2. **Перегляд ключів**
148
+
149
+ ```bash
150
+ gpg --list-secret-keys --keyid-format=long
151
+ ```
152
+
153
+ Отримаємо:
154
+
155
+ ```
156
+ sec rsa4096/ABC123456789 2025-01-01 [SC]
157
+ Key fingerprint = 123A 456B 789C ...
158
+ uid [ultimate] UserName <email@example.com>
159
+ ```
160
+
161
+ 3. **Налаштування Git (в кожному repo)**
162
+
163
+ ```bash
164
+ cd /path/to/your/repo
165
+
166
+ git config user.name "UserName"
167
+ git config user.email "email@example.com"
168
+ git config user.signingkey ABC123456789
169
+ git config commit.gpgsign true
170
+ ```
171
+
172
+ 4. **Тест коміту з підписом**
173
+
174
+ ```bash
175
+ git commit -S -m "First signed commit"
176
+ ```
177
+
178
+ > Вас попросять passphrase.
179
+
180
+ 5. **Перевірка підпису**
181
+ ```bash
182
+ git log --show-signature -1
183
+ ```
184
+ Перевірити:
185
+ ```
186
+ gpg: Good signature from "UserName <email@example.com>"
187
+ ```
188
+
189
+ > ❗ Ніколи не використовуйте `--global` для `user.signingkey` або `commit.gpgsign`
190
+ > Ідентичність залежить від **контексту** — одне repo, одна роль, один ключ.
191
+
192
+ ---
193
+
194
+ ## 🔄 Втрата ключа: Відновлення ідентичності
195
+
196
+ **Ви не можете відновити втрачений приватний ключ.**
197
+ Але ви можете довести перехід.
198
+
199
+ ### 3-крокова процедура відновлення
200
+
201
+ #### 1. **Написати декларацію переходу (якщо можливо)**
202
+
203
+ Якщо ще маєте доступ до старого ключа, підпишіть:
204
+
205
+ ```text
206
+ Я, UserName <email@example.com>, володів GPG ключем: ABC123456789
207
+ Станом на 2025-08-20, я втратив доступ до нього.
208
+ Мій новий ключ: DEF987654321
209
+ Підписано: [GPG clearsign цього повідомлення зі старим ключем]
210
+ ```
211
+
212
+ Записується як:
213
+
214
+ ```
215
+ .recover/username/rotation-2025-08-20.md
216
+ ```
217
+
218
+ #### 2. **Оновлення ідентичності в проєкті**
219
+
220
+ ```js
221
+ // releases/1/0/v1.0.0.js
222
+ class Members {
223
+ static UserName = new Person({
224
+ name: "UserName",
225
+ contacts: [new Contact("https://example.com")],
226
+ gpgKey: "DEF987654321", // новий ключ
227
+ revokedKey: "ABC123456789", // старий, втрачений ключ
228
+ });
229
+ }
230
+ ```
231
+
232
+ Або в `.well-known/keys.json`:
233
+
234
+ ```json
235
+ {
236
+ "user": "ya.raslove",
237
+ "currentKey": "DEF987654321",
238
+ "revokedKeys": [
239
+ {
240
+ "key": "ABC123456789",
241
+ "revokedAt": "2025-08-20",
242
+ "proof": "/recovery/ya.raslove/rotation-2025-08-20.md"
243
+ }
244
+ ]
245
+ }
246
+ ```
247
+
248
+ #### 3. **CI валідує перехід**
249
+
250
+ На першому коміті з новим ключем:
251
+
252
+ - CI перевіряє валідність ключа
253
+ - Шукає доказ переходу
254
+ - Блокує, якщо ключ непідписаний або без пояснення
255
+
256
+ ---
257
+
258
+ ## 🛠 CLI Команди
259
+
260
+ | Команда | Опис |
261
+ | -------------------------------------------------- | -------------------------- |
262
+ | `nan0release` | Показ статуса релізу |
263
+ | `nan0release v1.0.0` | Показ конкретного релізу |
264
+ | `nan0release ls` | Список релізів |
265
+ | `nan0release todo` | Список моїх завдань |
266
+ | `nan0release todo > todo.md` | Експорт завдань |
267
+ | `nan0release chat write "msg"` | Написати у загальний чат |
268
+ | `nan0release chat write ux.logo "help needed"` | Написати у конкретний чат |
269
+ | `nan0release validate` | Перевірити структуру, тести, GPG |
270
+ | `nan0release serve` | Запустити локальний сервер |
271
+ | `nan0release host --webui` | Хостити онлайн з автентифікацією |
272
+ | `nan0release init v1.0.0` | Створити новий реліз |
273
+ | `nan0release seal v1.0.0` | Зробити реліз незмінним |
274
+
275
+ > Усі команди працюють з CI. Вивід через `--json`, `--quiet`.
276
+
277
+ ---
278
+
279
+ ## 🧪 Життєвий цикл завдання
280
+
281
+ | Стан | Як реалізовано |
282
+ | ------------------ | ---------------------------------------- |
283
+ | `todo` | Тест визначено через `it.todo()` |
284
+ | `in-progress` | Тест написано, але не пройшов |
285
+ | `done` | Тест пройдено, жодних ручних оновлень |
286
+ | `blocked` | Залежить від іншого завдання/схвалення |
287
+ | `skipped` | `it.skip()` — рідко використовується |
288
+
289
+ Статус є **виводимий**, ніколи не написаний.
290
+
291
+ ---
292
+
293
+ ## ✅ Система схвалення
294
+
295
+ Схвалення — це **GPG-підписані JSON файли**:
296
+
297
+ ```
298
+ task/ux.logo/approved/ceo.json
299
+ ```
300
+
301
+ ```json
302
+ {
303
+ "by": "UserName <email@example.com>",
304
+ "sign": "gpg:DEF987654321",
305
+ "time": "2025-08-20T12:00:00Z"
306
+ }
307
+ ```
308
+
309
+ Тестування:
310
+
311
+ ```js
312
+ it("CEO approved logo", () => {
313
+ expect(fileExists("task/ux.logo/approved/ceo.json")).toBe(true);
314
+ });
315
+ ```
316
+
317
+ Дозволяється кілька схвалень:
318
+
319
+ - `ceo.json`, `cfo.json`, `legal.json`
320
+
321
+ CI перевіряє наявність файлу — **ви доводите це**.
322
+
323
+ ---
324
+
325
+ ## 🔒 Валідація на коміт
326
+
327
+ Git hook (через `husky` або `simple-git-hooks`) запускає:
328
+
329
+ ```bash
330
+ nan0release validate
331
+ ```
332
+
333
+ Помилка, якщо:
334
+
335
+ - Завдання згадано, але немає тестів
336
+ - Виконавець не існує в `release.js`
337
+ - Відсутній GPG підпис на важливому коміті
338
+ - Ключ — не поточний
339
+ - Спроба редагування пройденого завдання
340
+
341
+ Блокує `git commit` до виправлення.
342
+
343
+ > Правда підтверджена в джерелах.
344
+
345
+ ---
346
+
347
+ ## 🌐 Хостинг та інтерфейс
348
+
349
+ ### `nan0release serve`
350
+
351
+ - Запускає локальний сервер
352
+ - Подає WebUI (`@nan0web/release-ui-lit`)
353
+ - Індекс у пам’яті для пошуку
354
+ - Без автентифікації (лише локально)
355
+
356
+ ### `nan0release host --webui`
357
+
358
+ - Хостується онлайн
359
+ - Вимагає GPG автентифікації (через challenge-response)
360
+ - Синхронує `.cache` з Redis (необов’язково)
361
+ - WebSockets для миттєвих оновлень
362
+
363
+ > UI = ті ж дані, що й CLI. Лише візуалізація.
364
+
365
+ ---
366
+
367
+ ## 🧩 `.nan0rc.js` (Необов’язково)
368
+
369
+ ```js
370
+ // .nanorc.js
371
+ export default {
372
+ git: {
373
+ user: "UserName",
374
+ email: "email@example.com",
375
+ signingKey: "ABC123456789",
376
+ gpgsign: true,
377
+ },
378
+ hooks: {
379
+ "on-release-done": "git tag v1.0.0",
380
+ "on-task-fail": "echo @pm: task failure",
381
+ },
382
+ };
383
+ ```
384
+
385
+ Використовується:
386
+
387
+ ```bash
388
+ nan0release init --trust-config
389
+ ```
390
+
391
+ ---
392
+
393
+ ## Формат нотаток релізу
394
+
395
+ - `h1` - версія і дата релізу `# vX.Y.Z - YYYY-MM-DD`
396
+ - `h2` - розділ релізу
397
+ - `h3` - заголовок завдання з його описом
398
+
399
+ ---
400
+
401
+ ## 📜 Остаточна правда
402
+
403
+ > **Проєкт не той, який ви сказали.**
404
+ > **Він той, який тести пройшли.**
405
+ > **Він той, який коміти що демонструють.**
406
+ > **Він той, який підписи підтверджують.**
407
+
408
+ `nan0web/release` — це не інструмент.
409
+ Це **система дисципліни** для тих, хто шукає майстерності.
410
+
411
+ Починайте з малого.
412
+ Будьте правдивими.
413
+ Робіть релізи.