@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,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
|