@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,144 @@
1
+ import process from "node:process"
2
+ import { Command, CommandMessage } from "@nan0web/co"
3
+ import FS from "@nan0web/db-fs"
4
+ import { runSpawn } from '@nan0web/test'
5
+
6
+ export class PublishCommandOptions {
7
+ /** @type {string} */
8
+ tag
9
+ constructor(input = {}) {
10
+ const {
11
+ tag = ""
12
+ } = input
13
+ this.tag = String(tag)
14
+ }
15
+ /**
16
+ * @param {any} input
17
+ * @returns {PublishCommandOptions}
18
+ */
19
+ static from(input) {
20
+ if (input instanceof PublishCommandOptions) return input
21
+ return new PublishCommandOptions(input)
22
+ }
23
+ }
24
+
25
+ /**
26
+ * @extends {CommandMessage}
27
+ */
28
+ export class PublishCommandMessage extends CommandMessage {
29
+ /** @returns {PublishCommandOptions} */
30
+ get opts() {
31
+ const opts = super.opts
32
+ return PublishCommandOptions.from(opts)
33
+ }
34
+ set opts(value) {
35
+ super.opts = value
36
+ }
37
+ constructor(input = {}) {
38
+ super(input)
39
+ }
40
+ }
41
+
42
+ /**
43
+ * @extends {Command}
44
+ */
45
+ export default class PublishCommand extends Command {
46
+ static Message = PublishCommandMessage
47
+
48
+ constructor(input = {}) {
49
+ super({
50
+ name: "publish",
51
+ help: "Publish package to NPM and create Git tag",
52
+ ...input
53
+ })
54
+ this.addArgument("*", String, "", "Category of new version: major, minor, patch, or empty.")
55
+ }
56
+
57
+ /**
58
+ * @docs
59
+ * # `nan0release publish`
60
+ *
61
+ * Publishes current package to NPM and creates Git tags.
62
+ *
63
+ * Steps performed:
64
+ * - Verifies no uncommitted changes
65
+ * - Pulls latest changes
66
+ * - Runs build and test scripts
67
+ * - Creates Git tag if not exists
68
+ * - Publishes to NPM registry
69
+ * - Pushes changes and tags
70
+ *
71
+ * ```bash
72
+ * nan0release publish
73
+ * ```
74
+ * @param {PublishCommandMessage} msg
75
+ */
76
+ async run(msg) {
77
+ const fs = new FS()
78
+ await fs.connect()
79
+
80
+ const category = ["major", "minor", "patch"].find(c => msg.args.includes(c))
81
+ if (category) {
82
+ await this.#run('npm', ["version", category], "Unable to change version", fs)
83
+ }
84
+
85
+ const pkg = await fs.loadDocument('package.json', {})
86
+ const tag = `v${pkg.version}`
87
+
88
+ await this.#run('git', ["diff", "--quiet"], 'Uncommitted changes found. Commit or stash first.', fs)
89
+
90
+ this.logger.info(`🛜 nan0release: publishing @${pkg.name}@${pkg.version}`)
91
+
92
+ await this.#run('git', ["pull"], 'Failed to pull latest changes', fs)
93
+ await this.#run('npm', ["run", "clean"], 'Clean failed', fs)
94
+ await this.#run('npm', ["run", "build"], 'Build failed', fs)
95
+ await this.#run('npm', ["test"], 'Tests failed', fs)
96
+
97
+ const tagsResult = await this.#run('git', ["tag"], "Failed to get tags", fs)
98
+ const tags = tagsResult.text.split('\n').filter(Boolean)
99
+
100
+ if (!tags.includes(tag)) {
101
+ await this.#run('git', ['tag', '-a', tag, '-m', `Release ${pkg.version}`], 'Tag creation failed', fs)
102
+ } else {
103
+ this.logger.warn(`Tag ${tag} already exists`)
104
+ }
105
+
106
+ await this.#run('npm', ['publish', '--access', 'public'], 'Publish to npm failed', fs)
107
+
108
+ await this.#run('git', ['push', 'origin', 'main', '--no-verify'], 'Git push failed', fs)
109
+ await this.#run('git', ['push', 'origin', '--tags', '--no-verify'], 'Tag push failed', fs)
110
+
111
+ this.logger.success(`@${pkg.name}@${pkg.version} published. Zero becomes script of a life 🌱`)
112
+ }
113
+
114
+ /**
115
+ * @param {string} cmd
116
+ * @param {string[]} args
117
+ * @param {string} errorMsg
118
+ * @param {FS} fs
119
+ * @param {number[]} [okCodes=[0]]
120
+ */
121
+ async #run(cmd, args, errorMsg, fs, okCodes = [0]) {
122
+ let content = ""
123
+ const onData = (chunk) => {
124
+ content += String(chunk)
125
+ const rows = content.split("\n").filter(Boolean)
126
+ const recent = rows.slice(-1)
127
+ if (recent.length) {
128
+ this.logger.cursorUp(1, true)
129
+ this.logger.info(recent[0])
130
+ }
131
+ }
132
+
133
+ let cwd = fs.absolute()
134
+ if ("/" === cwd && "." === fs.cwd) cwd = fs.cwd
135
+ const response = await runSpawn(cmd, args, { onData, cwd })
136
+
137
+ if (!okCodes.includes(response.code)) {
138
+ this.logger.error(errorMsg)
139
+ process.exit(1)
140
+ }
141
+
142
+ return response
143
+ }
144
+ }
@@ -0,0 +1,51 @@
1
+ import SubCommand from "./SubCommand.js"
2
+ import { mkdir, writeFile } from "node:fs/promises"
3
+ import { join } from "node:path"
4
+ import Release from "../../src/Release.js"
5
+
6
+ /**
7
+ * SealCommand – finalises a release by writing a `retro.md` file
8
+ * and making the release directory read‑only. This is a *light* implementation
9
+ * that merely creates a placeholder retro file; a real implementation would
10
+ * also set file system permissions and maybe create a Git tag.
11
+ *
12
+ * Usage:
13
+ * release seal [--message <text>]
14
+ *
15
+ * Options:
16
+ * --message – optional retro‑reflection text
17
+ */
18
+ export default class SealCommand extends SubCommand {
19
+ constructor(options = {}) {
20
+ super(options)
21
+ this.addOption("message", String, "", "Retro‑reflection text")
22
+ }
23
+
24
+ /**
25
+ * Seal the current release.
26
+ *
27
+ * @param {Object} ctx – command context
28
+ */
29
+ async run(ctx) {
30
+ if (!this.db) {
31
+ this.logger.error("No DB – cannot seal release")
32
+ return
33
+ }
34
+
35
+ const retroPath = join(this.db.cwd, "retro.md")
36
+ const content = `# Retro – ${new Date().toISOString().split("T")[0]}
37
+
38
+ ${ctx.opts.message || "*No retro‑reflection provided.*"}\n`
39
+
40
+ await writeFile(retroPath, content, "utf8")
41
+ this.logger.success(`retro.md written to ${retroPath}`)
42
+
43
+ // make the folder read‑only (best‑effort – ignore errors on non‑POSIX FS)
44
+ try {
45
+ await mkdir(this.db.cwd, { mode: 0o444 })
46
+ this.logger.info("Release directory set to read‑only")
47
+ } catch (_) {
48
+ // not critical – just a hint
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,53 @@
1
+ import SubCommand from "./SubCommand.js"
2
+ import { createServer } from "node:http"
3
+ import { readFile } from "node:fs/promises"
4
+ import { join } from "node:path"
5
+
6
+ /**
7
+ * ServeCommand – runs a tiny static file server that serves all files
8
+ * inside the current release folder. It is useful for quick local
9
+ * inspection of assets, markdown, and generated test files.
10
+ *
11
+ * Usage:
12
+ * release serve [--port <number>]
13
+ */
14
+ export default class ServeCommand extends SubCommand {
15
+ constructor(options = {}) {
16
+ super(options)
17
+ this.addOption("port", Number, 8080, "Port for static server")
18
+ }
19
+
20
+ /**
21
+ * Start the static server.
22
+ *
23
+ * @param {Object} ctx – command context
24
+ */
25
+ async run(ctx) {
26
+ if (!this.db) {
27
+ this.logger.error("No DB – cannot serve files")
28
+ return
29
+ }
30
+
31
+ const root = this.db.cwd
32
+ const port = ctx.opts.port
33
+
34
+ const server = createServer(async (req, res) => {
35
+ const safePath = req.url?.split("?")[0] || "/"
36
+ const filePath = join(root, safePath)
37
+
38
+ try {
39
+ const data = await readFile(filePath)
40
+ res.writeHead(200)
41
+ res.end(data)
42
+ } catch (_) {
43
+ res.writeHead(404)
44
+ res.end("Not found")
45
+ }
46
+ })
47
+
48
+ server.listen(port, () => {
49
+ this.logger.success(`Static server listening on http://localhost:${port}`)
50
+ this.logger.info(`Serving ${root}`)
51
+ })
52
+ }
53
+ }
@@ -0,0 +1,58 @@
1
+ import SubCommand from "./SubCommand.js"
2
+ import { readFile } from "node:fs/promises"
3
+ import { join } from "node:path"
4
+
5
+ /**
6
+ * ShowCommand – prints basic information about the current release
7
+ * (version, creation date, and a short excerpt of the markdown notes).
8
+ *
9
+ * Usage:
10
+ * release show
11
+ *
12
+ * Options:
13
+ * --full – dump the whole release.md file
14
+ */
15
+ export default class ShowCommand extends SubCommand {
16
+ constructor(options = {}) {
17
+ super(options)
18
+ this.addOption("full", Boolean, false, "Show complete release.md")
19
+ }
20
+
21
+ /**
22
+ * Render the release information.
23
+ *
24
+ * @param {Object} ctx – command context
25
+ */
26
+ async run(ctx) {
27
+ if (!this.db) {
28
+ this.logger.error("No DB – cannot locate current release")
29
+ return
30
+ }
31
+
32
+ // `cwd` points to the current release directory (see CLI)
33
+ const releaseJsPath = join(this.db.cwd, "release.js")
34
+ const releaseMdPath = join(this.db.cwd, "release.md")
35
+
36
+ try {
37
+ const { default: release } = await import(releaseJsPath)
38
+ this.logger.info(`Version : ${release.version}`)
39
+ this.logger.info(`Created at : ${release.createdAt.toISOString()}`)
40
+ this.logger.info(`Path : ${this.db.cwd}`)
41
+ } catch (/** @type {any} */ e) {
42
+ this.logger.warn(`Unable to import release.js – ${e.message}`)
43
+ }
44
+
45
+ try {
46
+ const md = await readFile(releaseMdPath, "utf8")
47
+ if (ctx.opts.full) {
48
+ this.logger.info("\n=== release.md ===\n")
49
+ this.logger.info(md)
50
+ } else {
51
+ const firstLine = md.split("\n")[0]
52
+ this.logger.info(`Release note : ${firstLine}`)
53
+ }
54
+ } catch (/** @type {any} */ e) {
55
+ this.logger.warn(`release.md not found – ${e.message}`)
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,21 @@
1
+ import { Command } from '@nan0web/co'
2
+ import ReleaseDB from '../db/ReleaseDB.js'
3
+ import Logger from '@nan0web/log'
4
+
5
+ class SubCommand extends Command {
6
+ /** @type {Logger} */
7
+ logger
8
+ /** @type {ReleaseDB?} */
9
+ db
10
+ constructor(options = {}) {
11
+ const {
12
+ logger = new Logger(),
13
+ db,
14
+ } = options
15
+ super(options)
16
+ this.logger = Logger.from(logger)
17
+ this.db = db ? ReleaseDB.from(db) : null
18
+ }
19
+ }
20
+
21
+ export default SubCommand
@@ -0,0 +1,66 @@
1
+ import SubCommand from "./SubCommand.js"
2
+ import { readFile } from "node:fs/promises"
3
+ import { join } from "node:path"
4
+ import Release from "../../src/Release.js"
5
+
6
+ /**
7
+ * ValidateCommand – runs the {@link Release.validate} method for the
8
+ * currently checked‑out release and prints a short summary.
9
+ *
10
+ * Usage:
11
+ * release validate
12
+ *
13
+ * Options:
14
+ * --ignore-fail – do not treat failed tasks as a validation error
15
+ */
16
+ export default class ValidateCommand extends SubCommand {
17
+ constructor(options = {}) {
18
+ super(options)
19
+ this.addOption("ignoreFailTests", Boolean, false, "Ignore failed tests")
20
+ }
21
+
22
+ /**
23
+ * Execute validation.
24
+ *
25
+ * @param {Object} ctx – command context
26
+ */
27
+ async run(ctx) {
28
+ if (!this.db) {
29
+ this.logger.error("No DB – cannot locate current release")
30
+ return
31
+ }
32
+
33
+ const releaseJsPath = join(this.db.cwd, "release.js")
34
+ const releaseMdPath = join(this.db.cwd, "release.md")
35
+
36
+ let releaseInst
37
+ try {
38
+ const { default: rel } = await import(releaseJsPath)
39
+ releaseInst = rel
40
+ } catch (/** @type {any} */ e) {
41
+ this.logger.error(`Failed to import release.js – ${e.message}`)
42
+ return
43
+ }
44
+
45
+ // Attach a mock document – the real implementation would parse markdown
46
+ try {
47
+ const md = await readFile(releaseMdPath, "utf8")
48
+ // Very naïve parsing – just store the raw markdown
49
+ releaseInst.document = md
50
+ } catch (_) {
51
+ // ignore – document is optional for validation
52
+ }
53
+
54
+ const result = await releaseInst.validate()
55
+ const { passed, failed, pending } = result
56
+
57
+ if (passed.length) this.logger.success(`✅ Passed: ${passed.join(", ")}`)
58
+ if (failed.length) this.logger.error(`❌ Failed: ${failed.map(f => f.taskId).join(", ")}`)
59
+ if (pending.length) this.logger.warn(`● Pending: ${pending.map(p => p.taskId).join(", ")}`)
60
+
61
+ if (!ctx.opts.ignoreFailTests && failed.length) {
62
+ this.logger.error(`Validation failed – ${failed.length} tasks did not pass`)
63
+ process.exit(1)
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,54 @@
1
+ import DBFS from "@nan0web/db-fs"
2
+
3
+ /**
4
+ * ReleaseDB – thin wrapper around DBFS that adds a convenient `releases`
5
+ * getter returning all release directories discovered under the root.
6
+ *
7
+ * The underlying DBFS stores a `Map` called `data` where each key is a
8
+ * relative file path (e.g. "1/0/v1.0.0/release.js").
9
+ */
10
+ class ReleaseDB extends DBFS {
11
+ /**
12
+ * @param {string} uri
13
+ * @returns {ReleaseDB}
14
+ */
15
+ extract(uri) {
16
+ return new ReleaseDB(super.extract(uri))
17
+ }
18
+ /**
19
+ * @param {string} version
20
+ * @returns {ReleaseDB}
21
+ */
22
+ extractVersion(version) {
23
+ const found = version.match(/^v(\d+)\.(\d+)\.(\d+)$/)
24
+ if (!found) {
25
+ throw new Error("Incorrect version")
26
+ }
27
+ const arr = version.slice(1).split(".")
28
+ const path = [arr[0], arr[1], version, ""].join("/")
29
+ return ReleaseDB.from(this.extract(path))
30
+ }
31
+ /**
32
+ * @returns {Array<string>} List of discovered release root paths
33
+ */
34
+ get releases() {
35
+ const paths = new Set()
36
+ for (const key of this.data.keys()) {
37
+ // Expected structure: major/minor/vX.Y.Z/…
38
+ const parts = key.split("/")
39
+ if (parts.length > 2 && /^\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) {
40
+ paths.add(`${parts[0]}/${parts[1]}/${parts[2]}`)
41
+ }
42
+ }
43
+ return Array.from(paths)
44
+ }
45
+ /**
46
+ * @param {object} input
47
+ * @returns {ReleaseDB}
48
+ */
49
+ static from(input) {
50
+ if (input instanceof ReleaseDB) return input
51
+ return new ReleaseDB(input)
52
+ }
53
+ }
54
+ export default ReleaseDB