diff --git a/.eslintrc.base.js b/.eslintrc.base.js new file mode 120000 index 0000000..d0f90db --- /dev/null +++ b/.eslintrc.base.js @@ -0,0 +1 @@ +../../open-source-template/.eslintrc.base.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index a175c7d..3544413 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,9 @@ "use strict"; -module.exports = require("../../.eslintrc.base.js")(__dirname, { +module.exports = require("./.eslintrc.base.js")(__dirname, { "import/no-extraneous-dependencies": "error", - "lodash/import-scope": ["error", "method"], + "@typescript-eslint/explicit-function-return-type": [ + "error", + { allowExpressions: true, allowedNames: ["configure"] }, + ], + "lodash/import-scope": ["error", "method"] }); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 120000 index 0000000..d038e82 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1 @@ +../../../../open-source-template/.github/workflows/ci.yml \ No newline at end of file diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 120000 index 0000000..8dc4b58 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1 @@ +../../../../open-source-template/.github/workflows/semgrep.yml \ No newline at end of file diff --git a/.gitignore.public b/.gitignore.public new file mode 120000 index 0000000..158e2ef --- /dev/null +++ b/.gitignore.public @@ -0,0 +1 @@ +../../open-source-template/.gitignore.public \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 120000 index 0000000..5ca9cee --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +../../open-source-template/.npmignore \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 120000 index 0000000..b0a0dcf --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +../../open-source-template/.npmrc \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 120000 index 0000000..7ba5961 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1 @@ +../../../open-source-template/.vscode/extensions.json \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 120000 index 0000000..31880c3 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1 @@ +../../../open-source-template/.vscode/tasks.json \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 6f4afaf..0000000 --- a/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2023 Mango Technologies, Inc. DBA ClickUp - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/LICENSE b/LICENSE new file mode 120000 index 0000000..c7b9704 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +../../open-source-template/LICENSE \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..564ca75 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# @clickup/pg-mig: PostgreSQL schema migration tool with micro-sharding and clustering support + +See also [Full API documentation](https://github.com/clickup/pg-mig/blob/master/docs/modules.md). + +![CI run](https://github.com/clickup/pg-mig/actions/workflows/ci.yml/badge.svg?branch=main) + +The tool allows to create a PostgreSQL database schema (with tables, indexes, +sequences, functions etc.) and apply it consistently across multiple PG hosts +(even more, across multiple micro-shard schemas on multiple hosts). The behavior +is transactional per each micro-shard per version ("all or nothing"). + +In other words, `pg-mig` helps to keep your database clusters' schemas identical +(each micro-shard schema will have exactly the same DDL structure as any other +schema on all other PG hosts). + +# Usage + +``` +pg-mig + [--migdir=path/to/my-migrations/directory] + [--hosts=master1,master2,...] + [--port=5432] + [--user=user-which-can-apply-ddl] + [--pass=password] + [--db=my-database-name] + [--undo=20191107201239.my-migration-name.sh] + [--make=my-migration-name@sh] + [--parallelism=8] + [--dry] + [--list] + [--ci] +``` + +All of the arguments are optional: the tool tries to use `PGHOST`, `PGPORT`, +`PGUSER`, `PGPASSWORD`, `PGDATABASE` environment variables which are standard +for e.g. `psql`. + +It also uses `PGMIGDIR` environment variable as a default value for `--migdir` +option. + +When running in default mode, `pg-mig` tool reads (in order) the migration +versions `*.up.sql` files from the migration directory and applies them all of +the hostnames passed (of course, checking whether it has already been applied +before or not). See below for more details. + +## Migration Version File Format + +The migration version file name has the following format, examples: + +``` +20191107201239.add-table-abc.sh0000.up.sql +20191107201239.add-table-abc.sh0000.dn.sql +20231317204837.some-other-name.sh.up.sql +20231317204837.some-other-name.sh.dn.sql +20231203493744.anything-works.public.up.sql +20231203493744.anything-works.public.dn.sql +``` + +Here, + +- the 1st part is a UTC timestamp when the migration version file was created, +- the 2nd part is a descriptive name of the migration (can be arbitrary), +- the 3rd part is the "PostgreSQL schema name prefix" (micro-shard name prefix) +- the 4th part is either "up" ("up" migration) or "dn" ("down" migration). + Up-migrations roll the database schema version forward, and down-migrations + allow to undo the changes. + +It is the responsibility of the user to create up- and down-migration SQL files. +Basically, the user provides DDL SQL queries on how to roll the database schema +forward and how to roll it backward. + +You can use any `psql`-specific instructions in `*.sql` files: they are fed to +`psql` tool directly. E.g. you can use environment variables, `\echo`, `\ir` for +inclusion etc. See [psql +documentation](https://www.postgresql.org/docs/current/app-psql.html) for +details. + +## Applying the Migrations + +Each migration version will be applied (in order) to all PG schemas (aka +micro-shards) on all hosts whose names start from the provided prefix (if +multiple migration files match some schema, then only the file with the longest +prefix will be used; in the above example, prefix "sh" effectively works as "sh* +except sh0000" wildcard). + +The main idea is that, if the migration file application succeeds, then it will +be remembered on the corresponding PG host, in the corresponding schema +(micro-shard) itself. So next time when you run the tool, it will understand +that the migration version has already been applied, and won't try to apply it +again. + +When the tool runs, it prints a live-updating progress, which migration version +file is in progress on which PG host in which schema (micro-shard). In the end, +it prints the final versions map across all of the hosts and schemas. + +## Undoing the Migrations + +If `--undo` argument is used, then the tool will try to run the down-migration +for the the corresponding version everywhere. If it succeeds, then it will +remember that fact on the corresponding PG host in the corresponding schema. +Only the very latest migration version applied can be undone. + +Undoing migrations in production is not recommended (since the code which uses +the database may rely on its new structure), although you can use it of course. +The main use case for undoing the migrations is while development: you may want +to test your DDL statements multiple times, or you may pull from Git and get +someone else's migration before yours, so you'll need to undo your migration and +recreate its files. + +## Creating the New Migration Files + +If `--make` argument is used, `pg-mig` creates a new pair of empty files in the +migration directory. E.g. if you run: + +``` +pg-mig --migdir=my-dir --make=my-migration-name@sh +``` + +then it will create a pair of files which looks like +`my-dir/20231203493744.my-migration-name.sh.up.sql` and +`my-dir/20231203493744.my-migration-name.sh.dn.sql` which you can edit further. + +New migration version files can only be appended in the end. If `pg-mig` detects +that you try to apply migrations which conflict with the existing migration +versions remembered in the database, it will print the error and refuse to +continue. This is similar to "fast-forward" mode in Git. diff --git a/SECURITY.md b/SECURITY.md new file mode 120000 index 0000000..47a77e3 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1 @@ +../../open-source-template/SECURITY.md \ No newline at end of file diff --git a/internal/clean.sh b/internal/clean.sh new file mode 120000 index 0000000..c0d9bc6 --- /dev/null +++ b/internal/clean.sh @@ -0,0 +1 @@ +../../../open-source-template/internal/clean.sh \ No newline at end of file diff --git a/internal/deploy.sh b/internal/deploy.sh new file mode 120000 index 0000000..97be1f0 --- /dev/null +++ b/internal/deploy.sh @@ -0,0 +1 @@ +../../../open-source-template/internal/deploy.sh \ No newline at end of file diff --git a/internal/docs.sh b/internal/docs.sh new file mode 120000 index 0000000..2e6485b --- /dev/null +++ b/internal/docs.sh @@ -0,0 +1 @@ +../../../open-source-template/internal/docs.sh \ No newline at end of file diff --git a/internal/lint.sh b/internal/lint.sh new file mode 120000 index 0000000..4c2127b --- /dev/null +++ b/internal/lint.sh @@ -0,0 +1 @@ +../../../open-source-template/internal/lint.sh \ No newline at end of file diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 7ba2d4c..0000000 --- a/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -"use strict"; - -module.exports = { - ...require("../../jest.config.base")(), -}; diff --git a/jest.config.js b/jest.config.js new file mode 120000 index 0000000..e5a13fd --- /dev/null +++ b/jest.config.js @@ -0,0 +1 @@ +../../open-source-template/jest.config.js \ No newline at end of file diff --git a/package.json b/package.json index c1e273d..bccc46c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "pg-mig", - "description": "Postgres database migration tool with multi-schema (sharding) support", - "version": "2.10.291", + "name": "@clickup/pg-mig", + "description": "PostgreSQL schema migration tool with micro-sharding and clustering support", + "version": "2.10.296", "license": "MIT", "main": "dist/cli.js", "types": "dist/cli.d.ts", @@ -9,10 +9,15 @@ "pg-mig": "./dist/cli.js" }, "scripts": { - "build": "tsc.sh", - "dev": "tight-loop.sh tsc.sh --watch", - "lint": "lint.sh", - "test": "test.sh" + "build": "tsc", + "dev": "tsc --watch --preserveWatchOutput", + "lint": "bash internal/lint.sh", + "test": "jest", + "docs": "bash internal/docs.sh", + "clean": "bash internal/clean.sh", + "copy-package-to-public-dir": "copy-package-to-public-dir.sh", + "backport-package-from-public-dir": "backport-package-from-public-dir.sh", + "deploy": "bash internal/deploy.sh" }, "dependencies": { "await-semaphore": "^0.1.3", @@ -26,13 +31,36 @@ "table-layout": "^1.0.2" }, "devDependencies": { - "@types/shell-quote": "^1.7.1" + "@types/jest": "^29.5.5", + "@types/lodash": "^4.14.175", + "@types/minimist": "^1.2.2", + "@types/node": "^20.4.1", + "@types/pg": "^8.6.1", + "@types/prompts": "^2.4.0", + "@types/shell-quote": "^1.7.1", + "@types/sprintf-js": "^1.1.2", + "@typescript-eslint/eslint-plugin": "^5.59.6", + "@typescript-eslint/parser": "^5.59.6", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-lodash": "^7.4.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-typescript-enum": "^2.1.0", + "eslint-plugin-typescript-sort-keys": "^2.3.0", + "eslint-plugin-unused-imports": "^2.0.0", + "eslint": "^8.40.0", + "jest": "^29.7.0", + "prettier": "3.2.1", + "ts-jest": "^29.1.1", + "typedoc-plugin-markdown": "^3.16.0", + "typedoc-plugin-merge-modules": "^5.1.0", + "typedoc": "^0.25.2", + "typescript": "^5.2.2" }, "repository": { "type": "git", - "url": "git://github.com/time-loop/github-packages" - }, - "publishConfig": { - "registry": "https://npm.pkg.github.com/" + "url": "git://github.com/clickup/pg-mig" } } diff --git a/src/cli.ts b/src/cli.ts index 3c81767..54c159f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -2,11 +2,13 @@ import { basename } from "path"; import sortBy from "lodash/sortBy"; import throttle from "lodash/throttle"; import logUpdate from "log-update"; -import { Dest } from "./Dest"; -import { Grid } from "./Grid"; -import type { Chain } from "./Patch"; -import { Patch } from "./Patch"; -import { Registry } from "./Registry"; +import { Dest } from "./internal/Dest"; +import { Grid } from "./internal/Grid"; +import { Args } from "./internal/helpers/Args"; +import { makeMigration } from "./internal/helpers/makeMigration"; +import type { Chain } from "./internal/Patch"; +import { Patch } from "./internal/Patch"; +import { Registry } from "./internal/Registry"; import { printError, printSuccess, @@ -14,65 +16,94 @@ import { renderGrid, renderLatestVersions, renderPatchSummary, -} from "./render"; -import { Args } from "./utils/Args"; -import { makeMigration } from "./utils/makeMigration"; - -// Examples: -// yarn db:migrate --make=space_members_add_email@sh0000 -// yarn db:migrate --undo 20191107201239.space_members.sh0000 -// yarn db:migrate --undo 20191107201238.space_users_remove.sh - -export async function main() { +} from "./internal/render"; + +/** + * CLI tool entry point. This function is run when `pg-mig` is called from the + * command line. Accepts parameters from process.argv. See `migrate()` for + * option names. + * + * If no options are passed, uses `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, + * `PGDATABASE` environment variables which are standard for e.g. `psql`. + * + * You can pass multiple hosts separated by comma or semicolon. + * + * Examples: + * ``` + * pg-mig --make=my-migration-name@sh + * pg-mig --make=other-migration-name@sh0000 + * pg-mig --undo 20191107201239.my-migration-name.sh + * pg-mig + * ``` + */ +export async function main(): Promise { const args = new Args( process.argv, // Notice that we use --migdir and not --dir, because @mapbox/node-pre-gyp // used by bcrypt conflicts with --dir option. [ + "migdir", "hosts", "port", "user", "pass", "db", - "migdir", - "parallelism", "undo", "make", + "parallelism", ], ["dry", "ci", "list"], ); return migrate({ + migDir: args.get("migdir", process.env["PGMIGDIR"]), hosts: args - .get("hosts", process.env.PGHOST || "localhost") + .get("hosts", process.env["PGHOST"] || "127.0.0.1") .split(/[\s,;]+/), - port: parseInt(args.get("port", process.env.PGPORT || "5432")), - user: args.get("user", process.env.PGUSER || ""), - pass: args.get("pass", process.env.PGPASSWORD || ""), - db: args.get("db", process.env.PGDATABASE), - undo: args.get("undo", "empty"), + port: parseInt(args.get("port", process.env["PGPORT"] || "5432")), + user: args.get("user", process.env["PGUSER"] || ""), + pass: args.get("pass", process.env["PGPASSWORD"] || ""), + db: args.get("db", process.env["PGDATABASE"]), + undo: args.getOptional("undo"), + make: args.getOptional("make"), + parallelism: parseInt(args.get("parallelism", "0")) || undefined, dry: args.flag("dry"), list: args.flag("list"), - make: args.get("make", ""), - migDir: args.get("migdir"), - parallelism: parseInt(args.get("parallelism", "0")) || 10, ci: args.flag("ci"), }); } +/** + * Similar to main(), but accepts options explicitly, not from process.argv. + * This function is meant to be called from other tools. + */ export async function migrate(options: { + /** The directory the migration versions are loaded from. */ + migDir: string; + /** List of PostgreSQL master hostnames. The migration versions in `migDir` + * will be applied to all of them. */ hosts: string[]; + /** PostgreSQL port on each hosts. */ port: number; + /** PostgreSQL user on each host. */ user: string; + /** PostgreSQL password on each host. */ pass: string; + /** PostgreSQL database name on each host. */ db: string; - undo: string; - dry: boolean; - list: boolean; - make: string; - migDir: string; - parallelism: number; - ci: boolean; -}) { + /** How many schemas to process in parallel (defaults to 10). */ + parallelism?: number; + /** If passed, switches the action to undo the provided migration version. */ + undo?: string; + /** If passed, switches the action to create a new migration version. */ + make?: string; + /** If true, prints what it plans to do, but doesn't change anything. */ + dry?: boolean; + /** Lists all versions in `migDir`. */ + list?: boolean; + /** If true, then doesn't use logUpdate() and doesn't replace lines; instead, + * prints logs to stdout line by line. */ + ci?: boolean; +}): Promise { const hostDests = options.hosts.map( (host) => new Dest( @@ -88,11 +119,11 @@ export async function migrate(options: { printText(`Running on ${options.hosts}:${options.port} ${options.db}`); - if (options.make) { + if (options.make !== undefined) { // example: create_table_x@sh const [migrationName, schemaPrefix] = options.make.split("@"); - const usage = "Format: --make=migration_name@schema_prefix"; + if (!migrationName?.match(/^[a-z0-9_]+$/)) { printError("migration_name is missing or incorrect"); printText(usage); @@ -143,9 +174,7 @@ export async function migrate(options: { return false; } - const patch = new Patch(hostDests, registry, { - undo: options.undo !== "empty" ? options.undo : undefined, - }); + const patch = new Patch(hostDests, registry, { undo: options.undo }); const chains = await patch.getChains(); const [summary, hasWork] = renderPatchSummary(chains); @@ -183,7 +212,12 @@ export async function migrate(options: { ], })) : []; - const grid = new Grid(chains, options.parallelism, beforeChains, afterChains); + const grid = new Grid( + chains, + options.parallelism ?? 10, + beforeChains, + afterChains, + ); const success = await grid.run( throttle(() => { diff --git a/src/Dest.ts b/src/internal/Dest.ts similarity index 94% rename from src/Dest.ts rename to src/internal/Dest.ts index 0bf33df..c6cb8c1 100644 --- a/src/Dest.ts +++ b/src/internal/Dest.ts @@ -20,7 +20,7 @@ export class Dest { /** * Returns a Dest switched to a different schema. */ - createSchemaDest(schema: string) { + createSchemaDest(schema: string): Dest { return new Dest( this.host, this.port, @@ -34,7 +34,7 @@ export class Dest { /** * Returns a human-readable representation of the dest. */ - toString() { + toString(): string { return this.host + ":" + this.schema; } @@ -46,7 +46,7 @@ export class Dest { file: string, newVersions: string[] | null, onOut: (proc: Psql) => void = () => {}, - ) { + ): Promise { const psql = new Psql( this, dirname(file), @@ -97,7 +97,7 @@ export class Dest { /** * Returns all the shard-like schemas from the DB. */ - async loadSchemas() { + async loadSchemas(): Promise { return this.queryCol( "SELECT nspname FROM pg_namespace WHERE nspname NOT LIKE '%\\_%'", ); @@ -107,9 +107,11 @@ export class Dest { * Given a list of schemas, extracts versions for each schema * (which is a list of migration names). */ - async loadVersionsBySchema(schemas: string[]) { + async loadVersionsBySchema( + schemas: string[], + ): Promise> { if (!schemas.length) { - return new Map(); + return new Map(); } const inClause = schemas.map((v) => this.escape(v)).join(", "); @@ -139,7 +141,7 @@ export class Dest { /** * SQL value quoting. */ - private escape(v: string) { + private escape(v: string): string { return "'" + ("" + v).replace(/'/g, "''") + "'"; } diff --git a/src/Grid.ts b/src/internal/Grid.ts similarity index 98% rename from src/Grid.ts rename to src/internal/Grid.ts index 11c8d19..c4ceda4 100644 --- a/src/Grid.ts +++ b/src/internal/Grid.ts @@ -213,7 +213,10 @@ class Worker { } } - private async acquireSemaphore(maxWorkers: number, key: string) { + private async acquireSemaphore( + maxWorkers: number, + key: string, + ): Promise<() => void> { let semaphore = this.semaphores[key]; if (!semaphore) { semaphore = this.semaphores[key] = new Semaphore(maxWorkers); diff --git a/src/Patch.ts b/src/internal/Patch.ts similarity index 100% rename from src/Patch.ts rename to src/internal/Patch.ts diff --git a/src/Psql.ts b/src/internal/Psql.ts similarity index 92% rename from src/Psql.ts rename to src/internal/Psql.ts index 4587ec1..6c68134 100644 --- a/src/Psql.ts +++ b/src/internal/Psql.ts @@ -27,27 +27,27 @@ export class Psql { this._cmdline = "psql " + quote(this._args); } - get code() { + get code(): number | null { return this._code; } - get stdout() { + get stdout(): string { return this._stdout; } - get stderr() { + get stderr(): string { return this._stderr; } - get out() { + get out(): string { return this._out; } - get cmdline() { + get cmdline(): string { return this._cmdline; } - get lastOutLine() { + get lastOutLine(): string { let pos = this._out.lastIndexOf("\n"); let end = this._out.length; // Find the 1st non-empty line scanning backward. @@ -71,7 +71,7 @@ export class Psql { PGUSER: this.dest.user, PGPASSWORD: this.dest.pass, PGDATABASE: this.dest.db, - PATH: process.env.PATH, + PATH: process.env["PATH"], }, }); diff --git a/src/Registry.ts b/src/internal/Registry.ts similarity index 91% rename from src/Registry.ts rename to src/internal/Registry.ts index 4adaad9..bb69259 100644 --- a/src/Registry.ts +++ b/src/internal/Registry.ts @@ -1,9 +1,9 @@ import { existsSync, lstatSync, readdirSync, readFileSync } from "fs"; import { basename } from "path"; import sortBy from "lodash/sortBy"; -import { DefaultMap } from "./utils/DefaultMap"; -import { extractVars } from "./utils/extractVars"; -import { validateCreateIndexConcurrently } from "./utils/validateCreateIndexConcurrently"; +import { DefaultMap } from "./helpers/DefaultMap"; +import { extractVars } from "./helpers/extractVars"; +import { validateCreateIndexConcurrently } from "./helpers/validateCreateIndexConcurrently"; /** * One migration file (either *.up.* or *.dn.*). @@ -83,7 +83,7 @@ export class Registry { ); } - get prefixes() { + get prefixes(): string[] { return Array.from(this.entriesByPrefix.keys()); } @@ -123,24 +123,24 @@ export class Registry { return entriesBySchema; } - getVersions() { + getVersions(): string[] { return [...this.versions]; } - hasVersion(version: string) { + hasVersion(version: string): boolean { return this.versions.has(version); } - extractVersion(name: string) { + extractVersion(name: string): string { const matches = name.match(/^\d+\.[^.]+\.[^.]+/); return matches ? matches[0] : name; } } -function schemaNameMatchesPrefix(schema: string, prefix: string) { +function schemaNameMatchesPrefix(schema: string, prefix: string): boolean { return ( schema.startsWith(prefix) && - schema.substring(prefix.length).match(/^(\d|$)/s) + !!schema.substring(prefix.length).match(/^(\d|$)/s) ); } diff --git a/src/utils/Args.ts b/src/internal/helpers/Args.ts similarity index 78% rename from src/utils/Args.ts rename to src/internal/helpers/Args.ts index f240941..54156ac 100644 --- a/src/utils/Args.ts +++ b/src/internal/helpers/Args.ts @@ -13,16 +13,20 @@ export class Args { }); } + getOptional(name: TStringArgs): string | undefined { + return this.args[name]; + } + get(name: TStringArgs, def?: string): string { const v = this.args[name] !== undefined ? this.args[name] : def; if (v === undefined) { - throw "Parameter " + name + " is missing"; + throw `Parameter ${name} is missing`; } return v; } - flag(name: TFlagArgs) { + flag(name: TFlagArgs): boolean { return !!this.args[name]; } } diff --git a/src/utils/DefaultMap.ts b/src/internal/helpers/DefaultMap.ts similarity index 100% rename from src/utils/DefaultMap.ts rename to src/internal/helpers/DefaultMap.ts diff --git a/src/utils/__tests__/extractVars.test.ts b/src/internal/helpers/__tests__/extractVars.test.ts similarity index 100% rename from src/utils/__tests__/extractVars.test.ts rename to src/internal/helpers/__tests__/extractVars.test.ts diff --git a/src/utils/__tests__/validateCreateIndexConcurrently.test.ts b/src/internal/helpers/__tests__/validateCreateIndexConcurrently.test.ts similarity index 100% rename from src/utils/__tests__/validateCreateIndexConcurrently.test.ts rename to src/internal/helpers/__tests__/validateCreateIndexConcurrently.test.ts diff --git a/src/utils/collapse.ts b/src/internal/helpers/collapse.ts similarity index 90% rename from src/utils/collapse.ts rename to src/internal/helpers/collapse.ts index 1ed2a29..924ea6f 100644 --- a/src/utils/collapse.ts +++ b/src/internal/helpers/collapse.ts @@ -1,7 +1,7 @@ import { multirange } from "multi-integer-range"; import { DefaultMap } from "./DefaultMap"; -export function collapse(list: string[]) { +export function collapse(list: string[]): string[] { const res = []; const numberSuffixes = new DefaultMap(); for (const s of list.sort()) { diff --git a/src/utils/extractVars.ts b/src/internal/helpers/extractVars.ts similarity index 100% rename from src/utils/extractVars.ts rename to src/internal/helpers/extractVars.ts diff --git a/src/utils/makeMigration.ts b/src/internal/helpers/makeMigration.ts similarity index 96% rename from src/utils/makeMigration.ts rename to src/internal/helpers/makeMigration.ts index d7942d0..edd5ca7 100644 --- a/src/utils/makeMigration.ts +++ b/src/internal/helpers/makeMigration.ts @@ -6,7 +6,7 @@ export async function makeMigration( migrationDir: string, migrationName: string, schemaPrefix: string, -) { +): Promise { const utcTimestamp = moment(Date.now()).utc().format("YYYYMMDDHHmmss"); const migrationFilenameBase = `${utcTimestamp}.${migrationName}.${schemaPrefix}`; diff --git a/src/utils/validateCreateIndexConcurrently.ts b/src/internal/helpers/validateCreateIndexConcurrently.ts similarity index 100% rename from src/utils/validateCreateIndexConcurrently.ts rename to src/internal/helpers/validateCreateIndexConcurrently.ts diff --git a/src/render.ts b/src/internal/render.ts similarity index 91% rename from src/render.ts rename to src/internal/render.ts index 981f76b..cf7092f 100644 --- a/src/render.ts +++ b/src/internal/render.ts @@ -2,10 +2,10 @@ import chalk from "chalk"; import sortBy from "lodash/sortBy"; import type { Dest } from "./Dest"; import type { Grid } from "./Grid"; +import { collapse } from "./helpers/collapse"; +import { DefaultMap } from "./helpers/DefaultMap"; import type { Chain } from "./Patch"; import type { Registry } from "./Registry"; -import { collapse } from "./utils/collapse"; -import { DefaultMap } from "./utils/DefaultMap"; const Table = require("table-layout"); @@ -16,7 +16,7 @@ const TABLE_OPTIONS = { maxWidth: process.stdout.columns - 2, }; -export function renderGrid(grid: Grid) { +export function renderGrid(grid: Grid): string { const activeRows: string[][] = []; const errorRows: string[][] = []; for (const worker of sortBy( @@ -118,7 +118,10 @@ export function renderPatchSummary(chains: Chain[]): [string, boolean] { ]; } -export async function renderLatestVersions(dests: Dest[], registry: Registry) { +export async function renderLatestVersions( + dests: Dest[], + registry: Registry, +): Promise { const destsGrouped = new DefaultMap(); await Promise["all"]( dests.map(async (dest) => { @@ -147,19 +150,19 @@ export async function renderLatestVersions(dests: Dest[], registry: Registry) { ); } -export function printText(text: string) { +export function printText(text: string): void { // eslint-disable-next-line no-console return console.log(text); } -export function printSuccess(text: string) { +export function printSuccess(text: string): void { return printText(chalk.green("" + text)); } -export function printError(error: any) { +export function printError(error: unknown): void { return printText(chalk.red("Error: " + error)); } -function formatHost(host: string) { +function formatHost(host: string): string { return host.match(/^\d+\.\d+\.\d+\.\d+$/) ? host : host.replace(/\..*/, ""); } diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 120000 index 0000000..d03a8cd --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1 @@ +../../open-source-template/tsconfig.base.json \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 8d8828a..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "include": ["src/**/*"], - "compilerOptions": { - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", - "emitDeclarationOnly": true, // turns on SWC - "rootDir": "src", - "outDir": "dist", - "lib": ["es2019"], - "types": ["node", "jest"], - "module": "commonjs" - } -} diff --git a/tsconfig.json b/tsconfig.json new file mode 120000 index 0000000..39723d2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1 @@ +../../open-source-template/tsconfig.json \ No newline at end of file diff --git a/typedoc.config.js b/typedoc.config.js new file mode 120000 index 0000000..cabc0d1 --- /dev/null +++ b/typedoc.config.js @@ -0,0 +1 @@ +../../open-source-template/typedoc.config.js \ No newline at end of file