diff --git a/emvar-lite/mod.ts b/emvar-lite/mod.ts deleted file mode 100644 index 16991a5..0000000 --- a/emvar-lite/mod.ts +++ /dev/null @@ -1,253 +0,0 @@ - -const starSub = /((\d+\.)*\d+)\.\*/ - -function incrementLastNumber(list: number[]) { - const newList = [...list] - newList[newList.length - 1]++ - return newList -} -/** - * Will take in a range, like `>1.2` or `<1.2.3.4` or `=1.2` or `1.*` - * and return a checker, that has the check function for checking that a version is in the valid - * @param range - * @returns - */ -export function rangeOf(range: string | Checker): Checker { - return Checker.parse(range) -} - -/** - * Used to create a checker that will `and` all the ranges passed in - * @param ranges - * @returns - */ -export function rangeAnd(...ranges: (string | Checker)[]): Checker { - if (ranges.length === 0) { - throw new Error('No ranges given'); - } - const [firstCheck, ...rest] = ranges; - return Checker.parse(firstCheck).and(...rest); -} - -/** - * Used to create a checker that will `or` all the ranges passed in - * @param ranges - * @returns - */ -export function rangeOr(...ranges: (string | Checker)[]): Checker { - if (ranges.length === 0) { - throw new Error('No ranges given'); - } - const [firstCheck, ...rest] = ranges; - return Checker.parse(firstCheck).or(...rest); -} - -/** - * This will negate the checker, so given a checker that checks for >= 1.0.0, it will check for < 1.0.0 - * @param range - * @returns - */ -export function notRange(range: string | Checker): Checker { - return rangeOf(range).not(); -} - - -/** - * EmVar is a set of versioning of any pattern like 1 or 1.2 or 1.2.3 or 1.2.3.4 or .. - */ -export class EmVar { - /** - * Convert the range, should be 1.2.* or * into a emvar - * Or an already made emvar - * IsUnsafe - */ - static from(range: string | EmVar): EmVar { - if (range instanceof EmVar) { - return range - } - return EmVar.parse(range) - } - /** - * Convert the range, should be 1.2.* or * into a emvar - * IsUnsafe - */ - static parse(range: string): EmVar { - const values = range.split('.').map(x => parseInt(x)); - for (const value of values) { - if (isNaN(value)) { - throw new Error(`Couldn't parse range: ${range}`); - } - } - return new EmVar(values); - } - private constructor(public readonly values: number[]) { } - - /** - * Used when we need a new emvar that has the last number incremented, used in the 1.* like things - */ - public withLastIncremented() { - return new EmVar(incrementLastNumber(this.values)) - } - - public greaterThan(other: EmVar): boolean { - for (const i in this.values) { - if (other.values[i] == null) { - return true - } - if (this.values[i] > other.values[i]) { - return true; - } - - if (this.values[i] < other.values[i]) { - return false; - } - } - return false; - } - - public equals(other: EmVar): boolean { - if (other.values.length !== this.values.length) { - return false - } - for (const i in this.values) { - if (this.values[i] !== other.values[i]) { - return false; - } - } - return true; - } - public greaterThanOrEqual(other: EmVar): boolean { - return this.greaterThan(other) || this.equals(other); - } - public lessThanOrEqual(other: EmVar): boolean { - return !this.greaterThan(other); - } - public lessThan(other: EmVar): boolean { - return !this.greaterThanOrEqual(other); - } -} - -/** - * A checker is a function that takes a version and returns true if the version matches the checker. - * Used when we are doing range checking, like saying ">=1.0.0".check("1.2.3") will be true - */ -export class Checker { - /** - * Will take in a range, like `>1.2` or `<1.2.3.4` or `=1.2` or `1.*` - * and return a checker, that has the check function for checking that a version is in the valid - * @param range - * @returns - */ - static parse(range: string | Checker): Checker { - if (range instanceof Checker) { - return range - } - range = range.trim(); - if (range.indexOf('||') !== -1) { - return rangeOr(...range.split('||').map(x => Checker.parse(x))); - } - if (range.indexOf('&&') !== -1) { - return rangeAnd(...range.split('&&').map(x => Checker.parse(x))); - } - if (range === '*') return new Checker((version) => { - EmVar.from(version) - return true - }); - if (range.startsWith('!')) { - return Checker.parse(range.substring(1)).not() - } - const starSubMatches = starSub.exec(range) - if (starSubMatches != null) { - const emVarLower = EmVar.parse(starSubMatches[1]) - const emVarUpper = emVarLower.withLastIncremented() - - return new Checker((version) => { - const v = EmVar.from(version); - return (v.greaterThan(emVarLower) || v.equals(emVarLower)) && !v.greaterThan(emVarUpper) && !v.equals(emVarUpper); - }) - } - - switch (range.substring(0, 2)) { - case '>=': { - const emVar = EmVar.parse(range.substring(2)); - return new Checker((version) => { - const v = EmVar.from(version); - return v.greaterThanOrEqual(emVar) - }) - } - case '<=': { - const emVar = EmVar.parse(range.substring(2)); - return new Checker((version) => { - const v = EmVar.from(version); - return v.lessThanOrEqual(emVar); - }) - } - - } - - switch (range.substring(0, 1)) { - case '>': { - console.log('greaterThan') - const emVar = EmVar.parse(range.substring(1)); - return new Checker((version) => { - const v = EmVar.from(version); - return v.greaterThan(emVar); - }) - } - case '<': { - const emVar = EmVar.parse(range.substring(1)); - return new Checker((version) => { - const v = EmVar.from(version); - return v.lessThan(emVar) - }) - } - case '=': { - const emVar = EmVar.parse(range.substring(1)); - return new Checker((version) => { - const v = EmVar.from(version); - return v.equals(emVar); - }) - } - - } - throw new Error("Couldn't parse range: " + range); - } - constructor( - /** - * Check is the function that will be given a emvar or unparsed emvar and should give if it follows - * a pattern - */ - public readonly check: (value: string | EmVar) => boolean - ) { } - - - public and(...others: (Checker | string)[]): Checker { - return new Checker((value) => { - if (!this.check(value)) { - return false; - } - for (const other of others) { - if (!Checker.parse(other).check(value)) { - return false - } - } - return true - }); - } - public or(...others: (Checker | string)[]): Checker { - return new Checker((value) => { - if (this.check(value)) { - return true; - } - for (const other of others) { - if (Checker.parse(other).check(value)) { - return true - } - } - return false - }); - } - public not(): Checker { - return new Checker((value) => !this.check(value)); - } -} \ No newline at end of file diff --git a/emver-lite/mod.ts b/emver-lite/mod.ts new file mode 100644 index 0000000..0d19260 --- /dev/null +++ b/emver-lite/mod.ts @@ -0,0 +1,260 @@ +const starSub = /((\d+\.)*\d+)\.\*/; + +function incrementLastNumber(list: number[]) { + const newList = [...list]; + newList[newList.length - 1]++; + return newList; +} +/** + * Will take in a range, like `>1.2` or `<1.2.3.4` or `=1.2` or `1.*` + * and return a checker, that has the check function for checking that a version is in the valid + * @param range + * @returns + */ +export function rangeOf(range: string | Checker): Checker { + return Checker.parse(range); +} + +/** + * Used to create a checker that will `and` all the ranges passed in + * @param ranges + * @returns + */ +export function rangeAnd(...ranges: (string | Checker)[]): Checker { + if (ranges.length === 0) { + throw new Error("No ranges given"); + } + const [firstCheck, ...rest] = ranges; + return Checker.parse(firstCheck).and(...rest); +} + +/** + * Used to create a checker that will `or` all the ranges passed in + * @param ranges + * @returns + */ +export function rangeOr(...ranges: (string | Checker)[]): Checker { + if (ranges.length === 0) { + throw new Error("No ranges given"); + } + const [firstCheck, ...rest] = ranges; + return Checker.parse(firstCheck).or(...rest); +} + +/** + * This will negate the checker, so given a checker that checks for >= 1.0.0, it will check for < 1.0.0 + * @param range + * @returns + */ +export function notRange(range: string | Checker): Checker { + return rangeOf(range).not(); +} + +/** + * EmVer is a set of versioning of any pattern like 1 or 1.2 or 1.2.3 or 1.2.3.4 or .. + */ +export class EmVer { + /** + * Convert the range, should be 1.2.* or * into a emver + * Or an already made emver + * IsUnsafe + */ + static from(range: string | EmVer): EmVer { + if (range instanceof EmVer) { + return range; + } + return EmVer.parse(range); + } + /** + * Convert the range, should be 1.2.* or * into a emver + * IsUnsafe + */ + static parse(range: string): EmVer { + const values = range.split(".").map((x) => parseInt(x)); + for (const value of values) { + if (isNaN(value)) { + throw new Error(`Couldn't parse range: ${range}`); + } + } + return new EmVer(values); + } + private constructor(public readonly values: number[]) {} + + /** + * Used when we need a new emver that has the last number incremented, used in the 1.* like things + */ + public withLastIncremented() { + return new EmVer(incrementLastNumber(this.values)); + } + + public greaterThan(other: EmVer): boolean { + for (const i in this.values) { + if (other.values[i] == null) { + return true; + } + if (this.values[i] > other.values[i]) { + return true; + } + + if (this.values[i] < other.values[i]) { + return false; + } + } + return false; + } + + public equals(other: EmVer): boolean { + if (other.values.length !== this.values.length) { + return false; + } + for (const i in this.values) { + if (this.values[i] !== other.values[i]) { + return false; + } + } + return true; + } + public greaterThanOrEqual(other: EmVer): boolean { + return this.greaterThan(other) || this.equals(other); + } + public lessThanOrEqual(other: EmVer): boolean { + return !this.greaterThan(other); + } + public lessThan(other: EmVer): boolean { + return !this.greaterThanOrEqual(other); + } + public compare(other: EmVer): number { + if (this.equals(other)) { + return 0; + } else if (this.greaterThan(other)) { + return 1; + } else { + return -1; + } + } +} + +/** + * A checker is a function that takes a version and returns true if the version matches the checker. + * Used when we are doing range checking, like saying ">=1.0.0".check("1.2.3") will be true + */ +export class Checker { + /** + * Will take in a range, like `>1.2` or `<1.2.3.4` or `=1.2` or `1.*` + * and return a checker, that has the check function for checking that a version is in the valid + * @param range + * @returns + */ + static parse(range: string | Checker): Checker { + if (range instanceof Checker) { + return range; + } + range = range.trim(); + if (range.indexOf("||") !== -1) { + return rangeOr(...range.split("||").map((x) => Checker.parse(x))); + } + if (range.indexOf("&&") !== -1) { + return rangeAnd(...range.split("&&").map((x) => Checker.parse(x))); + } + if (range === "*") { + return new Checker((version) => { + EmVer.from(version); + return true; + }); + } + if (range.startsWith("!")) { + return Checker.parse(range.substring(1)).not(); + } + const starSubMatches = starSub.exec(range); + if (starSubMatches != null) { + const emVarLower = EmVer.parse(starSubMatches[1]); + const emVarUpper = emVarLower.withLastIncremented(); + + return new Checker((version) => { + const v = EmVer.from(version); + return (v.greaterThan(emVarLower) || v.equals(emVarLower)) && + !v.greaterThan(emVarUpper) && !v.equals(emVarUpper); + }); + } + + switch (range.substring(0, 2)) { + case ">=": { + const emVar = EmVer.parse(range.substring(2)); + return new Checker((version) => { + const v = EmVer.from(version); + return v.greaterThanOrEqual(emVar); + }); + } + case "<=": { + const emVar = EmVer.parse(range.substring(2)); + return new Checker((version) => { + const v = EmVer.from(version); + return v.lessThanOrEqual(emVar); + }); + } + } + + switch (range.substring(0, 1)) { + case ">": { + console.log("greaterThan"); + const emVar = EmVer.parse(range.substring(1)); + return new Checker((version) => { + const v = EmVer.from(version); + return v.greaterThan(emVar); + }); + } + case "<": { + const emVar = EmVer.parse(range.substring(1)); + return new Checker((version) => { + const v = EmVer.from(version); + return v.lessThan(emVar); + }); + } + case "=": { + const emVar = EmVer.parse(range.substring(1)); + return new Checker((version) => { + const v = EmVer.from(version); + return v.equals(emVar); + }); + } + } + throw new Error("Couldn't parse range: " + range); + } + constructor( + /** + * Check is the function that will be given a emver or unparsed emver and should give if it follows + * a pattern + */ + public readonly check: (value: string | EmVer) => boolean, + ) {} + + public and(...others: (Checker | string)[]): Checker { + return new Checker((value) => { + if (!this.check(value)) { + return false; + } + for (const other of others) { + if (!Checker.parse(other).check(value)) { + return false; + } + } + return true; + }); + } + public or(...others: (Checker | string)[]): Checker { + return new Checker((value) => { + if (this.check(value)) { + return true; + } + for (const other of others) { + if (Checker.parse(other).check(value)) { + return true; + } + } + return false; + }); + } + public not(): Checker { + return new Checker((value) => !this.check(value)); + } +} diff --git a/emvar-lite/test.ts b/emver-lite/test.ts similarity index 100% rename from emvar-lite/test.ts rename to emver-lite/test.ts diff --git a/migrations.ts b/migrations.ts new file mode 100644 index 0000000..c00609a --- /dev/null +++ b/migrations.ts @@ -0,0 +1,63 @@ +import { types as T } from "./mod.ts"; +import { EmVer } from "./emver-lite/mod.ts"; + +export type MigrationFn = (effects: T.Effects) => Promise; + +export interface Migration { + up: MigrationFn; + down: MigrationFn; +} + +export interface MigrationMapping { + [version: string]: Migration; +} + +export function fromMapping( + migrations: MigrationMapping, + currentVersion: string, +): T.ExpectedExports.migration { + return async (effects: T.Effects, version: string) => { + let configured = true; + + const current = EmVer.parse(currentVersion); + const previous = EmVer.parse(version); + + let migrationsToRun: MigrationFn[]; + switch (previous.compare(current)) { + case 0: + migrationsToRun = []; + break; + case 1: // ups + migrationsToRun = Object.entries(migrations).map( + ([version, migration]) => ({ + version: EmVer.parse(version), + migration, + }), + ).filter(({ version }) => version.greaterThan(previous)).sort((a, b) => + a.version.compare(b.version) + ).map(({ migration }) => migration.up); + break; + case -1: // downs + migrationsToRun = Object.entries(migrations).map( + ([version, migration]) => ({ + version: EmVer.parse(version), + migration, + }), + ).filter(({ version }) => version.lessThanOrEqual(previous)).sort(( + a, + b, + ) => b.version.compare(a.version)).map(({ migration }) => + migration.down + ); + break; + default: + return { error: "unreachable" }; + } + + for (const migration of migrationsToRun) { + configured = (await migration(effects)).configured && configured; + } + + return { result: { configured } }; + }; +} diff --git a/mod.ts b/mod.ts index 654e9de..0ab748d 100644 --- a/mod.ts +++ b/mod.ts @@ -1,5 +1,6 @@ -export { matches, YAML } from './dependencies.ts' -export * as types from './types.ts' +export { matches, YAML } from "./dependencies.ts"; +export * as types from "./types.ts"; -export { exists } from './exists.ts' -export * as compat from './compat/mod.ts' \ No newline at end of file +export { exists } from "./exists.ts"; +export * as compat from "./compat/mod.ts"; +export * as migrations from "./migrations.ts";