@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.
- package/.datasets/README.dataset.jsonl +19 -0
- package/.editorconfig +20 -0
- package/CHANGELOG.md +6 -0
- package/CONTRIBUTING.md +42 -0
- package/LICENSE +15 -0
- package/README.md +284 -0
- package/bin/commands/InitCommand.js +10 -0
- package/bin/lib/functions.js +240 -0
- package/bin/lib/functions.test.js +138 -0
- package/bin/lib/temp/pnpm-lock.yaml +0 -0
- package/bin/nan0release.js +57 -0
- package/bin/release.js +12 -0
- package/bin/release.test.js +73 -0
- package/bin/temp/package.json +1 -0
- package/docs/system.uk.md +413 -0
- package/docs/uk/README.md +284 -0
- package/legacy/PrepareCommand.js +154 -0
- package/legacy/PrepareCommand.test.js +189 -0
- package/legacy/changelog.js +446 -0
- package/package.json +43 -0
- package/releases/1/0/v1.0.0/models/Person.js +26 -0
- package/releases/1/0/v1.0.0/models/Person.test.js +44 -0
- package/releases/1/0/v1.0.0/models/company.js +11 -0
- package/releases/1/0/v1.0.0/models/members.js +14 -0
- package/releases/1/0/v1.0.0/models/members.test.js +21 -0
- package/releases/1/0/v1.0.0/models/teams/DevTeam.js +6 -0
- package/releases/1/0/v1.0.0/models/teams/DevTeam.test.js +15 -0
- package/releases/1/0/v1.0.0/models/teams/index.js +5 -0
- package/releases/1/0/v1.0.0/release.js +12 -0
- package/releases/1/0/v1.0.0/release.md +121 -0
- package/releases/1/0/v1.0.0/release.test.js +36 -0
- package/releases/1/0/v1.0.0/test/YaRaSLove/git-native.test.js +14 -0
- package/releases/current.js +3 -0
- package/releases/index.js +11 -0
- package/src/README.md.js +468 -0
- package/src/Release/Document.js +110 -0
- package/src/Release/MarkdownToTest.js +137 -0
- package/src/Release/MarkdownToTest.test.js +60 -0
- package/src/Release/Person.js +29 -0
- package/src/Release.js +132 -0
- package/src/ReleaseCLI.js +122 -0
- package/src/Team.js +35 -0
- package/src/architecture/ProjectManagementAsCode.js +264 -0
- package/src/architecture/ProjectManagementAsCode.test.js +128 -0
- package/src/co/AppCommandMessage.js +22 -0
- package/src/co/AppCommandOptions.js +27 -0
- package/src/commands/ChatCommand.js +56 -0
- package/src/commands/HostCommand.js +63 -0
- package/src/commands/InitCommand.js +65 -0
- package/src/commands/ListCommand.js +56 -0
- package/src/commands/PublishCommand.js +144 -0
- package/src/commands/SealCommand.js +51 -0
- package/src/commands/ServeCommand.js +53 -0
- package/src/commands/ShowCommand.js +58 -0
- package/src/commands/SubCommand.js +21 -0
- package/src/commands/ValidateCommand.js +66 -0
- package/src/db/ReleaseDB.js +54 -0
- package/system.md +571 -0
- package/tsconfig.json +23 -0
- package/types/Release/Document.d.ts +31 -0
- package/types/Release/MarkdownToTest.d.ts +47 -0
- package/types/Release/Person.d.ts +17 -0
- package/types/Release.d.ts +89 -0
- package/types/ReleaseCLI.d.ts +33 -0
- package/types/Team.d.ts +27 -0
- package/types/architecture/ProjectManagementAsCode.d.ts +159 -0
- package/types/co/AppCommandMessage.d.ts +12 -0
- package/types/co/AppCommandOptions.d.ts +23 -0
- package/types/commands/ChatCommand.d.ts +20 -0
- package/types/commands/HostCommand.d.ts +16 -0
- package/types/commands/InitCommand.d.ts +20 -0
- package/types/commands/ListCommand.d.ts +21 -0
- package/types/commands/PublishCommand.d.ts +49 -0
- package/types/commands/SealCommand.d.ts +21 -0
- package/types/commands/ServeCommand.d.ts +17 -0
- package/types/commands/ShowCommand.d.ts +19 -0
- package/types/commands/SubCommand.d.ts +8 -0
- package/types/commands/ValidateCommand.d.ts +19 -0
- 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
|
+
Робіть релізи.
|