├── .gitignore ├── .yarnrc.yml ├── .prettierrc ├── src ├── model │ ├── utils.ts │ ├── package.ts │ ├── version.ts │ ├── api.ts │ ├── release.ts │ └── git.ts ├── commands.ts ├── cmd │ ├── common.ts │ ├── args.ts │ ├── version.ts │ ├── changelog.ts │ └── release.ts ├── logger.ts ├── context.ts ├── index.ts ├── config.ts ├── api.ts └── parser.ts ├── bin └── monopub ├── tsconfig.json ├── package.json ├── README.md ├── LICENSE └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | export 2 | node_modules 3 | dev 4 | .DS_Store 5 | *.js 6 | *.tsbuildinfo -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.11.0.cjs 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "tabWidth": 4, 4 | "semi": true, 5 | "singleQuote": false, 6 | "arrowParens": "always", 7 | "endOfLine": "lf" 8 | } 9 | -------------------------------------------------------------------------------- /src/model/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Commit } from "./api.js"; 2 | 3 | export const isPublish = (x: Commit) => x.title.toLowerCase() === "publish"; 4 | 5 | export const isBreakingChangeMsg = (x: string) => /^BREAKING CHANGES?:/.test(x); 6 | -------------------------------------------------------------------------------- /src/model/package.ts: -------------------------------------------------------------------------------- 1 | export const pkgShortName = (name: string) => name.split("/")[1]; 2 | 3 | export const pkgPath = (repo: string, root: string, pkg: string) => 4 | `${repo}/${root}/${pkg}`; 5 | 6 | export const pkgJsonPath = (repo: string, root: string, pkg: string) => 7 | `${repo}/${root}/${pkg}/package.json`; 8 | -------------------------------------------------------------------------------- /src/commands.ts: -------------------------------------------------------------------------------- 1 | import type { IObjectOf } from "@thi.ng/api"; 2 | import type { CommandSpec } from "./api.js"; 3 | import { CHANGELOG } from "./cmd/changelog.js"; 4 | import { RELEASE } from "./cmd/release.js"; 5 | import { VERSION } from "./cmd/version.js"; 6 | 7 | export class CommandRegistry { 8 | registry: IObjectOf> = { 9 | changelog: CHANGELOG, 10 | release: RELEASE, 11 | version: VERSION, 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /bin/monopub: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # https://stackoverflow.com/a/246128/294515 4 | SOURCE="${BASH_SOURCE[0]}" 5 | while [ -h "$SOURCE" ]; do 6 | DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )" 7 | SOURCE="$(readlink "$SOURCE")" 8 | [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" 9 | done 10 | DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )" 11 | 12 | /usr/bin/env node "$DIR/../lib/index.js" "$DIR" "$@" 13 | -------------------------------------------------------------------------------- /src/cmd/common.ts: -------------------------------------------------------------------------------- 1 | import type { CommandCtx } from "../api.js"; 2 | import { buildReleaseSpec } from "../model/release.js"; 3 | 4 | export const buildReleaseSpecFromCtx = ({ logger, opts }: CommandCtx) => 5 | buildReleaseSpec( 6 | { 7 | path: opts.repoPath, 8 | url: opts.repoUrl, 9 | scope: opts.scope, 10 | pkgRoot: opts.root, 11 | fileExt: opts.ext, 12 | alias: opts.alias, 13 | all: opts.all, 14 | dump: opts.dumpSpec, 15 | indent: opts.indent, 16 | }, 17 | logger 18 | ); 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "esnext", 5 | "module": "es2020", 6 | "outDir": "./lib", 7 | "removeComments": true, 8 | "strict": true, 9 | "noImplicitAny": true, 10 | "strictNullChecks": true, 11 | "noImplicitThis": true, 12 | "alwaysStrict": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "verbatimModuleSyntax": true, 16 | "moduleResolution": "node", 17 | "esModuleInterop": true, 18 | "skipLibCheck": true, 19 | "forceConsistentCasingInFileNames": true 20 | }, 21 | "include": ["src/**/*.ts", "types/**/*.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /src/model/version.ts: -------------------------------------------------------------------------------- 1 | import type { Commit, VersionType } from "./api.js"; 2 | 3 | export const versionParts = (version: string) => version.split(".").map(Number); 4 | 5 | export const classifyVersion = (version: string): VersionType => { 6 | const [_, minor, patch] = versionParts(version); 7 | return patch === 0 ? (minor === 0 ? "major" : "minor") : "patch"; 8 | }; 9 | 10 | export const classifyVersionBump = ( 11 | id: string, 12 | commits: Commit[] 13 | ): VersionType => { 14 | let minor = false; 15 | for (let c of commits) { 16 | if (!c.pkgs.includes(id)) continue; 17 | if (c.breaking) return "major"; 18 | if (c.type === "feat") minor = true; 19 | } 20 | return minor ? "minor" : "patch"; 21 | }; 22 | 23 | export const versionBump = (version: string, type: VersionType) => { 24 | const [m, n, p] = versionParts(version); 25 | switch (type) { 26 | case "major": 27 | return `${m + 1}.0.0`; 28 | case "minor": 29 | return `${m}.${n + 1}.0`; 30 | case "patch": 31 | default: 32 | return `${m}.${n}.${p + 1}`; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import { ConsoleLogger, LogLevel, type LogEntry } from "@thi.ng/logger"; 2 | import type { AppConfig } from "./config.js"; 3 | 4 | export class Logger extends ConsoleLogger { 5 | constructor(protected config: AppConfig, id: string, level: LogLevel) { 6 | super(id, level); 7 | } 8 | 9 | dry(isDry: boolean, ...args: any[]) { 10 | this.level <= LogLevel.INFO && 11 | this.logEntry([ 12 | LogLevel.INFO, 13 | this.id, 14 | Date.now(), 15 | ...(isDry ? ["[dryrun]", ...args] : args), 16 | ]); 17 | } 18 | 19 | important(...args: any[]) { 20 | this.level <= LogLevel.NONE && 21 | this.logEntry([LogLevel.INFO, this.id, Date.now(), ...args]); 22 | } 23 | 24 | logEntry([level, id, _, ...args]: LogEntry) { 25 | let msg = `[${LogLevel[level]}] ${id}: ${args.join(" ")}\n`; 26 | const theme = this.config.theme; 27 | switch (level) { 28 | case LogLevel.INFO: 29 | msg = theme.lightYellow(msg); 30 | break; 31 | case LogLevel.WARN: 32 | msg = theme.lightRed(msg); 33 | break; 34 | case LogLevel.SEVERE: 35 | msg = theme.red(msg); 36 | break; 37 | default: 38 | } 39 | process.stderr.write(msg); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "@thi.ng/errors"; 2 | import { seconds } from "@thi.ng/strings"; 3 | import { 4 | REQUIRED, 5 | type CLIOpts, 6 | type CommandCtx, 7 | type CommandSpec, 8 | } from "./api.js"; 9 | import type { AppConfig } from "./config.js"; 10 | import type { Logger } from "./logger.js"; 11 | import type { ArgParser } from "./parser.js"; 12 | 13 | export class AppContext implements CommandCtx { 14 | cmd!: CommandSpec; 15 | opts!: T; 16 | rest!: string[]; 17 | 18 | constructor( 19 | public config: AppConfig, 20 | public logger: Logger, 21 | public args: ArgParser 22 | ) {} 23 | 24 | async start() { 25 | const ctx = this.args.ctx!; 26 | this.cmd = ctx.cmd!; 27 | this.rest = ctx.rest!; 28 | this.opts = ctx.opts!; 29 | this.ensureParam("--repo-path", this.opts.repoPath); 30 | this.ensureParam("--repo-url", this.opts.repoUrl); 31 | this.ensureParam("--scope", this.opts.scope); 32 | const t0 = Date.now(); 33 | await this.cmd.fn(this); 34 | this.logger.important( 35 | `completed in ${seconds((Date.now() - t0) / 1000)}` 36 | ); 37 | return true; 38 | } 39 | 40 | ensureParam(id: string, val: string) { 41 | assert(val !== REQUIRED, `missing required ${id}`); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { LogLevel, type LogLevelName } from "@thi.ng/logger"; 2 | import { defSystem } from "@thi.ng/system"; 3 | import { config } from "dotenv"; 4 | import { APP_NAME } from "./api.js"; 5 | import { CommandRegistry } from "./commands.js"; 6 | import { AppConfig } from "./config.js"; 7 | import { AppContext } from "./context.js"; 8 | import { Logger } from "./logger.js"; 9 | import { ArgParser } from "./parser.js"; 10 | 11 | interface App { 12 | args: ArgParser; 13 | commands: CommandRegistry; 14 | config: AppConfig; 15 | ctx: AppContext; 16 | logger: Logger; 17 | } 18 | 19 | config(); 20 | 21 | (async () => { 22 | // main app 23 | const APP = defSystem({ 24 | config: { 25 | factory: async () => new AppConfig(), 26 | }, 27 | logger: { 28 | factory: async ({ config }) => 29 | new Logger( 30 | config, 31 | APP_NAME, 32 | LogLevel[config.logLevel] 33 | ), 34 | deps: ["config"], 35 | }, 36 | commands: { 37 | factory: async () => new CommandRegistry(), 38 | }, 39 | args: { 40 | factory: async ({ logger, config, commands }) => 41 | new ArgParser(logger, config, commands), 42 | deps: ["config", "logger", "commands"], 43 | }, 44 | ctx: { 45 | factory: async ({ logger, config, args }) => 46 | new AppContext(config, logger, args), 47 | deps: ["config", "logger", "args"], 48 | }, 49 | }); 50 | 51 | try { 52 | await APP.start(); 53 | } catch (e) { 54 | APP.components.logger.severe((e).message); 55 | console.log(e); 56 | } 57 | })(); 58 | -------------------------------------------------------------------------------- /src/cmd/args.ts: -------------------------------------------------------------------------------- 1 | import { coerceInt, flag, int, oneOfMulti, string } from "@thi.ng/args"; 2 | import { illegalArgs } from "@thi.ng/errors"; 3 | import { DEFAULT_CC_TYPES } from "../api.js"; 4 | import { CHANGELOG_TYPE_ORDER } from "../model/api.js"; 5 | 6 | export const ARG_ALL = { 7 | all: flag({ 8 | alias: "a", 9 | desc: "Process all packages, not just unreleased", 10 | }), 11 | }; 12 | 13 | export const ARG_CC_TYPES = { 14 | ccTypes: oneOfMulti({ 15 | alias: "cc", 16 | hint: "TYPE", 17 | delim: ",", 18 | opts: CHANGELOG_TYPE_ORDER.slice(1), 19 | default: DEFAULT_CC_TYPES, 20 | desc: "Only consider given Conventional Commit types for determining changes", 21 | }), 22 | }; 23 | 24 | export const ARG_DRY = { 25 | dryRun: flag({ desc: "Dry run" }), 26 | }; 27 | 28 | export const ARG_DUMP_SPEC = { 29 | dumpSpec: string({ 30 | hint: "PATH", 31 | desc: "Write release spec to JSON file", 32 | }), 33 | }; 34 | 35 | export const ARG_OUT_DIR = { 36 | outDir: string({ 37 | alias: "o", 38 | hint: "PATH", 39 | desc: "Output root dir (default: --repo-path)", 40 | }), 41 | }; 42 | 43 | export const ARG_REPEAT = { 44 | maxRepeat: int({ 45 | desc: "Max attempts", 46 | default: 3, 47 | coerce: (x: string) => { 48 | const val = coerceInt(x); 49 | return val > 0 && val < 32 50 | ? val 51 | : illegalArgs("value must be in [1..31] range"); 52 | }, 53 | }), 54 | }; 55 | 56 | export const ARG_SINCE = { 57 | since: string({ 58 | hint: "DATE", 59 | default: `${new Date().getFullYear() - 3}-01-01`, 60 | desc: "Cut-off date for versions to be included in changelog", 61 | }), 62 | }; 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thi.ng/monopub", 3 | "version": "1.0.0", 4 | "description": "Monorepo publish/release/changelog manager", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/thi-ng/monopub.git" 8 | }, 9 | "homepage": "https://thi.ng/monopub", 10 | "funding": [ 11 | { 12 | "type": "github", 13 | "url": "https://github.com/sponsors/postspectacular" 14 | }, 15 | { 16 | "type": "liberapay", 17 | "url": "https://liberapay.com/thi.ng" 18 | }, 19 | { 20 | "type": "patreon", 21 | "url": "https://patreon.com/thing_umbrella" 22 | } 23 | ], 24 | "license": "Apache-2.0", 25 | "author": "Karsten Schmidt ", 26 | "type": "module", 27 | "module": "./lib/index.js", 28 | "typings": "./lib/index.d.ts", 29 | "sideEffects": false, 30 | "bin": { 31 | "notes": "bin/monopub" 32 | }, 33 | "dependencies": { 34 | "@thi.ng/api": "^8.12.7", 35 | "@thi.ng/args": "^3.2.1", 36 | "@thi.ng/bench": "^3.6.34", 37 | "@thi.ng/checks": "^3.7.23", 38 | "@thi.ng/compare": "^2.4.33", 39 | "@thi.ng/date": "^2.7.69", 40 | "@thi.ng/dgraph": "^2.1.183", 41 | "@thi.ng/errors": "^2.5.47", 42 | "@thi.ng/file-io": "^2.2.16", 43 | "@thi.ng/logger": "^3.2.6", 44 | "@thi.ng/rstream": "^9.3.4", 45 | "@thi.ng/strings": "^3.9.27", 46 | "@thi.ng/system": "^3.1.82", 47 | "@thi.ng/text-format": "^2.2.46", 48 | "@thi.ng/transducers": "^9.6.15", 49 | "dotenv": "^17.2.3" 50 | }, 51 | "devDependencies": { 52 | "@types/node": "^24.10.0", 53 | "ts-node": "^10.9.2", 54 | "typescript": "^5.9.3" 55 | }, 56 | "files": [ 57 | "lib/*.js", 58 | "bin" 59 | ], 60 | "scripts": { 61 | "build": "yarn clean && tsc", 62 | "clean": "rm -rf ./lib", 63 | "dev": "yarn clean && tsc -w", 64 | "pub": "yarn publish --access public" 65 | }, 66 | "publishConfig": { 67 | "access": "public" 68 | }, 69 | "packageManager": "yarn@4.11.0" 70 | } 71 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { kvPairs, string, strings, type Args } from "@thi.ng/args"; 2 | import type { LogLevelName } from "@thi.ng/logger"; 3 | import { 4 | FMT_ANSI16, 5 | FMT_NONE, 6 | defFormatPresets, 7 | type FormatPresets, 8 | type StringFormat, 9 | } from "@thi.ng/text-format"; 10 | import { REQUIRED, type CLIOpts } from "./api.js"; 11 | 12 | export class AppConfig { 13 | logLevel: LogLevelName; 14 | fmt!: StringFormat; 15 | theme!: FormatPresets; 16 | 17 | specs: Args; 18 | 19 | constructor() { 20 | this.logLevel = process.env.MONOPUB_LOG_LEVEL || "INFO"; 21 | this.specs = { 22 | repoPath: string({ 23 | alias: "p", 24 | hint: "PATH", 25 | default: process.env.MONOPUB_REPO_PATH || REQUIRED, 26 | desc: "Monorepo local path", 27 | group: "common", 28 | }), 29 | repoUrl: string({ 30 | alias: "u", 31 | hint: "URL", 32 | default: process.env.MONOPUB_REPO_URL || REQUIRED, 33 | desc: "Monorepo remote URL", 34 | group: "common", 35 | }), 36 | scope: string({ 37 | alias: "s", 38 | hint: "SCOPE", 39 | default: process.env.MONOPUB_SCOPE || REQUIRED, 40 | desc: "Package scope", 41 | group: "common", 42 | }), 43 | root: string({ 44 | alias: "r", 45 | hint: "PATH", 46 | default: process.env.MONOPUB_PKG_ROOT || "packages", 47 | desc: "Relative package root dir in repo", 48 | group: "common", 49 | }), 50 | ext: strings({ 51 | delim: ",", 52 | hint: "EXT", 53 | default: [".+"], 54 | desc: "File types to consider for changes (comma separated)", 55 | group: "common", 56 | }), 57 | alias: kvPairs({ 58 | alias: "A", 59 | default: {}, 60 | desc: "Alias pkg names (old=new)", 61 | group: "common", 62 | }), 63 | indent: string({ 64 | default: "\t", 65 | hint: "VAL", 66 | desc: "Indentation string for generated JSON files", 67 | group: "common", 68 | }), 69 | }; 70 | this.setFormat(process.env.NO_COLOR ? FMT_NONE : FMT_ANSI16); 71 | } 72 | 73 | get isColor() { 74 | return this.fmt !== FMT_NONE; 75 | } 76 | 77 | setFormat(fmt: StringFormat) { 78 | this.fmt = fmt; 79 | this.theme = defFormatPresets(fmt); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/cmd/version.ts: -------------------------------------------------------------------------------- 1 | import type { IObjectOf } from "@thi.ng/api"; 2 | import { assert } from "@thi.ng/errors"; 3 | import { readJSON, writeJSON } from "@thi.ng/file-io"; 4 | import { resolve } from "node:path"; 5 | import type { 6 | AllPkgOpts, 7 | CLIOpts, 8 | CommandSpec, 9 | DryRunOpts, 10 | DumpSpecOpts, 11 | OutDirOpts, 12 | } from "../api"; 13 | import type { Logger } from "../logger.js"; 14 | import type { ReleaseSpec } from "../model/api.js"; 15 | import { pkgJsonPath, pkgShortName } from "../model/package.js"; 16 | import { ARG_ALL, ARG_DRY, ARG_DUMP_SPEC, ARG_OUT_DIR } from "./args.js"; 17 | import { buildReleaseSpecFromCtx } from "./common.js"; 18 | 19 | export interface VersionOpts 20 | extends CLIOpts, 21 | AllPkgOpts, 22 | DumpSpecOpts, 23 | DryRunOpts, 24 | OutDirOpts {} 25 | 26 | export const VERSION: CommandSpec = { 27 | fn: async (ctx) => { 28 | applyVersionBumps( 29 | ctx.opts, 30 | await buildReleaseSpecFromCtx(ctx), 31 | ctx.logger 32 | ); 33 | }, 34 | opts: { 35 | ...ARG_ALL, 36 | ...ARG_DRY, 37 | ...ARG_DUMP_SPEC, 38 | ...ARG_OUT_DIR, 39 | }, 40 | usage: "Compute & apply version bumps", 41 | }; 42 | 43 | export const applyVersionBumps = ( 44 | opts: VersionOpts, 45 | spec: Readonly, 46 | logger: Logger 47 | ) => { 48 | const dest = resolve(opts.outDir || opts.repoPath); 49 | for (let id in spec.nextVersions) { 50 | const nextVersion = spec.nextVersions[id]; 51 | assert(!!nextVersion, `missing version info for pkg: ${id}`); 52 | const pkg = readJSON(pkgJsonPath(opts.repoPath, opts.root, id)); 53 | assert( 54 | pkg.version === spec.versions[id], 55 | `current version mismatch for pkg: ${id}` 56 | ); 57 | pkg.version = nextVersion; 58 | logger.info(`version bump:`, id, spec.versions[id], "->", nextVersion); 59 | pkg.dependencies && 60 | updateDeps(opts.scope, pkg.dependencies, spec, logger); 61 | pkg.devDependencies && 62 | updateDeps(opts.scope, pkg.devDependencies, spec, logger); 63 | writeJSON( 64 | pkgJsonPath(dest, opts.root, id), 65 | pkg, 66 | null, 67 | opts.indent, 68 | logger, 69 | opts.dryRun 70 | ); 71 | } 72 | }; 73 | 74 | const RE_SEMVER = /^\^?\d+\.\d+\.\d+/; 75 | 76 | const updateDeps = ( 77 | scope: string, 78 | deps: IObjectOf, 79 | spec: ReleaseSpec, 80 | logger: Logger 81 | ) => { 82 | for (let id in deps) { 83 | if ( 84 | !id.startsWith(scope) || 85 | // only process semver deps 86 | !RE_SEMVER.test(deps[id]) 87 | ) { 88 | logger.debug(`skipping dep: ${id}: ${deps[id]}`); 89 | continue; 90 | } 91 | const name = pkgShortName(id); 92 | deps[id] = `^${spec.nextVersions[name] || spec.versions[name]}`; 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import type { Fn } from "@thi.ng/api"; 2 | import type { Args, KVDict } from "@thi.ng/args"; 3 | import { readJSON } from "@thi.ng/file-io"; 4 | import { resolve } from "node:path"; 5 | import type { AppConfig } from "./config.js"; 6 | import type { Logger } from "./logger.js"; 7 | import { pkgShortName } from "./model/package.js"; 8 | 9 | export interface CLIOpts { 10 | /** 11 | * Same as {@link RepoConfig.path} 12 | */ 13 | repoPath: string; 14 | /** 15 | * Same as {@link RepoConfig.url} 16 | */ 17 | repoUrl: string; 18 | /** 19 | * Same as {@link RepoConfig.scope} 20 | */ 21 | scope: string; 22 | /** 23 | * Same as {@link RepoConfig.pkgRoot} 24 | */ 25 | root: string; 26 | /** 27 | * Same as {@link RepoConfig.fileExt} 28 | */ 29 | ext: string[]; 30 | /** 31 | * Same as {@link RepoConfig.alias} 32 | */ 33 | alias: KVDict; 34 | /** 35 | * Indentation for generated JSON files 36 | * 37 | * @defaultValue "\t" 38 | */ 39 | indent: string; 40 | } 41 | 42 | export interface AllPkgOpts { 43 | all: boolean; 44 | } 45 | 46 | export interface CCTypeOpts { 47 | ccTypes: string[]; 48 | } 49 | 50 | export interface DryRunOpts { 51 | dryRun: boolean; 52 | } 53 | 54 | export interface DumpSpecOpts { 55 | dumpSpec?: string; 56 | } 57 | 58 | export interface OutDirOpts { 59 | outDir?: string; 60 | } 61 | 62 | export interface MaxRepeatOpts { 63 | maxRepeat: number; 64 | } 65 | 66 | export interface CommandSpec { 67 | /** 68 | * Actual command implementation 69 | */ 70 | fn: Fn, Promise>; 71 | /** 72 | * Command specific CLI arg specs 73 | */ 74 | opts: Args>; 75 | /** 76 | * Usage string for command overview. 77 | */ 78 | usage: string; 79 | } 80 | 81 | export interface CommandCtx { 82 | cmd: CommandSpec; 83 | config: AppConfig; 84 | logger: Logger; 85 | opts: T; 86 | rest: string[]; 87 | } 88 | 89 | export const INSTALL_DIR = resolve(`${process.argv[2]}/..`); 90 | 91 | export const PKG = readJSON(`${INSTALL_DIR}/package.json`); 92 | 93 | export const APP_NAME = pkgShortName(PKG.name); 94 | 95 | export const HEADER = ` 96 | █ █ █ │ 97 | ██ █ │ 98 | █ █ █ █ █ █ █ █ │ ${PKG.name} ${PKG.version} 99 | █ █ █ █ █ █ █ █ █ │ ${PKG.description} 100 | █ │ 101 | █ █ │ 102 | `; 103 | 104 | export const REQUIRED = ""; 105 | 106 | export const DEFAULT_CHANGELOG_BRANCH = "main"; 107 | 108 | export const DEFAULT_RELEASE_BRANCH = "main"; 109 | 110 | export const DEFAULT_CC_TYPES = ["feat", "fix", "refactor", "perf"]; 111 | -------------------------------------------------------------------------------- /src/model/api.ts: -------------------------------------------------------------------------------- 1 | import type { IObjectOf, NumOrString } from "@thi.ng/api"; 2 | import type { KVDict } from "@thi.ng/args"; 3 | import type { DGraph } from "@thi.ng/dgraph"; 4 | 5 | export interface RepoConfig { 6 | /** 7 | * Absolute local monorepo path/root dir 8 | */ 9 | path: string; 10 | /** 11 | * Remote monorepo base URL (e.g. https://github.com/thi-ng/umbrella) - NO trailing slash! 12 | */ 13 | url: string; 14 | /** 15 | * Relative package root dir in repo (NO trailing slash!). 16 | * 17 | * @defaultValue "packages" 18 | */ 19 | pkgRoot: string; 20 | /** 21 | * Common package scope for all packages in the monorepo, e.g. `@thi.ng`. 22 | */ 23 | scope: string; 24 | /** 25 | * Only consider given file types/extensions for determining changes 26 | */ 27 | fileExt: string[]; 28 | /** 29 | * Package names aliases (keys = old name, vals = new name) 30 | */ 31 | alias: KVDict; 32 | } 33 | 34 | export interface Commit { 35 | /** 36 | * SHA1 hash 37 | */ 38 | sha: string; 39 | /** 40 | * Object of unscoped package names (as keys) and their versions (as 41 | * values). 42 | */ 43 | tags: IObjectOf; 44 | /** 45 | * First line of commit message 46 | */ 47 | title: string; 48 | /** 49 | * Commit date as ISO string 50 | */ 51 | date: string; 52 | /** 53 | * Commit author 54 | */ 55 | author: string; 56 | /** 57 | * Remaining commit message lines (excluding empty lines) 58 | */ 59 | msg: string[]; 60 | /** 61 | * Files touched by this commit 62 | */ 63 | files: string[]; 64 | /** 65 | * (Short) package names touched by this commit (computed from `files`). 66 | */ 67 | pkgs: string[]; 68 | /** 69 | * Conventional commit type (e.g. feat/fix/refactor/perf/build/chore etc.) 70 | */ 71 | type: string; 72 | /** 73 | * True, if commit is a breaking change (i.e. if commit message includes a 74 | * line starting with: `BREAKING CHANGE:`) 75 | */ 76 | breaking: boolean; 77 | } 78 | 79 | export interface CommitHistoryOpts extends RepoConfig { 80 | all: boolean; 81 | } 82 | 83 | export interface ReleaseSpecOpts extends CommitHistoryOpts { 84 | dump?: string; 85 | indent: NumOrString; 86 | } 87 | 88 | export interface ReleaseSpec { 89 | repo: RepoConfig; 90 | touched: Set; 91 | graph: DGraph; 92 | unreleased: Commit[]; 93 | previous: Commit[][]; 94 | versions: IObjectOf; 95 | nextVersions: IObjectOf; 96 | } 97 | 98 | export type VersionType = "major" | "minor" | "patch"; 99 | 100 | export const CHANGELOG_TYPE_ORDER: ConventionalCommitType[] = [ 101 | "break", 102 | "feat", 103 | "fix", 104 | "perf", 105 | "refactor", 106 | "build", 107 | "docs", 108 | "chore", 109 | ]; 110 | 111 | export type ConventionalCommitType = keyof typeof CHANGELOG_TYPE_LABELS; 112 | 113 | export const CHANGELOG_TYPE_LABELS = { 114 | break: "🛑 Breaking changes", 115 | build: "🛠 Build related", 116 | chore: "🧹 Chores", 117 | docs: "📖 Documentation", 118 | feat: "🚀 Features", 119 | fix: "🩹 Bug fixes", 120 | refactor: "♻️ Refactoring", 121 | perf: "⏱ Performance improvements", 122 | }; 123 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import type { IObjectOf } from "@thi.ng/api"; 2 | import { DEFAULT_THEME, parse, usage, type UsageOpts } from "@thi.ng/args"; 3 | import { padRight, repeat, wordWrapLine } from "@thi.ng/strings"; 4 | import type { FormatPresets } from "@thi.ng/text-format"; 5 | import { APP_NAME, HEADER, type CommandCtx, type CommandSpec } from "./api.js"; 6 | import type { CommandRegistry } from "./commands.js"; 7 | import type { AppConfig } from "./config.js"; 8 | import type { Logger } from "./logger.js"; 9 | 10 | const usageOpts: Partial = { 11 | lineWidth: process.stdout.columns, 12 | prefix: `${HEADER} 13 | usage: ${APP_NAME} CMD [OPTS] ... 14 | ${APP_NAME} [CMD] --help 15 | 16 | `, 17 | groups: ["flags", "main", "common"], 18 | showGroupNames: true, 19 | paramWidth: 36, 20 | }; 21 | 22 | export class ArgParser { 23 | ctx?: Partial>; 24 | 25 | constructor( 26 | public logger: Logger, 27 | public config: AppConfig, 28 | public commands: CommandRegistry 29 | ) {} 30 | 31 | async start() { 32 | const commands = this.commands.registry; 33 | try { 34 | const cmd = process.argv[3]; 35 | const cmdSpec = commands[cmd]; 36 | if (cmdSpec) { 37 | const args = parse( 38 | { ...this.config.specs, ...cmdSpec.opts }, 39 | process.argv, 40 | { 41 | start: 4, 42 | usageOpts: { 43 | ...usageOpts, 44 | color: this.config.isColor ? DEFAULT_THEME : false, 45 | prefix: commandUsagePrefix( 46 | cmd, 47 | cmdSpec, 48 | this.config.theme 49 | ), 50 | }, 51 | } 52 | ); 53 | if (args) { 54 | this.ctx = { 55 | cmd: cmdSpec, 56 | opts: args.result, 57 | rest: args.rest, 58 | }; 59 | return true; 60 | } 61 | } else { 62 | process.stdout.write( 63 | usage(this.config.specs, { 64 | ...usageOpts, 65 | color: this.config.isColor ? DEFAULT_THEME : false, 66 | prefix: commonUsagePrefix(commands), 67 | }) 68 | ); 69 | } 70 | } catch (e) { 71 | this.logger.severe((e).message); 72 | } 73 | return false; 74 | } 75 | } 76 | 77 | const commandOverview = (id: string, usage: string) => 78 | ` ${padRight(10, " ")(id)}∷ ${wordWrapLine(firstSentence(usage), { 79 | width: process.stdout.columns - 16, 80 | }) 81 | .map((l, i) => (i > 0 ? repeat(" ", 16) + l : l)) 82 | .join("\n")}`; 83 | 84 | const commonUsagePrefix = (commands: IObjectOf>) => 85 | [ 86 | usageOpts.prefix, 87 | `Available commands:\n`, 88 | ...Object.keys(commands).map((id) => 89 | commandOverview(id, commands[id].usage) 90 | ), 91 | "\n", 92 | ].join("\n"); 93 | 94 | const commandUsagePrefix = ( 95 | id: string, 96 | spec: CommandSpec, 97 | theme: FormatPresets 98 | ) => 99 | usageOpts.prefix + 100 | [ 101 | `Current command '${id}':`, 102 | "", 103 | highlightArgs(spec.usage, theme), 104 | "\n", 105 | ].join("\n"); 106 | 107 | export const firstSentence = (x: string) => { 108 | const idx = x.indexOf("."); 109 | return idx > 0 ? x.substring(0, idx) : x; 110 | }; 111 | 112 | export const highlightArgs = (x: string, theme: FormatPresets) => 113 | x.replace(/`(-[a-z-]+)`/g, (_, opt) => theme.cyan(opt)); 114 | -------------------------------------------------------------------------------- /src/model/release.ts: -------------------------------------------------------------------------------- 1 | import type { IObjectOf } from "@thi.ng/api"; 2 | import { defDGraph } from "@thi.ng/dgraph"; 3 | import { assert } from "@thi.ng/errors"; 4 | import { readJSON, writeJSON } from "@thi.ng/file-io"; 5 | import { conj, mapcat, partitionWhen, transduce } from "@thi.ng/transducers"; 6 | import type { Logger } from "../logger.js"; 7 | import type { ReleaseSpec, ReleaseSpecOpts } from "./api.js"; 8 | import { commitsSinceLastPublish } from "./git.js"; 9 | import { pkgJsonPath, pkgShortName } from "./package.js"; 10 | import { isPublish } from "./utils.js"; 11 | import { classifyVersionBump, versionBump } from "./version.js"; 12 | 13 | export const buildReleaseSpec = async ( 14 | opts: ReleaseSpecOpts, 15 | logger: Logger 16 | ) => { 17 | const commits = await commitsSinceLastPublish(opts); 18 | assert(commits.length > 0, `no new commits yet, exiting...`); 19 | let groups = [...partitionWhen(isPublish, commits)]; 20 | const [unreleased, previous] = isPublish(groups[0][0]) 21 | ? [[], groups] 22 | : [groups[0], groups.slice(1)]; 23 | const touchedPkgIDs = transduce( 24 | mapcat((x) => x.pkgs), 25 | conj(), 26 | opts.all ? commits : unreleased 27 | ); 28 | const allPkgIDs = transduce( 29 | mapcat((x) => x.pkgs), 30 | conj(), 31 | commits 32 | ); 33 | // touchedPkgIDs.delete("api"); 34 | // touchedPkgIDs.delete("transducers"); 35 | const { deps, versions } = buildPkgCache( 36 | opts, 37 | allPkgIDs, 38 | touchedPkgIDs, 39 | logger 40 | ); 41 | const graph = buildPkgGraph(deps, opts.scope); 42 | const spec: ReleaseSpec = { 43 | repo: opts, 44 | touched: touchedPkgIDs, 45 | graph, 46 | unreleased, 47 | previous, 48 | versions, 49 | nextVersions: {}, 50 | }; 51 | if (unreleased.length || opts.all) { 52 | const transitivePackages = transduce( 53 | mapcat((id) => [id, ...graph.transitiveDependents(id)]), 54 | conj(), 55 | touchedPkgIDs 56 | ); 57 | for (let pkg of transitivePackages) { 58 | spec.nextVersions[pkg] = versionBump( 59 | versions[pkg], 60 | classifyVersionBump(pkg, unreleased) 61 | ); 62 | } 63 | } 64 | if (opts.dump) { 65 | writeJSON( 66 | opts.dump, 67 | { 68 | ...spec, 69 | touched: [...spec.touched].sort(), 70 | graph: [...spec.graph], 71 | }, 72 | null, 73 | opts.indent, 74 | logger 75 | ); 76 | } 77 | return spec; 78 | }; 79 | 80 | const buildPkgCache = ( 81 | opts: ReleaseSpecOpts, 82 | allPkgIDs: Set, 83 | touchedPkgIDs: Set, 84 | logger: Logger 85 | ) => { 86 | const deps: IObjectOf = {}; 87 | const versions: IObjectOf = {}; 88 | for (let id of allPkgIDs) { 89 | try { 90 | const pkg = readJSON(pkgJsonPath(opts.path, opts.pkgRoot, id)); 91 | versions[id] = pkg.version; 92 | deps[id] = Object.keys(pkg.dependencies || {}); 93 | } catch (_) { 94 | logger.debug(`ignoring invalid/obsolete/missing package: ${id}`); 95 | touchedPkgIDs.delete(id); 96 | allPkgIDs.delete(id); 97 | } 98 | } 99 | return { deps, versions }; 100 | }; 101 | 102 | export const buildPkgGraph = (cache: IObjectOf, scope: string) => { 103 | const graph = defDGraph(); 104 | for (let id in cache) { 105 | const deps = cache[id]; 106 | if (deps.length) { 107 | for (let d of deps) { 108 | d.startsWith(scope) && graph.addDependency(id, pkgShortName(d)); 109 | } 110 | } else { 111 | graph.addNode(id); 112 | } 113 | } 114 | return graph; 115 | }; 116 | -------------------------------------------------------------------------------- /src/model/git.ts: -------------------------------------------------------------------------------- 1 | import type { IObjectOf, Maybe, Nullable, Pair } from "@thi.ng/api"; 2 | import type { KVDict } from "@thi.ng/args"; 3 | import { illegalState } from "@thi.ng/errors"; 4 | import { transduce as $transduce, linesFromNodeJS } from "@thi.ng/rstream"; 5 | import { 6 | comp, 7 | filter, 8 | push, 9 | type Reducer, 10 | type Transducer, 11 | } from "@thi.ng/transducers"; 12 | import { spawn } from "node:child_process"; 13 | import type { Commit, CommitHistoryOpts, RepoConfig } from "./api.js"; 14 | import { isBreakingChangeMsg } from "./utils.js"; 15 | 16 | const parseTags = (src: string, scope: string) => { 17 | const re = /tag: ([@a-z0-9/.-]+)/g; 18 | const tags: IObjectOf = {}; 19 | const prefix = `refs/tags/${scope}/`; 20 | let match: Nullable; 21 | while ((match = re.exec(src))) { 22 | const [pkg, version] = >( 23 | match[1].substring(prefix.length).split("@") 24 | ); 25 | tags[pkg] = version; 26 | } 27 | return tags; 28 | }; 29 | 30 | type ParseCommitOpts = Required< 31 | Pick 32 | >; 33 | 34 | /** 35 | * Transducer consuming lines from `git log` and parsing/grouping them into 36 | * {@link Commit} objects. Used by {@link commitsSinceLastPublish}. 37 | * 38 | * @param opts 39 | */ 40 | export const parseCommit = 41 | (opts: ParseCommitOpts): Transducer => 42 | ([init, complete, reduce]: Reducer) => { 43 | const reCommitHeader = /^commit ([a-f0-9]{40})(.*)/i; 44 | const reCommitMeta = /^(author|date):\s+(.*)/i; 45 | const reConventionalCommit = /^([a-z]+)(\([a-z0-9_-]+\))?:\s+(.+)/i; 46 | const reFileChange = /^([adm]|[cr]\d+)\s+(.*)/i; 47 | const fileExt = [...new Set(["json", ...opts.fileExt])].join("|"); 48 | const rePkgFile = new RegExp( 49 | `^${opts.pkgRoot}/([a-z0-9_-]+)/.+\\.(${fileExt})$` 50 | ); 51 | let commit: Maybe; 52 | return [ 53 | init, 54 | (acc: any) => { 55 | if (commit) { 56 | acc = reduce(acc, commit); 57 | commit = undefined; 58 | } 59 | return complete(acc); 60 | }, 61 | (acc: any, line: string) => { 62 | let match = reCommitHeader.exec(line); 63 | if (match) { 64 | if (commit) { 65 | acc = reduce(acc, commit); 66 | } 67 | commit = { 68 | sha: match[1], 69 | tags: parseTags(match[2], opts.scope), 70 | title: "", 71 | msg: [], 72 | files: [], 73 | date: "", 74 | author: "", 75 | type: "", 76 | pkgs: [], 77 | breaking: false, 78 | }; 79 | } else if (commit) { 80 | match = reCommitMeta.exec(line); 81 | if (match) { 82 | if (match[1].toLowerCase() === "author") 83 | commit.author = match[2]; 84 | if (match[1].toLowerCase() === "date") 85 | commit.date = match[2]; 86 | } else { 87 | match = reFileChange.exec(line); 88 | if (match) { 89 | const matchExt = rePkgFile.exec(match[2]); 90 | if (matchExt) { 91 | commit.files.push(match[2]); 92 | const pkgName = resolveAlias( 93 | opts.alias, 94 | matchExt[1] 95 | ); 96 | if (!commit.pkgs.includes(pkgName)) { 97 | commit.pkgs.push(pkgName); 98 | } 99 | } 100 | } else { 101 | line = line.substring(4); 102 | if (line.length) { 103 | if (!commit.title) { 104 | match = reConventionalCommit.exec(line); 105 | if (match) { 106 | commit.type = match[1]; 107 | commit.title = match[3]; 108 | } else { 109 | commit.title = line; 110 | } 111 | } else { 112 | commit.msg.push(line); 113 | !commit.breaking && 114 | (commit.breaking = 115 | isBreakingChangeMsg(line)); 116 | } 117 | } 118 | } 119 | } 120 | } else { 121 | illegalState(`unexpected line ${line}`); 122 | } 123 | return acc; 124 | }, 125 | ]; 126 | }; 127 | 128 | const resolveAlias = (aliases: KVDict, id: string) => aliases[id] || id; 129 | 130 | export const commitsSinceLastPublish = async (opts: CommitHistoryOpts) => { 131 | const cmd = spawn( 132 | "git", 133 | [ 134 | "log", 135 | "--no-color", 136 | "--name-status", 137 | "--decorate=full", 138 | "--date=iso-strict", 139 | ], 140 | { cwd: opts.path } 141 | ); 142 | return await $transduce( 143 | linesFromNodeJS(cmd.stdout, cmd.stderr), 144 | comp( 145 | parseCommit(opts), 146 | filter((x) => x.pkgs.length > 0) 147 | ), 148 | push() 149 | ); 150 | }; 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @thi.ng/monopub 2 | 3 | Lightweight, simple & fast monorepo publish/release/changelog manager to 4 | automate releases using nothing more than [Conventional 5 | Commits](https://conventionalcommits.org/). 6 | 7 | ## Status 8 | 9 | **stable** - used in production 10 | 11 | Currently implemented: 12 | 13 | - [x] Detailed Git commit parsing, filtering & analysis 14 | - [x] Filter by file ext 15 | - [x] Package name aliases (to assign commits from old pkg names) 16 | - [x] Computing packages touched by recent commits (or allow forcing all) 17 | - [x] Dependency graph construction for monorepo internal packages (incl. topological sort) 18 | - [x] Computing new package versions (based on Conventional Commit types used) 19 | - [x] Selective changelog creation (as Markdown files) 20 | - [x] Commit type config 21 | - [x] Repo/publish config via dotenv 22 | - [x] Update package files w/ version bumps 23 | - [x] Update/bump deps in transitive dependents 24 | - [x] Update `yarn.lock` prior to 'publish' commit 25 | - [x] Commit updated package, yarn.lock & changelog files 26 | - [x] Create & add release tags 27 | - [x] Push to git remote 28 | - [x] Inject `gitHead` into published `package.json` files 29 | - [x] Publish to registry 30 | - [x] Reset git head post-publish 31 | - [x] Add pre-checks 32 | - [x] On clean release branch? 33 | - [ ] Valid npm login/auth? 34 | - [ ] Research granular NPM token creation 35 | 36 | ## Goals & Non-goals 37 | 38 | The original aim of this project was to produce an as minimal as possible 39 | release workflow suitable for the [thi.ng/umbrella 40 | monorepo](https://thi.ng/umbrella) (currently ~210 TypeScript 41 | projects/packages). Over the past 2+ years, this tool has been reliably used to 42 | handle hundreds of releases (tens of thousands if you count individual package 43 | releases) and so I consider this goal reached. The tool is also a magnitude 44 | faster than my previous user experience with Lerna. Version analysis, version 45 | bumping and changelog generation (all Conventional Commits based) for all ~210 46 | packages in thi.ng/umbrella only takes ~2-3 seconds (max), unlike Lerna which 47 | regularly took 30+ secs for the same tasks (and produced worse changelogs)... 48 | 49 | There are configuration options to allow this project being used with other 50 | (similarly structured) monorepo setups, however there's no desire to go down the 51 | usual route in JS-land of adding hundreds of overly complicated options suitable 52 | for seemingly all use cases and then none... 53 | 54 | If you're interested in utilizing this tool with your repo, but not sure how, 55 | please reach out via the issue tracker... 56 | 57 | ## Usage 58 | 59 | ```bash 60 | git clone https://github.com/thi-ng/monopub.git 61 | 62 | cd monopub 63 | 64 | yarn install 65 | yarn build 66 | 67 | bin/monopub --help 68 | ``` 69 | 70 | ```text 71 | █ █ █ │ 72 | ██ █ │ 73 | █ █ █ █ █ █ █ █ │ @thi.ng/monopub 1.0.0 74 | █ █ █ █ █ █ █ █ █ │ Monorepo publish/release/changelog manager 75 | █ │ 76 | █ █ │ 77 | 78 | usage: monopub CMD [OPTS] ... 79 | monopub [CMD] --help 80 | 81 | 82 | Available commands: 83 | 84 | changelog ∷ Create/update changelogs 85 | release ∷ Prepare and execute full release of all touched packages 86 | version ∷ Compute & apply version bumps 87 | 88 | Common: 89 | 90 | -A key=val, --alias key=val [multiple] Alias pkg names (old=new) (default: {}) 91 | --ext EXT [multiple] File types to consider for changes (comma separated) (default: [".+"]) 92 | --indent VAL Indentation for generated JSON files: "number", "tab" (default: "\t") 93 | -p PATH, --repo-path PATH Monorepo local path (default: "") 94 | -u URL, --repo-url URL Monorepo remote URL (default: "") 95 | -r PATH, --root PATH Relative package root dir in repo (default: "packages") 96 | -s SCOPE, --scope SCOPE Package scope (default: "") 97 | ``` 98 | 99 | ### Command: changelog 100 | 101 | > [!NOTE] 102 | > See various packages in the [thi.ng/umbrella](https://github.com/thi-ng/umbrella) monorepo for generated changelogs: 103 | > example [thi.ng/rstream changelog](https://github.com/thi-ng/umbrella/blob/develop/packages/rstream/CHANGELOG.md) 104 | 105 | Create/update changelogs 106 | 107 | ```text 108 | Flags: 109 | 110 | -a, --all Process all packages, not just unreleased 111 | --dry-run Dry run 112 | 113 | Main: 114 | 115 | -b NAME, --branch NAME Remote Git branch for package links in changelog (default: "main") 116 | -cc TYPE, --cc-types TYPE [multiple] Only consider given Conventional Commit types for determining changes: 117 | "feat", "fix", "perf", "refactor", "build", "docs", "chore" (default: 118 | ["feat","fix","refactor","perf"]) 119 | --dump-spec PATH Write release spec to JSON file 120 | -o PATH, --out-dir PATH Output root dir (default: --repo-path) 121 | ``` 122 | 123 | ### Command: version 124 | 125 | Compute & apply version bumps 126 | 127 | ```text 128 | Flags: 129 | 130 | -a, --all Process all packages, not just unreleased 131 | --dry-run Dry run 132 | 133 | Main: 134 | 135 | --dump-spec PATH Write release spec to JSON file 136 | -o PATH, --out-dir PATH Output root dir (default: --repo-path) 137 | ``` 138 | 139 | ### Command: release 140 | 141 | Prepare and execute full release of all touched packages 142 | 143 | ```text 144 | Flags: 145 | 146 | -a, --all Process all packages, not just unreleased 147 | --dry-run Dry run 148 | 149 | Main: 150 | 151 | -cc TYPE, --cc-types TYPE [multiple] Only consider given Conventional Commit types for determining changes: 152 | "feat", "fix", "perf", "refactor", "build", "docs", "chore" (default: 153 | ["feat","fix","refactor","perf"]) 154 | -cb NAME, --changelog-branch NAME Remote Git branch for package links in changelog (default: "main") 155 | --dump-spec PATH Write release spec to JSON file 156 | --max-repeat INT Max attempts (default: 3) 157 | -script CMD, --publish-script CMD Publish script alias name (default: "pub") 158 | -rb NAME, --release-branch NAME Remote branch name for publishing releases (default: "main") 159 | -t INT, --throttle INT Delay time (in ms) between publishing each pkg (default: 0) 160 | ``` 161 | 162 | ## License 163 | 164 | © 2021 - 2025 Karsten Schmidt // Apache Software License 2.0 165 | -------------------------------------------------------------------------------- /src/cmd/changelog.ts: -------------------------------------------------------------------------------- 1 | import { string } from "@thi.ng/args"; 2 | import { compareByKey } from "@thi.ng/compare"; 3 | import { FMT_ISO_SHORT, dateTime } from "@thi.ng/date"; 4 | import { writeText } from "@thi.ng/file-io"; 5 | import { comp, filter, groupByObj, transduce } from "@thi.ng/transducers"; 6 | import { resolve } from "node:path"; 7 | import { 8 | DEFAULT_CHANGELOG_BRANCH, 9 | type AllPkgOpts, 10 | type CCTypeOpts, 11 | type CLIOpts, 12 | type CommandSpec, 13 | type DryRunOpts, 14 | type DumpSpecOpts, 15 | type OutDirOpts, 16 | } from "../api.js"; 17 | import type { Logger } from "../logger.js"; 18 | import { 19 | CHANGELOG_TYPE_LABELS, 20 | CHANGELOG_TYPE_ORDER, 21 | type Commit, 22 | type ConventionalCommitType, 23 | type ReleaseSpec, 24 | } from "../model/api.js"; 25 | import { isBreakingChangeMsg } from "../model/utils.js"; 26 | import { classifyVersion } from "../model/version.js"; 27 | import { 28 | ARG_ALL, 29 | ARG_CC_TYPES, 30 | ARG_DRY, 31 | ARG_DUMP_SPEC, 32 | ARG_OUT_DIR, 33 | ARG_SINCE, 34 | } from "./args.js"; 35 | import { buildReleaseSpecFromCtx } from "./common.js"; 36 | 37 | export interface ChangelogOpts 38 | extends CLIOpts, 39 | AllPkgOpts, 40 | CCTypeOpts, 41 | DumpSpecOpts, 42 | DryRunOpts, 43 | OutDirOpts { 44 | branch: string; 45 | since: string; 46 | } 47 | 48 | export const CHANGELOG: CommandSpec = { 49 | fn: async (ctx) => { 50 | generateChangeLogs( 51 | ctx.opts, 52 | await buildReleaseSpecFromCtx(ctx), 53 | ctx.logger 54 | ); 55 | }, 56 | opts: { 57 | ...ARG_ALL, 58 | ...ARG_CC_TYPES, 59 | ...ARG_DRY, 60 | ...ARG_DUMP_SPEC, 61 | ...ARG_OUT_DIR, 62 | ...ARG_SINCE, 63 | 64 | branch: string({ 65 | alias: "b", 66 | hint: "NAME", 67 | default: DEFAULT_CHANGELOG_BRANCH, 68 | desc: "Remote Git branch for package links in changelog", 69 | }), 70 | }, 71 | usage: "Create/update changelogs", 72 | }; 73 | 74 | export const generateChangeLogs = ( 75 | opts: ChangelogOpts, 76 | spec: Readonly, 77 | logger: Logger 78 | ) => { 79 | const dest = resolve(opts.outDir || opts.repoPath); 80 | for (let pkg of spec.touched) { 81 | logger.debug(pkg, spec.nextVersions[pkg]); 82 | const changelog = changeLogForPackage( 83 | opts, 84 | pkg, 85 | spec.nextVersions[pkg], 86 | [spec.unreleased, ...spec.previous], 87 | false 88 | ); 89 | if (changelog) { 90 | writeText( 91 | `${dest}/packages/${pkg}/CHANGELOG.md`, 92 | changelog, 93 | logger, 94 | opts.dryRun 95 | ); 96 | } else { 97 | logger.info("skipping changelog:", pkg); 98 | } 99 | } 100 | }; 101 | 102 | /** 103 | * Processes commit groups and constructs a changelog (in Markdown format) for 104 | * given (short) package ID. The `nextVersion` can be obtained via 105 | * {@link getNextVersion}. Unless `newOnly` is false, the function returns 106 | * `undefined` if the first chunk of commits (i.e. the supposedly unreleased 107 | * commit group) does NOT touch the given package ID. 108 | * 109 | * @param opts 110 | * @param id 111 | * @param nextVersion 112 | * @param releases 113 | * @param newOnly 114 | */ 115 | const changeLogForPackage = ( 116 | opts: ChangelogOpts, 117 | id: string, 118 | nextVersion: string, 119 | releases: Commit[][], 120 | newOnly = true 121 | ) => { 122 | const allowedTypes = opts.ccTypes || CHANGELOG_TYPE_ORDER; 123 | const changelog: any[] = [ 124 | `# Change Log`, 125 | ``, 126 | `- **Last updated**: ${FMT_ISO_SHORT(Date.now(), true)}`, 127 | `- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)`, 128 | ``, 129 | `All notable changes to this project will be documented in this file.`, 130 | `Only versions published since **${opts.since}** are listed here.`, 131 | `Please consult the Git history for older version information.`, 132 | `See [Conventional Commits](https://conventionalcommits.org/) for commit guidelines.`, 133 | ``, 134 | `**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes`, 135 | `and/or version bumps of transitive dependencies.`, 136 | ``, 137 | ]; 138 | let first = true; 139 | let hasNewChanges = false; 140 | for (let r of releases) { 141 | if (!r.length) continue; 142 | if (r[0].date < opts.since) break; 143 | let version: string; 144 | let commits: Commit[]; 145 | let date: string; 146 | if (first) { 147 | version = nextVersion; 148 | date = FMT_ISO_SHORT(Date.now(), true); 149 | commits = r; 150 | } else { 151 | version = r[0].tags[id]; 152 | date = FMT_ISO_SHORT(dateTime(r[0].date), true); 153 | commits = r.slice(1); 154 | } 155 | if (!version) { 156 | first = false; 157 | continue; 158 | } 159 | const entryGroups = transduce( 160 | comp( 161 | filter((x) => x.pkgs.includes(id)), 162 | filter( 163 | (x) => 164 | x.breaking || 165 | allowedTypes.includes(x.type) 166 | ) 167 | ), 168 | groupByObj({ 169 | key: (x) => (x.breaking ? "break" : x.type), 170 | }), 171 | commits.slice().sort(compareByKey("date")) 172 | ); 173 | if (!Object.keys(entryGroups).length) { 174 | first = false; 175 | continue; 176 | } 177 | if (first) { 178 | hasNewChanges = true; 179 | first = false; 180 | } 181 | changelog.push( 182 | `${versionHeader(version)} [${version}](${taggedPackageUrl( 183 | opts, 184 | id, 185 | version 186 | )}) (${date.substring(0, 10)})\n` 187 | ); 188 | for (let type of CHANGELOG_TYPE_ORDER) { 189 | const group = entryGroups[type]; 190 | if (!group) continue; 191 | changelog.push(`#### ${CHANGELOG_TYPE_LABELS[type]}\n`); 192 | for (let e of group) { 193 | const sha = e.sha.substring(0, 7); 194 | changelog.push( 195 | `- ${formatGFM(opts, e.title)} (${commitLink(opts, sha)})` 196 | ); 197 | e.msg.length && changelog.push(formatLogMsg(opts, e.msg)); 198 | } 199 | changelog.push(""); 200 | } 201 | } 202 | return hasNewChanges || !newOnly ? changelog.join("\n") : undefined; 203 | }; 204 | 205 | /** 206 | * Applies some Github Flavored Markdown formatting to given line. Currently, 207 | * only the following are processed/replaced with links: 208 | * 209 | * - issue IDs 210 | * - commit SHA1s 211 | * - scoped package names 212 | * 213 | * @param opts 214 | * @param line 215 | */ 216 | const formatGFM = (opts: ChangelogOpts, line: string) => { 217 | line = line 218 | .replace(/#(\d+)/g, (_, id) => issueLink(opts, id)) 219 | .replace(/ ([a-f0-9]{7,})/g, (_, sha) => ` ${commitLink(opts, sha)}`); 220 | return opts.scope 221 | ? line.replace( 222 | new RegExp( 223 | `@?${opts.scope 224 | .substring(1) 225 | .replace(".", "\\.")}/([a-z0-9_-]+)`, 226 | "g" 227 | ), 228 | (_, id) => pkgLink(opts, id) 229 | ) 230 | : line; 231 | }; 232 | 233 | const formatLogMsg = (opts: ChangelogOpts, msg: string[]) => 234 | msg 235 | .map( 236 | (x) => 237 | `${isBreakingChangeMsg(x) ? "- " : " "}${formatGFM(opts, x)}` 238 | ) 239 | .join("\n"); 240 | 241 | const versionHeader = (version: string) => 242 | ({ major: "#", minor: "##", patch: "###" }[classifyVersion(version)]); 243 | 244 | const taggedPackageUrl = (opts: ChangelogOpts, pkg: string, version: string) => 245 | `${opts.repoUrl}/tree/${ 246 | opts.scope ? opts.scope + "/" : "" 247 | }${pkg}@${version}`; 248 | 249 | const commitUrl = (opts: ChangelogOpts, sha: string) => 250 | `${opts.repoUrl}/commit/${sha}`; 251 | 252 | const issueLink = (opts: ChangelogOpts, id: string) => 253 | `[#${id}](${opts.repoUrl}/issues/${id})`; 254 | 255 | const commitLink = (opts: ChangelogOpts, sha: string) => 256 | `[${sha}](${commitUrl(opts, sha)})`; 257 | 258 | const pkgLink = (opts: ChangelogOpts, pkg: string) => 259 | `[${opts.scope}/${pkg}](${opts.repoUrl}/tree/${opts.branch}/${opts.root}/${pkg})`; 260 | -------------------------------------------------------------------------------- /src/cmd/release.ts: -------------------------------------------------------------------------------- 1 | import { coerceInt, int, string } from "@thi.ng/args"; 2 | import { delayed } from "@thi.ng/compose"; 3 | import { illegalArgs } from "@thi.ng/errors"; 4 | import { readJSON, writeJSON } from "@thi.ng/file-io"; 5 | import { execFileSync } from "node:child_process"; 6 | import { 7 | DEFAULT_CHANGELOG_BRANCH, 8 | DEFAULT_RELEASE_BRANCH, 9 | type AllPkgOpts, 10 | type CCTypeOpts, 11 | type CLIOpts, 12 | type CommandCtx, 13 | type CommandSpec, 14 | type DryRunOpts, 15 | type DumpSpecOpts, 16 | type MaxRepeatOpts, 17 | } from "../api.js"; 18 | import type { Logger } from "../logger.js"; 19 | import type { ReleaseSpec } from "../model/api.js"; 20 | import { pkgJsonPath, pkgPath } from "../model/package.js"; 21 | import { 22 | ARG_ALL, 23 | ARG_CC_TYPES, 24 | ARG_DRY, 25 | ARG_DUMP_SPEC, 26 | ARG_REPEAT, 27 | ARG_SINCE, 28 | } from "./args.js"; 29 | import { generateChangeLogs } from "./changelog.js"; 30 | import { buildReleaseSpecFromCtx } from "./common.js"; 31 | import { applyVersionBumps } from "./version.js"; 32 | 33 | export interface ReleaseOpts 34 | extends CLIOpts, 35 | AllPkgOpts, 36 | CCTypeOpts, 37 | DryRunOpts, 38 | DumpSpecOpts, 39 | MaxRepeatOpts { 40 | changelogBranch: string; 41 | since: string; 42 | releaseBranch: string; 43 | publishScript: string; 44 | throttle: number; 45 | } 46 | 47 | export const RELEASE: CommandSpec = { 48 | fn: async (ctx) => { 49 | const { opts, logger } = ctx; 50 | // FIXME debug only 51 | // opts.dryRun = true; 52 | ensureReleaseBranch(ctx); 53 | ensureRepoIsClean(ctx); 54 | const spec = await buildReleaseSpecFromCtx(ctx); 55 | generateChangeLogs( 56 | { 57 | ...opts, 58 | branch: opts.changelogBranch, 59 | since: opts.since, 60 | ccTypes: opts.ccTypes, 61 | }, 62 | spec, 63 | logger 64 | ); 65 | logSep(logger); 66 | applyVersionBumps(opts, spec, logger); 67 | updateYarnLock(ctx); 68 | logSep(logger); 69 | gitCommit(ctx, spec); 70 | logSep(logger); 71 | gitAddReleaseTags(ctx, spec); 72 | logSep(logger); 73 | gitPushRelease(ctx); 74 | logSep(logger); 75 | injectGitHead(ctx, spec); 76 | logSep(logger); 77 | await publishPackages(ctx, spec); 78 | logSep(logger); 79 | gitReset(ctx); 80 | logger.info( 81 | "Successfully published", 82 | Object.keys(spec.nextVersions).length, 83 | "packages" 84 | ); 85 | }, 86 | opts: { 87 | ...ARG_ALL, 88 | ...ARG_CC_TYPES, 89 | ...ARG_DRY, 90 | ...ARG_DUMP_SPEC, 91 | ...ARG_REPEAT, 92 | ...ARG_SINCE, 93 | 94 | changelogBranch: string({ 95 | alias: "cb", 96 | hint: "NAME", 97 | default: DEFAULT_CHANGELOG_BRANCH, 98 | desc: "Remote Git branch for package links in changelog", 99 | }), 100 | releaseBranch: string({ 101 | alias: "rb", 102 | hint: "NAME", 103 | default: DEFAULT_RELEASE_BRANCH, 104 | desc: "Remote branch name for publishing releases", 105 | }), 106 | publishScript: string({ 107 | alias: "script", 108 | hint: "CMD", 109 | default: "pub", 110 | desc: "Publish script alias name", 111 | }), 112 | throttle: int({ 113 | alias: "t", 114 | default: 0, 115 | desc: "Delay time (in ms) between publishing each pkg", 116 | fn: (x: string) => { 117 | const val = coerceInt(x); 118 | return val >= 0 || illegalArgs("value must be >= 0"); 119 | }, 120 | }), 121 | }, 122 | usage: "Prepare and execute full release of all touched packages", 123 | }; 124 | 125 | const logSep = (logger: Logger) => 126 | logger.info("--------------------------------"); 127 | 128 | const execInRepo = ( 129 | ctx: CommandCtx, 130 | cmd: string, 131 | ...args: string[] 132 | ) => { 133 | ctx.logger.debug(cmd, ...args); 134 | try { 135 | return execFileSync(cmd, args, { cwd: ctx.opts.repoPath }); 136 | } catch (e) { 137 | ctx.logger.severe((e).message); 138 | throw new Error("Couldn't execute command, aborting..."); 139 | } 140 | }; 141 | 142 | const ensureRepoIsClean = (ctx: CommandCtx) => { 143 | try { 144 | execInRepo(ctx, "git", "diff-index", "--quiet", "HEAD", "--"); 145 | } catch (e) { 146 | throw new Error("Repo has uncommitted changes, aborting..."); 147 | } 148 | }; 149 | 150 | const ensureReleaseBranch = (ctx: CommandCtx) => { 151 | const branch = execInRepo(ctx, "git", "rev-parse", "--abbrev-ref", "HEAD") 152 | .toString() 153 | .trim(); 154 | if (branch !== ctx.opts.releaseBranch) { 155 | throw new Error("Repo is currently not on release branch, aborting..."); 156 | } 157 | }; 158 | 159 | const gitCommit = (ctx: CommandCtx, spec: ReleaseSpec) => { 160 | const { opts, logger } = ctx; 161 | logger.dry(opts.dryRun, "Creating 'Publish' Git commit..."); 162 | if (opts.dryRun) return; 163 | execInRepo( 164 | ctx, 165 | "git", 166 | "add", 167 | "-f", 168 | "yarn.lock", 169 | ...Object.keys(spec.nextVersions).map( 170 | (x) => `${opts.root}/${x}/CHANGELOG.md` 171 | ) 172 | ); 173 | execInRepo(ctx, "git", "commit", "-a", "-m", "Publish"); 174 | }; 175 | 176 | const gitAddReleaseTags = (ctx: CommandCtx, spec: ReleaseSpec) => { 177 | const { opts, logger } = ctx; 178 | logger.dry(opts.dryRun, "Adding Git release tags..."); 179 | for (let id in spec.nextVersions) { 180 | const tag = `${opts.scope}/${id}@${spec.nextVersions[id]}`; 181 | logger.dry(opts.dryRun, tag); 182 | !opts.dryRun && execInRepo(ctx, "git", "tag", tag); 183 | } 184 | }; 185 | 186 | const gitPushRelease = (ctx: CommandCtx) => { 187 | const { opts, logger } = ctx; 188 | logger.dry(opts.dryRun, "Pushing release to Git remote:", opts.repoUrl); 189 | if (opts.dryRun) return; 190 | execInRepo(ctx, "git", "push", "origin", opts.releaseBranch, "--tags"); 191 | }; 192 | 193 | const gitReset = (ctx: CommandCtx) => { 194 | const { opts, logger } = ctx; 195 | logger.dry(opts.dryRun, "Resetting local git head..."); 196 | if (opts.dryRun) return; 197 | execInRepo(ctx, "git", "reset", "--hard"); 198 | }; 199 | 200 | const injectGitHead = (ctx: CommandCtx, spec: ReleaseSpec) => { 201 | const { opts, logger } = ctx; 202 | const gitHead = execInRepo(ctx, "git", "rev-parse", "HEAD").toString(); 203 | logger.dry(opts.dryRun, "injecting gitHead SHA", gitHead); 204 | for (let id in spec.nextVersions) { 205 | const path = pkgJsonPath(opts.repoPath, opts.root, id); 206 | const pkg = readJSON(path); 207 | pkg.gitHead = gitHead; 208 | writeJSON(path, pkg, null, opts.indent, logger, opts.dryRun); 209 | } 210 | }; 211 | 212 | const publishPackages = async ( 213 | ctx: CommandCtx, 214 | spec: ReleaseSpec 215 | ) => { 216 | const { opts, logger } = ctx; 217 | const packages = [...spec.graph].filter((id) => spec.nextVersions[id]); 218 | const num = packages.length; 219 | for (let i = 0; i < packages.length; i++) { 220 | const id = packages[i]; 221 | logger.dry( 222 | opts.dryRun, 223 | `(${i + 1} / ${num}) publishing pkg: ${opts.scope}/${id}@${ 224 | spec.nextVersions[id] 225 | }` 226 | ); 227 | if (!opts.dryRun) { 228 | for (let k = 0; k < opts.maxRepeat; k++) { 229 | try { 230 | execFileSync("yarn", ["run", opts.publishScript], { 231 | cwd: pkgPath(opts.repoPath, opts.root, id), 232 | }); 233 | if (opts.throttle > 0) await delayed(null, opts.throttle); 234 | break; 235 | } catch (e) { 236 | logger.warn((e).message); 237 | if (k < opts.maxRepeat - 1) { 238 | logger.info( 239 | "waiting for ", 240 | 1 << k, 241 | "second(s) before retrying..." 242 | ); 243 | await delayed(null, (1 << k) * 1000); 244 | } else { 245 | gitReset(ctx); 246 | throw new Error( 247 | "reached max. number of publish attempts, giving up..." 248 | ); 249 | } 250 | } 251 | } 252 | } 253 | } 254 | }; 255 | 256 | const updateYarnLock = (ctx: CommandCtx) => { 257 | ctx.logger.info("update yarn.lock file"); 258 | execInRepo(ctx, "yarn", "install"); 259 | }; 260 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # This file is generated by running "yarn install" inside your project. 2 | # Manual changes might be lost - proceed with caution! 3 | 4 | __metadata: 5 | version: 8 6 | cacheKey: 10c0 7 | 8 | "@cspotcode/source-map-support@npm:^0.8.0": 9 | version: 0.8.1 10 | resolution: "@cspotcode/source-map-support@npm:0.8.1" 11 | dependencies: 12 | "@jridgewell/trace-mapping": "npm:0.3.9" 13 | checksum: 10c0/05c5368c13b662ee4c122c7bfbe5dc0b613416672a829f3e78bc49a357a197e0218d6e74e7c66cfcd04e15a179acab080bd3c69658c9fbefd0e1ccd950a07fc6 14 | languageName: node 15 | linkType: hard 16 | 17 | "@jridgewell/resolve-uri@npm:^3.0.3": 18 | version: 3.0.7 19 | resolution: "@jridgewell/resolve-uri@npm:3.0.7" 20 | checksum: 10c0/74884ef6dbf0d21067abe93a36ffd76e8e3c957b7b50503e725ed1705f6bfe6e896461cd1f9cb760bd662e0427765d99f3f590540278acb721254474ba1aa1e2 21 | languageName: node 22 | linkType: hard 23 | 24 | "@jridgewell/sourcemap-codec@npm:^1.4.10": 25 | version: 1.4.13 26 | resolution: "@jridgewell/sourcemap-codec@npm:1.4.13" 27 | checksum: 10c0/063b529e052143ef05d69d71655754a5182092f8ed9ee9c50f61c4dd162892614135c6f85f9504aa19052b66e93720a10cadc72bc1b69c56dd15a62c06403c57 28 | languageName: node 29 | linkType: hard 30 | 31 | "@jridgewell/trace-mapping@npm:0.3.9": 32 | version: 0.3.9 33 | resolution: "@jridgewell/trace-mapping@npm:0.3.9" 34 | dependencies: 35 | "@jridgewell/resolve-uri": "npm:^3.0.3" 36 | "@jridgewell/sourcemap-codec": "npm:^1.4.10" 37 | checksum: 10c0/fa425b606d7c7ee5bfa6a31a7b050dd5814b4082f318e0e4190f991902181b4330f43f4805db1dd4f2433fd0ed9cc7a7b9c2683f1deeab1df1b0a98b1e24055b 38 | languageName: node 39 | linkType: hard 40 | 41 | "@thi.ng/api@npm:^8.12.7": 42 | version: 8.12.7 43 | resolution: "@thi.ng/api@npm:8.12.7" 44 | checksum: 10c0/72e6ebd880232af9027ed1bb0560d484e07816d5e1beda89b6e664b5055550c626e43bb64911c92d99ded326ddb4199217f7fd27816b0a5072c88a82c94ebe01 45 | languageName: node 46 | linkType: hard 47 | 48 | "@thi.ng/args@npm:^3.2.1": 49 | version: 3.2.1 50 | resolution: "@thi.ng/args@npm:3.2.1" 51 | dependencies: 52 | "@thi.ng/api": "npm:^8.12.7" 53 | "@thi.ng/checks": "npm:^3.7.23" 54 | "@thi.ng/errors": "npm:^2.5.47" 55 | "@thi.ng/logger": "npm:^3.2.6" 56 | "@thi.ng/strings": "npm:^3.9.27" 57 | "@thi.ng/text-format": "npm:^2.2.46" 58 | checksum: 10c0/f28cc8ddb66c10048f959ec1bbd047af886890e53420b8699cf4bfed27f9f1b1fe73940f2228b4b615731b80b589626148a0e779cda2e07c19dd4bf1f10f0f01 59 | languageName: node 60 | linkType: hard 61 | 62 | "@thi.ng/arrays@npm:^2.13.16": 63 | version: 2.13.16 64 | resolution: "@thi.ng/arrays@npm:2.13.16" 65 | dependencies: 66 | "@thi.ng/api": "npm:^8.12.7" 67 | "@thi.ng/checks": "npm:^3.7.23" 68 | "@thi.ng/compare": "npm:^2.4.33" 69 | "@thi.ng/equiv": "npm:^2.1.97" 70 | "@thi.ng/errors": "npm:^2.5.47" 71 | "@thi.ng/random": "npm:^4.1.32" 72 | checksum: 10c0/5acd82412549743d3cba19c846bd67d427a2c9bc3409aa220c1227c7b1b307e66f57bf55d33b228976029be077e3e6149e97268db38dbfc237fc378fdff319b1 73 | languageName: node 74 | linkType: hard 75 | 76 | "@thi.ng/associative@npm:^7.1.17": 77 | version: 7.1.17 78 | resolution: "@thi.ng/associative@npm:7.1.17" 79 | dependencies: 80 | "@thi.ng/api": "npm:^8.12.7" 81 | "@thi.ng/arrays": "npm:^2.13.16" 82 | "@thi.ng/binary": "npm:^3.4.65" 83 | "@thi.ng/checks": "npm:^3.7.23" 84 | "@thi.ng/dcons": "npm:^3.2.174" 85 | "@thi.ng/equiv": "npm:^2.1.97" 86 | "@thi.ng/object-utils": "npm:^1.2.15" 87 | "@thi.ng/transducers": "npm:^9.6.15" 88 | checksum: 10c0/e7bdb5a777f7b18de7c144d0d42d6038ca0a43e3a7ba11d2f936bc97ef5c59ed2804e17f2207dff838309df33ea56e85b00860b28a2de975a1c53cacb0817fef 89 | languageName: node 90 | linkType: hard 91 | 92 | "@thi.ng/atom@npm:^5.3.48": 93 | version: 5.3.48 94 | resolution: "@thi.ng/atom@npm:5.3.48" 95 | dependencies: 96 | "@thi.ng/api": "npm:^8.12.7" 97 | "@thi.ng/equiv": "npm:^2.1.97" 98 | "@thi.ng/errors": "npm:^2.5.47" 99 | "@thi.ng/paths": "npm:^5.2.26" 100 | checksum: 10c0/4aa476236553e3ec45146f5c207503bfc1236fcbee28d76a522e981458db78e9a6f157d401e1774e8fed9d054d6e7db349abf01ecf862fa6bfae5cd7ecaa959f 101 | languageName: node 102 | linkType: hard 103 | 104 | "@thi.ng/bench@npm:^3.6.34": 105 | version: 3.6.34 106 | resolution: "@thi.ng/bench@npm:3.6.34" 107 | dependencies: 108 | "@thi.ng/api": "npm:^8.12.7" 109 | "@thi.ng/timestamp": "npm:^1.1.26" 110 | checksum: 10c0/a5b5c029bca917d6b21b66f038a7e85d09c6df3454f7fbc1930ecdfb4b3eaea7fa190ff5900c042f66e3c921ed142590c85ddb67764dcb0cdbfe8a42a9d9717a 111 | languageName: node 112 | linkType: hard 113 | 114 | "@thi.ng/binary@npm:^3.4.65": 115 | version: 3.4.65 116 | resolution: "@thi.ng/binary@npm:3.4.65" 117 | dependencies: 118 | "@thi.ng/api": "npm:^8.12.7" 119 | checksum: 10c0/870af552f06ef14fcecf0a137235a538ed8ce05803dd688ff195d6d5ef0bc7aa64dcc9b3b50e4ddf653f68b2353444de68683d5b139f8aaed0097c6fa8207d96 120 | languageName: node 121 | linkType: hard 122 | 123 | "@thi.ng/checks@npm:^3.7.23": 124 | version: 3.7.23 125 | resolution: "@thi.ng/checks@npm:3.7.23" 126 | checksum: 10c0/6a096911e786360f14e35be0255f141fe9e45842957014639c707916662168b2e254ec385dc0ef0f80e3b0e3aedb4685852cc18df333dae326716991b93b5e7d 127 | languageName: node 128 | linkType: hard 129 | 130 | "@thi.ng/compare@npm:^2.4.33": 131 | version: 2.4.33 132 | resolution: "@thi.ng/compare@npm:2.4.33" 133 | dependencies: 134 | "@thi.ng/api": "npm:^8.12.7" 135 | checksum: 10c0/ffe2ae720569ece86e03ad0cb8e9bf9562ea8c029c26fed4d8f3ee7b8616bbe8729cdfb668a517b64aebc28dd0c0d8ba02060efe335788015fee51e1e690120d 136 | languageName: node 137 | linkType: hard 138 | 139 | "@thi.ng/compose@npm:^3.0.44": 140 | version: 3.0.44 141 | resolution: "@thi.ng/compose@npm:3.0.44" 142 | dependencies: 143 | "@thi.ng/api": "npm:^8.12.7" 144 | "@thi.ng/errors": "npm:^2.5.47" 145 | checksum: 10c0/fdcb5e479097f29402c3fef2ff11f3860c00ba9483a9479ede7b19554cee40c952411a2a05642920d8abb828537c4d7d7b28762635d3ef6d7ae6b4985aa31b88 146 | languageName: node 147 | linkType: hard 148 | 149 | "@thi.ng/date@npm:^2.7.69": 150 | version: 2.7.69 151 | resolution: "@thi.ng/date@npm:2.7.69" 152 | dependencies: 153 | "@thi.ng/api": "npm:^8.12.7" 154 | "@thi.ng/checks": "npm:^3.7.23" 155 | "@thi.ng/strings": "npm:^3.9.27" 156 | checksum: 10c0/5fa9a265c048e89f1ecf58516b1fe353ee67130de483d8456fb302b9598b67d3a45b90bd604bfde6eaa86236aa2f977c67c9df8d25dbcf3ae711c4c8e2527e7d 157 | languageName: node 158 | linkType: hard 159 | 160 | "@thi.ng/dcons@npm:^3.2.174": 161 | version: 3.2.174 162 | resolution: "@thi.ng/dcons@npm:3.2.174" 163 | dependencies: 164 | "@thi.ng/api": "npm:^8.12.7" 165 | "@thi.ng/checks": "npm:^3.7.23" 166 | "@thi.ng/compare": "npm:^2.4.33" 167 | "@thi.ng/equiv": "npm:^2.1.97" 168 | "@thi.ng/errors": "npm:^2.5.47" 169 | "@thi.ng/random": "npm:^4.1.32" 170 | "@thi.ng/transducers": "npm:^9.6.15" 171 | checksum: 10c0/e804d3d5404edf4016d34f987fd84603a045fa1e3921234fd4cdf74abe4b3e6d2b213e8896452cb105a1c84f4d1cf843b19e049d78174156f4b8f9e699b572e2 172 | languageName: node 173 | linkType: hard 174 | 175 | "@thi.ng/dgraph@npm:^2.1.183": 176 | version: 2.1.183 177 | resolution: "@thi.ng/dgraph@npm:2.1.183" 178 | dependencies: 179 | "@thi.ng/api": "npm:^8.12.7" 180 | "@thi.ng/associative": "npm:^7.1.17" 181 | "@thi.ng/equiv": "npm:^2.1.97" 182 | "@thi.ng/errors": "npm:^2.5.47" 183 | "@thi.ng/transducers": "npm:^9.6.15" 184 | checksum: 10c0/a743dad7b1a7004cdaa96001d226fc79136edc864a07422573f17afe6826607549bbada32758ef7e8e0ccf7a79b4000552754a260f7b029f42b63d89412a7559 185 | languageName: node 186 | linkType: hard 187 | 188 | "@thi.ng/equiv@npm:^2.1.97": 189 | version: 2.1.97 190 | resolution: "@thi.ng/equiv@npm:2.1.97" 191 | checksum: 10c0/8bae672154c60e7f8b0ea2dd96d87f8dff97160a6ada06a523644e4e0f5067362937bb995b4d33cb45e35599407a7e52ebb14a54d71d316e0824187ac59814a7 192 | languageName: node 193 | linkType: hard 194 | 195 | "@thi.ng/errors@npm:^2.5.47": 196 | version: 2.5.47 197 | resolution: "@thi.ng/errors@npm:2.5.47" 198 | checksum: 10c0/2b0782dfc6fdc63f534c716c093a265411bd276c728a1b46ca7dc78119cb72d157c8a77992d9440c750ce21e825e4d4441e2a143d512070a29bbafa35f8c5179 199 | languageName: node 200 | linkType: hard 201 | 202 | "@thi.ng/file-io@npm:^2.2.16": 203 | version: 2.2.16 204 | resolution: "@thi.ng/file-io@npm:2.2.16" 205 | dependencies: 206 | "@thi.ng/api": "npm:^8.12.7" 207 | "@thi.ng/checks": "npm:^3.7.23" 208 | "@thi.ng/hex": "npm:^2.3.85" 209 | "@thi.ng/logger": "npm:^3.2.6" 210 | "@thi.ng/random": "npm:^4.1.32" 211 | checksum: 10c0/8a44c0a3a58c7b24dc2755e0f6cbfc3bd7a77f828d1d609c5f185aba6b19a3e362d96ed6c144d6faf6584c1f8d2792c89079914622734a379d3855b9a8ae3083 212 | languageName: node 213 | linkType: hard 214 | 215 | "@thi.ng/hex@npm:^2.3.85": 216 | version: 2.3.85 217 | resolution: "@thi.ng/hex@npm:2.3.85" 218 | checksum: 10c0/1ed9da94fb34cb185029ecd102cd54116bb70838fd9d6a72169ac0129c173b01ab7fb3e4d657377c85bd3a2856cfa0bed1310153022693047c25c7eb3c99fc8b 219 | languageName: node 220 | linkType: hard 221 | 222 | "@thi.ng/logger@npm:^3.2.6": 223 | version: 3.2.6 224 | resolution: "@thi.ng/logger@npm:3.2.6" 225 | checksum: 10c0/395428ec1e07aec211b8f6032c1a9b91dd30520f0c91bc6da17ead3cae95e7ef238a078afc670a373e6dda93e4b2c68e5f47150748283358f0d535cf202d42ff 226 | languageName: node 227 | linkType: hard 228 | 229 | "@thi.ng/math@npm:^5.13.4": 230 | version: 5.13.4 231 | resolution: "@thi.ng/math@npm:5.13.4" 232 | dependencies: 233 | "@thi.ng/api": "npm:^8.12.7" 234 | checksum: 10c0/bed2e2963a0a5ad90192602ffdb4bc8183601310d000c5a9d922e91c2d119e3d60596f9e655188f6cb97dc78b06b7d124a4d014a9f358e640b52cfee7f8ace89 235 | languageName: node 236 | linkType: hard 237 | 238 | "@thi.ng/memoize@npm:^4.0.31": 239 | version: 4.0.31 240 | resolution: "@thi.ng/memoize@npm:4.0.31" 241 | dependencies: 242 | "@thi.ng/api": "npm:^8.12.7" 243 | checksum: 10c0/3e14cefe2d7af83fc45b61510d1a08571c228a526790c6de371a607f2efb6c165ede068573f8a25420737c61ca056913e8788410e59aa4b50760001f21904242 244 | languageName: node 245 | linkType: hard 246 | 247 | "@thi.ng/monopub@workspace:.": 248 | version: 0.0.0-use.local 249 | resolution: "@thi.ng/monopub@workspace:." 250 | dependencies: 251 | "@thi.ng/api": "npm:^8.12.7" 252 | "@thi.ng/args": "npm:^3.2.1" 253 | "@thi.ng/bench": "npm:^3.6.34" 254 | "@thi.ng/checks": "npm:^3.7.23" 255 | "@thi.ng/compare": "npm:^2.4.33" 256 | "@thi.ng/date": "npm:^2.7.69" 257 | "@thi.ng/dgraph": "npm:^2.1.183" 258 | "@thi.ng/errors": "npm:^2.5.47" 259 | "@thi.ng/file-io": "npm:^2.2.16" 260 | "@thi.ng/logger": "npm:^3.2.6" 261 | "@thi.ng/rstream": "npm:^9.3.4" 262 | "@thi.ng/strings": "npm:^3.9.27" 263 | "@thi.ng/system": "npm:^3.1.82" 264 | "@thi.ng/text-format": "npm:^2.2.46" 265 | "@thi.ng/transducers": "npm:^9.6.15" 266 | "@types/node": "npm:^24.10.0" 267 | dotenv: "npm:^17.2.3" 268 | ts-node: "npm:^10.9.2" 269 | typescript: "npm:^5.9.3" 270 | bin: 271 | notes: bin/monopub 272 | languageName: unknown 273 | linkType: soft 274 | 275 | "@thi.ng/object-utils@npm:^1.2.15": 276 | version: 1.2.15 277 | resolution: "@thi.ng/object-utils@npm:1.2.15" 278 | dependencies: 279 | "@thi.ng/api": "npm:^8.12.7" 280 | "@thi.ng/checks": "npm:^3.7.23" 281 | checksum: 10c0/8649a2d9df72bb9fa9145af3448a54522422368ab807025f735629bb0d5d33212f9b819e75394689b5a7dc6f93dfaeb60e04af5ff2606b3677d5b8472776f93c 282 | languageName: node 283 | linkType: hard 284 | 285 | "@thi.ng/paths@npm:^5.2.26": 286 | version: 5.2.26 287 | resolution: "@thi.ng/paths@npm:5.2.26" 288 | dependencies: 289 | "@thi.ng/api": "npm:^8.12.7" 290 | "@thi.ng/checks": "npm:^3.7.23" 291 | "@thi.ng/errors": "npm:^2.5.47" 292 | checksum: 10c0/e7fe8a26ad21c00591eab7505728b123adc4bc6b4697c6efd68fd60f63a8497e55676da84b9ae4709aa79e4dc20890cb732d74d828248e8529e1b1f7bd33aaf2 293 | languageName: node 294 | linkType: hard 295 | 296 | "@thi.ng/random@npm:^4.1.32": 297 | version: 4.1.32 298 | resolution: "@thi.ng/random@npm:4.1.32" 299 | dependencies: 300 | "@thi.ng/api": "npm:^8.12.7" 301 | "@thi.ng/errors": "npm:^2.5.47" 302 | checksum: 10c0/69e853b5e20ded7ecc688e1f5fa846b7acc3fb9cabcc641f062c3d3735ece7c1a7d110f754035e2d15f3902c5d2204f3866fa8ff2c39ccfb8ea5ed3675985671 303 | languageName: node 304 | linkType: hard 305 | 306 | "@thi.ng/rstream@npm:^9.3.4": 307 | version: 9.3.4 308 | resolution: "@thi.ng/rstream@npm:9.3.4" 309 | dependencies: 310 | "@thi.ng/api": "npm:^8.12.7" 311 | "@thi.ng/arrays": "npm:^2.13.16" 312 | "@thi.ng/associative": "npm:^7.1.17" 313 | "@thi.ng/atom": "npm:^5.3.48" 314 | "@thi.ng/checks": "npm:^3.7.23" 315 | "@thi.ng/errors": "npm:^2.5.47" 316 | "@thi.ng/logger": "npm:^3.2.6" 317 | "@thi.ng/transducers": "npm:^9.6.15" 318 | checksum: 10c0/5b51a5142f6e4913cabeaee740e594f61de32d99b106f5837525a5548887ac471fa13fd243f6eb26779535f7061e2a868ff2fc6c62acb711bf7f01c021f4e8da 319 | languageName: node 320 | linkType: hard 321 | 322 | "@thi.ng/strings@npm:^3.9.27": 323 | version: 3.9.27 324 | resolution: "@thi.ng/strings@npm:3.9.27" 325 | dependencies: 326 | "@thi.ng/api": "npm:^8.12.7" 327 | "@thi.ng/errors": "npm:^2.5.47" 328 | "@thi.ng/hex": "npm:^2.3.85" 329 | "@thi.ng/memoize": "npm:^4.0.31" 330 | checksum: 10c0/2839a2fcbe7021e2ae6554ffb259d75a4e774b640844e8e5d7867b5b3259f08f464bad599fe6c920878ef5c482ccdc92dc5f841545c796954825ab594608d85b 331 | languageName: node 332 | linkType: hard 333 | 334 | "@thi.ng/system@npm:^3.1.82": 335 | version: 3.1.82 336 | resolution: "@thi.ng/system@npm:3.1.82" 337 | dependencies: 338 | "@thi.ng/api": "npm:^8.12.7" 339 | "@thi.ng/dgraph": "npm:^2.1.183" 340 | "@thi.ng/logger": "npm:^3.2.6" 341 | checksum: 10c0/55330923acea6eb411e6fe59c3793d464722e8e57794be4c3ea883d445ddea6131f4df97c810b60d079fcaf2336e2b403f36e447b750ece99c5550345b75eb19 342 | languageName: node 343 | linkType: hard 344 | 345 | "@thi.ng/text-format@npm:^2.2.46": 346 | version: 2.2.46 347 | resolution: "@thi.ng/text-format@npm:2.2.46" 348 | dependencies: 349 | "@thi.ng/api": "npm:^8.12.7" 350 | "@thi.ng/hex": "npm:^2.3.85" 351 | "@thi.ng/memoize": "npm:^4.0.31" 352 | checksum: 10c0/52505084851a72c1a52126ca393efe867582e2f9a10a2a53e977a82f9ff39cb9f469a50dbdbc0e6a8f0e5f9d090f49cb62561f240c49b2e9c7e1b345821b8d28 353 | languageName: node 354 | linkType: hard 355 | 356 | "@thi.ng/timestamp@npm:^1.1.26": 357 | version: 1.1.26 358 | resolution: "@thi.ng/timestamp@npm:1.1.26" 359 | checksum: 10c0/ecf613fb3a8053b05a161f484773876af673c437954ef1c63009084ab02db4b2b5e310df1937e046939690f79cdf3958aa4bd014e1ce290906b96cf32ae303cc 360 | languageName: node 361 | linkType: hard 362 | 363 | "@thi.ng/transducers@npm:^9.6.15": 364 | version: 9.6.15 365 | resolution: "@thi.ng/transducers@npm:9.6.15" 366 | dependencies: 367 | "@thi.ng/api": "npm:^8.12.7" 368 | "@thi.ng/arrays": "npm:^2.13.16" 369 | "@thi.ng/checks": "npm:^3.7.23" 370 | "@thi.ng/compare": "npm:^2.4.33" 371 | "@thi.ng/compose": "npm:^3.0.44" 372 | "@thi.ng/errors": "npm:^2.5.47" 373 | "@thi.ng/math": "npm:^5.13.4" 374 | "@thi.ng/random": "npm:^4.1.32" 375 | "@thi.ng/timestamp": "npm:^1.1.26" 376 | checksum: 10c0/c14e600b0410b65f03ac8cd2ca53b5875763f3b894ddf1f45c217fcb2122900bfdb429468d36248b1a66be7e86f7a748d7a1a4eacc73bc8736fe69278f36904f 377 | languageName: node 378 | linkType: hard 379 | 380 | "@tsconfig/node10@npm:^1.0.7": 381 | version: 1.0.8 382 | resolution: "@tsconfig/node10@npm:1.0.8" 383 | checksum: 10c0/d400f7b5c02acd74620f892c0f41cea39e7c1b5f7f272ad6f127f4b1fba23346b2d8e30d272731a733675494145f6aa74f9faf050390c034c7c553123ab979b3 384 | languageName: node 385 | linkType: hard 386 | 387 | "@tsconfig/node12@npm:^1.0.7": 388 | version: 1.0.9 389 | resolution: "@tsconfig/node12@npm:1.0.9" 390 | checksum: 10c0/fc1fb68a89d8a641953036d23d95fe68f69f74d37a499db20791b09543ad23afe7ae9ee0840eea92dd470bdcba69eef6f1ed3fe90ba64d763bcd3f738e364597 391 | languageName: node 392 | linkType: hard 393 | 394 | "@tsconfig/node14@npm:^1.0.0": 395 | version: 1.0.1 396 | resolution: "@tsconfig/node14@npm:1.0.1" 397 | checksum: 10c0/abd4e27d9ad712e1e229716a3dbf35d5cbb580d624a82d67414e7606cefd85d502e58800a2ab930d46a428fcfcb199436283b1a88e47d738ca1a5f7fd022ee74 398 | languageName: node 399 | linkType: hard 400 | 401 | "@tsconfig/node16@npm:^1.0.2": 402 | version: 1.0.2 403 | resolution: "@tsconfig/node16@npm:1.0.2" 404 | checksum: 10c0/d402706562444a173d48810d13fdf866c78f1b876ed8962eeac6c7cddf4e29e8aaa06dc28093219e3e9eb6316799cf4d9a7acba62c6a4e215ee0c94d83f9081f 405 | languageName: node 406 | linkType: hard 407 | 408 | "@types/node@npm:^24.10.0": 409 | version: 24.10.0 410 | resolution: "@types/node@npm:24.10.0" 411 | dependencies: 412 | undici-types: "npm:~7.16.0" 413 | checksum: 10c0/f82ed7194e16f5590ef7afdc20c6d09068c76d50278b485ede8f0c5749683536e3064ffa8def8db76915196afb3724b854aa5723c64d6571b890b14492943b46 414 | languageName: node 415 | linkType: hard 416 | 417 | "acorn-walk@npm:^8.1.1": 418 | version: 8.2.0 419 | resolution: "acorn-walk@npm:8.2.0" 420 | checksum: 10c0/dbe92f5b2452c93e960c5594e666dd1fae141b965ff2cb4a1e1d0381e3e4db4274c5ce4ffa3d681a86ca2a8d4e29d5efc0670a08e23fd2800051ea387df56ca2 421 | languageName: node 422 | linkType: hard 423 | 424 | "acorn@npm:^8.4.1": 425 | version: 8.7.0 426 | resolution: "acorn@npm:8.7.0" 427 | bin: 428 | acorn: bin/acorn 429 | checksum: 10c0/8168e567c2f0b9fb7a418d2651b4b614326a0814b4937ebddee0f5e5e25ddd6320aec0c20d3a67efd97a02d836cc7f9e5c84befe3daeeea68ed89a48ee8f7a5d 430 | languageName: node 431 | linkType: hard 432 | 433 | "arg@npm:^4.1.0": 434 | version: 4.1.3 435 | resolution: "arg@npm:4.1.3" 436 | checksum: 10c0/070ff801a9d236a6caa647507bdcc7034530604844d64408149a26b9e87c2f97650055c0f049abd1efc024b334635c01f29e0b632b371ac3f26130f4cf65997a 437 | languageName: node 438 | linkType: hard 439 | 440 | "create-require@npm:^1.1.0": 441 | version: 1.1.1 442 | resolution: "create-require@npm:1.1.1" 443 | checksum: 10c0/157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 444 | languageName: node 445 | linkType: hard 446 | 447 | "diff@npm:^4.0.1": 448 | version: 4.0.2 449 | resolution: "diff@npm:4.0.2" 450 | checksum: 10c0/81b91f9d39c4eaca068eb0c1eb0e4afbdc5bb2941d197f513dd596b820b956fef43485876226d65d497bebc15666aa2aa82c679e84f65d5f2bfbf14ee46e32c1 451 | languageName: node 452 | linkType: hard 453 | 454 | "dotenv@npm:^17.2.3": 455 | version: 17.2.3 456 | resolution: "dotenv@npm:17.2.3" 457 | checksum: 10c0/c884403209f713214a1b64d4d1defa4934c2aa5b0002f5a670ae298a51e3c3ad3ba79dfee2f8df49f01ae74290fcd9acdb1ab1d09c7bfb42b539036108bb2ba0 458 | languageName: node 459 | linkType: hard 460 | 461 | "make-error@npm:^1.1.1": 462 | version: 1.3.6 463 | resolution: "make-error@npm:1.3.6" 464 | checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f 465 | languageName: node 466 | linkType: hard 467 | 468 | "ts-node@npm:^10.9.2": 469 | version: 10.9.2 470 | resolution: "ts-node@npm:10.9.2" 471 | dependencies: 472 | "@cspotcode/source-map-support": "npm:^0.8.0" 473 | "@tsconfig/node10": "npm:^1.0.7" 474 | "@tsconfig/node12": "npm:^1.0.7" 475 | "@tsconfig/node14": "npm:^1.0.0" 476 | "@tsconfig/node16": "npm:^1.0.2" 477 | acorn: "npm:^8.4.1" 478 | acorn-walk: "npm:^8.1.1" 479 | arg: "npm:^4.1.0" 480 | create-require: "npm:^1.1.0" 481 | diff: "npm:^4.0.1" 482 | make-error: "npm:^1.1.1" 483 | v8-compile-cache-lib: "npm:^3.0.1" 484 | yn: "npm:3.1.1" 485 | peerDependencies: 486 | "@swc/core": ">=1.2.50" 487 | "@swc/wasm": ">=1.2.50" 488 | "@types/node": "*" 489 | typescript: ">=2.7" 490 | peerDependenciesMeta: 491 | "@swc/core": 492 | optional: true 493 | "@swc/wasm": 494 | optional: true 495 | bin: 496 | ts-node: dist/bin.js 497 | ts-node-cwd: dist/bin-cwd.js 498 | ts-node-esm: dist/bin-esm.js 499 | ts-node-script: dist/bin-script.js 500 | ts-node-transpile-only: dist/bin-transpile.js 501 | ts-script: dist/bin-script-deprecated.js 502 | checksum: 10c0/5f29938489f96982a25ba650b64218e83a3357d76f7bede80195c65ab44ad279c8357264639b7abdd5d7e75fc269a83daa0e9c62fd8637a3def67254ecc9ddc2 503 | languageName: node 504 | linkType: hard 505 | 506 | "typescript@npm:^5.9.3": 507 | version: 5.9.3 508 | resolution: "typescript@npm:5.9.3" 509 | bin: 510 | tsc: bin/tsc 511 | tsserver: bin/tsserver 512 | checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 513 | languageName: node 514 | linkType: hard 515 | 516 | "typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": 517 | version: 5.9.3 518 | resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" 519 | bin: 520 | tsc: bin/tsc 521 | tsserver: bin/tsserver 522 | checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 523 | languageName: node 524 | linkType: hard 525 | 526 | "undici-types@npm:~7.16.0": 527 | version: 7.16.0 528 | resolution: "undici-types@npm:7.16.0" 529 | checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a 530 | languageName: node 531 | linkType: hard 532 | 533 | "v8-compile-cache-lib@npm:^3.0.1": 534 | version: 3.0.1 535 | resolution: "v8-compile-cache-lib@npm:3.0.1" 536 | checksum: 10c0/bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 537 | languageName: node 538 | linkType: hard 539 | 540 | "yn@npm:3.1.1": 541 | version: 3.1.1 542 | resolution: "yn@npm:3.1.1" 543 | checksum: 10c0/0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 544 | languageName: node 545 | linkType: hard 546 | --------------------------------------------------------------------------------