From e82b402cff2bcd87e187a1e2ba5116ffbfed736b Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sun, 19 Nov 2023 17:20:52 +0100 Subject: [PATCH 01/27] deps: add ts-brand --- package-lock.json | 11 +++++++++++ package.json | 1 + 2 files changed, 12 insertions(+) diff --git a/package-lock.json b/package-lock.json index f335ebfb..78a90b5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "npmlog": "^6.0.2", "pkginfo": "^0.4.1", "promptly": "^3.2.0", + "ts-brand": "^0.0.2", "update-notifier": "^5.1.0", "yaml": "^2.0.1" }, @@ -9817,6 +9818,11 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-brand": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/ts-brand/-/ts-brand-0.0.2.tgz", + "integrity": "sha512-UhSzWY4On9ZHIj6DKkRYVN/8OaprbLAZ3b/Y2AJwdl6oozSABsQ0PvwDh4vOVdkvOtWQOkIrjctZ1kj8YfF3jA==" + }, "node_modules/ts-mocha": { "version": "10.0.0", "dev": true, @@ -16834,6 +16840,11 @@ "dev": true, "requires": {} }, + "ts-brand": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/ts-brand/-/ts-brand-0.0.2.tgz", + "integrity": "sha512-UhSzWY4On9ZHIj6DKkRYVN/8OaprbLAZ3b/Y2AJwdl6oozSABsQ0PvwDh4vOVdkvOtWQOkIrjctZ1kj8YfF3jA==" + }, "ts-mocha": { "version": "10.0.0", "dev": true, diff --git a/package.json b/package.json index e9108219..e9ea5b6a 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "npmlog": "^6.0.2", "pkginfo": "^0.4.1", "promptly": "^3.2.0", + "ts-brand": "^0.0.2", "update-notifier": "^5.1.0", "yaml": "^2.0.1" } From 92ee6722121e0ec37f442173733aba43e7270a2e Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sun, 19 Nov 2023 17:26:01 +0100 Subject: [PATCH 02/27] refactor: reformat files --- src/utils/editor-version.ts | 2 +- src/utils/manifest.ts | 6 +++--- src/utils/pkg-name.ts | 2 +- src/utils/upm-config.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utils/editor-version.ts b/src/utils/editor-version.ts index aed12f0a..015165dc 100644 --- a/src/utils/editor-version.ts +++ b/src/utils/editor-version.ts @@ -1,4 +1,4 @@ -import { EditorVersion } from "../types/global"; +import {EditorVersion} from "../types/global"; /** * Compares two editor versions for ordering diff --git a/src/utils/manifest.ts b/src/utils/manifest.ts index 25fa3ed8..2320fb52 100644 --- a/src/utils/manifest.ts +++ b/src/utils/manifest.ts @@ -1,8 +1,8 @@ -import { PkgManifest } from "../types/global"; +import {PkgManifest} from "../types/global"; import fs from "fs"; -import { assertIsError } from "./error-type-guards"; +import {assertIsError} from "./error-type-guards"; import log from "../logger"; -import { env } from "./env"; +import {env} from "./env"; /** * Attempts to load the manifest from the path specified in env diff --git a/src/utils/pkg-name.ts b/src/utils/pkg-name.ts index e663f4b1..8a5ea67f 100644 --- a/src/utils/pkg-name.ts +++ b/src/utils/pkg-name.ts @@ -1,4 +1,4 @@ -import { PkgName, PkgVersion, ReverseDomainName } from "../types/global"; +import {PkgName, PkgVersion, ReverseDomainName} from "../types/global"; /** * Split package-name, which may include a version into the actual name of the diff --git a/src/utils/upm-config.ts b/src/utils/upm-config.ts index 2cdfc2fe..ef689d4b 100644 --- a/src/utils/upm-config.ts +++ b/src/utils/upm-config.ts @@ -1,4 +1,4 @@ -import { UPMConfig } from "../types/global"; +import {UPMConfig} from "../types/global"; import mkdirp from "mkdirp"; import path from "path"; import TOML from "@iarna/toml"; @@ -6,7 +6,7 @@ import fs from "fs"; import log from "../logger"; import isWsl from "is-wsl"; import execute from "./process"; -import { env } from "./env"; +import {env} from "./env"; /** * Gets the path to directory in which the upm config is stored From 30b0f7135c0782c6105a7f3ed531ec9bcbe7d808 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sun, 19 Nov 2023 17:35:44 +0100 Subject: [PATCH 03/27] refactor: add ip-address type --- src/types/global.ts | 3 ++- src/types/ip-address.ts | 15 +++++++++++++++ src/utils/env.ts | 4 ++-- test/test-ip-address.ts | 28 ++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/types/ip-address.ts create mode 100644 test/test-ip-address.ts diff --git a/src/types/global.ts b/src/types/global.ts index 306f93a9..d3838acd 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -1,4 +1,5 @@ import { NpmAuth } from "another-npm-registry-client"; +import { IpAddress } from "./ip-address"; export type PkgVersion = string; @@ -32,7 +33,7 @@ export type Env = { upstream: boolean; upstreamRegistry: string; registry: string; - namespace: string; + namespace: ReverseDomainName | IpAddress; editorVersion: string | null; region: Region; manifestPath: string; diff --git a/src/types/ip-address.ts b/src/types/ip-address.ts new file mode 100644 index 00000000..5a9de148 --- /dev/null +++ b/src/types/ip-address.ts @@ -0,0 +1,15 @@ +import { Brand } from "ts-brand"; +import net from "node:net"; + +/** + * A string that is either a valid v4 or v6 ip-address + */ +export type IpAddress = Brand; + +/** + * Checks if a string is valid {@link IpAddress} + * @param s The string + */ +export function isIpAddress(s: string): s is IpAddress { + return net.isIPv4(s) || net.isIPv6(s); +} diff --git a/src/utils/env.ts b/src/utils/env.ts index c49b6f1e..b26419ee 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -2,11 +2,11 @@ import { Env, GlobalOptions } from "../types/global"; import log from "../logger"; import chalk from "chalk"; import url from "url"; -import net from "node:net"; import { loadUpmConfig } from "./upm-config"; import path from "path"; import fs from "fs"; import yaml from "yaml"; +import { isIpAddress } from "../types/ip-address"; export const env: Env = { auth: {}, @@ -70,7 +70,7 @@ export const parseEnv = async function ( env.registry = registry; // TODO: Check hostname for null const hostname = url.parse(registry).hostname as string; - if (net.isIP(hostname)) env.namespace = hostname; + if (isIpAddress(hostname)) env.namespace = hostname; else env.namespace = hostname.split(".").reverse().slice(0, 2).join("."); } // auth diff --git a/test/test-ip-address.ts b/test/test-ip-address.ts new file mode 100644 index 00000000..49e28d4b --- /dev/null +++ b/test/test-ip-address.ts @@ -0,0 +1,28 @@ +import { describe } from "mocha"; +import { isIpAddress } from "../src/types/ip-address"; +import should from "should"; + +describe("ip-address", function () { + describe("validate", function () { + [ + "10.20.30.40", + "64.233.160.0", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + ].forEach((s) => + it(`"${s}" should be ip-address`, () => should(isIpAddress(s)).be.true()) + ); + + [ + "", + " ", + "hello", + // Missing 4th segment + "64.233.160", + // Deleted some colons + "2001:0db8:85a30000:0000:8a2e0370:7334", + ].forEach((s) => + it(`"${s}" should not be ip-address`, () => + should(isIpAddress(s)).be.false()) + ); + }); +}); From c36738908e50b797edb5501abdf6768a5f2fd03c Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sun, 19 Nov 2023 19:15:06 +0100 Subject: [PATCH 04/27] refactor: add reverse-domain-name type Add branded type for reverse-domain-name. This showed a lot of cases where pkg-name type was used, where reverse-domain-name should have been used. Fixed these cases --- src/cmd-add.ts | 3 +- src/cmd-deps.ts | 3 +- src/cmd-remove.ts | 3 +- src/cmd-search.ts | 2 +- src/cmd-view.ts | 7 ++++- src/registry-client.ts | 10 ++++-- src/types/global.ts | 9 +++--- src/types/reverse-domain-name.ts | 52 ++++++++++++++++++++++++++++++++ src/utils/env.ts | 24 +++++---------- src/utils/pkg-name.ts | 8 +++-- test/test-reverse-domain-name.ts | 50 ++++++++++++++++++++++++++++++ test/types.ts | 3 +- 12 files changed, 142 insertions(+), 32 deletions(-) create mode 100644 src/types/reverse-domain-name.ts create mode 100644 test/test-reverse-domain-name.ts diff --git a/src/cmd-add.ts b/src/cmd-add.ts index 0490b7ec..cb1a79e6 100644 --- a/src/cmd-add.ts +++ b/src/cmd-add.ts @@ -12,6 +12,7 @@ import { tryParseEditorVersion, } from "./utils/editor-version"; import { fetchPackageDependencies, fetchPackageInfo } from "./registry-client"; +import { isReverseDomainName } from "./types/reverse-domain-name"; export type AddOptions = { test?: boolean; @@ -227,7 +228,7 @@ const _add = async function ({ const entry = manifest.scopedRegistries.filter(filterEntry)[0]; // apply pkgsInScope const scopesSet = new Set(entry.scopes || []); - pkgsInScope.push(env.namespace); + if (isReverseDomainName(env.namespace)) pkgsInScope.push(env.namespace); pkgsInScope.forEach((name) => { if (!scopesSet.has(name)) { scopesSet.add(name); diff --git a/src/cmd-deps.ts b/src/cmd-deps.ts index ee63424b..99b9e972 100644 --- a/src/cmd-deps.ts +++ b/src/cmd-deps.ts @@ -3,6 +3,7 @@ import { atVersion, splitPkgName } from "./utils/pkg-name"; import { GlobalOptions, PkgName, PkgVersion } from "./types/global"; import { parseEnv } from "./utils/env"; import { fetchPackageDependencies } from "./registry-client"; +import { ReverseDomainName } from "./types/reverse-domain-name"; export type DepsOptions = { deep?: boolean; @@ -25,7 +26,7 @@ const _deps = async function ({ version, deep, }: { - name: PkgName; + name: ReverseDomainName; version: PkgVersion | undefined; deep?: boolean; }) { diff --git a/src/cmd-remove.ts b/src/cmd-remove.ts index b051367d..b338f69a 100644 --- a/src/cmd-remove.ts +++ b/src/cmd-remove.ts @@ -3,6 +3,7 @@ import { atVersion, splitPkgName } from "./utils/pkg-name"; import { GlobalOptions, PkgName, ScopedRegistry } from "./types/global"; import { loadManifest, saveManifest } from "./utils/manifest"; import { env, parseEnv } from "./utils/env"; +import { isReverseDomainName } from "./types/reverse-domain-name"; export type RemoveOptions = { _global: GlobalOptions; @@ -68,7 +69,7 @@ const _remove = async function (pkg: PkgName) { if (index > -1) { entry.scopes.splice(index, 1); const scopesSet = new Set(entry.scopes); - scopesSet.add(env.namespace); + if (isReverseDomainName(env.namespace)) scopesSet.add(env.namespace); entry.scopes = Array.from(scopesSet).sort(); dirty = true; } diff --git a/src/cmd-search.ts b/src/cmd-search.ts index 2dcb471b..a0df138f 100644 --- a/src/cmd-search.ts +++ b/src/cmd-search.ts @@ -11,10 +11,10 @@ import { PkgName, PkgVersion, Registry, - ReverseDomainName, } from "./types/global"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { env, parseEnv } from "./utils/env"; +import { ReverseDomainName } from "./types/reverse-domain-name"; type DateString = string; diff --git a/src/cmd-view.ts b/src/cmd-view.ts index b9f76248..c12e9c0f 100644 --- a/src/cmd-view.ts +++ b/src/cmd-view.ts @@ -6,6 +6,7 @@ import { GlobalOptions, PkgInfo, PkgName } from "./types/global"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { env, parseEnv } from "./utils/env"; import { fetchPackageInfo } from "./registry-client"; +import { ReverseDomainName } from "./types/reverse-domain-name"; export type ViewOptions = { _global: GlobalOptions; @@ -81,7 +82,11 @@ const printInfo = function (pkg: PkgInfo) { console.log("dependencies"); Object.keys(dependencies) .sort() - .forEach((n) => console.log(chalk.yellow(n) + ` ${dependencies[n]}`)); + .forEach((n) => + console.log( + chalk.yellow(n) + ` ${dependencies[n as ReverseDomainName]}` + ) + ); } console.log(); diff --git a/src/registry-client.ts b/src/registry-client.ts index 2e0152be..80aa9df8 100644 --- a/src/registry-client.ts +++ b/src/registry-client.ts @@ -20,6 +20,7 @@ import { env } from "./utils/env"; import { atVersion, isInternalPackage } from "./utils/pkg-name"; import _ from "lodash"; import { tryGetLatestVersion } from "./utils/pkg-info"; +import { ReverseDomainName } from "./types/reverse-domain-name"; export type NpmClient = { rawClient: RegClient; @@ -124,7 +125,7 @@ export const fetchPackageDependencies = async function ({ version, deep, }: { - name: PkgName; + name: ReverseDomainName; version: PkgVersion | undefined; deep?: boolean; }): Promise<[Dependency[], Dependency[]]> { @@ -224,8 +225,11 @@ export const fetchPackageDependencies = async function ({ if (depObj.self || deep) { const deps: NameVersionPair[] = _.toPairs( pkgInfo.versions[entry.version]["dependencies"] - ).map((x: [PkgName, PkgVersion]): NameVersionPair => { - return { name: x[0], version: x[1] }; + ).map((x): NameVersionPair => { + return { + name: x[0] as ReverseDomainName, + version: x[1] as PkgVersion, + }; }); deps.forEach((x) => pendingList.push(x)); } diff --git a/src/types/global.ts b/src/types/global.ts index d3838acd..01fac0ae 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -1,10 +1,9 @@ import { NpmAuth } from "another-npm-registry-client"; import { IpAddress } from "./ip-address"; +import { ReverseDomainName } from "./reverse-domain-name"; export type PkgVersion = string; -export type ReverseDomainName = string; - export type PkgName = ReverseDomainName | `${ReverseDomainName}@${PkgVersion}`; export type Region = "us" | "cn"; @@ -60,7 +59,7 @@ export type PkgVersionInfo = { version: string; unity?: string; unityRelease?: string; - dependencies?: Record; + dependencies?: Record; license?: string; displayName?: string; description?: string; @@ -91,12 +90,12 @@ export type PkgInfo = { }; export type NameVersionPair = { - name: PkgName; + name: ReverseDomainName; version: PkgVersion | undefined; }; export type Dependency = { - name: PkgName; + name: ReverseDomainName; version: PkgVersion; upstream: boolean; self: boolean; diff --git a/src/types/reverse-domain-name.ts b/src/types/reverse-domain-name.ts new file mode 100644 index 00000000..7066c075 --- /dev/null +++ b/src/types/reverse-domain-name.ts @@ -0,0 +1,52 @@ +import { Brand } from "ts-brand"; + +/** + * A string matching the format of a reverse domain name. + * @example com.unity + * @example com.my-company + */ +export type ReverseDomainName = Brand; + +const segmentRegex = /^(?!.*--)[^-][a-zA-Z0-9-]{0,20}[^-]$/; + +export const openUpmReverseDomainName = "com.openupm" as ReverseDomainName; + +function domainSegmentsIn(hostName: string): string[] { + return hostName.split("."); +} + +/** + * Creates a namespace by reversing the TDL of a host-name. + * @param hostname The host-name to reverse. + * @example unity.com becomes com.unity. + * @example registry.npmjs.org becomes org.npmjs. + * @example my-school.ac.at becomes at.ac.my-school + */ +export function namespaceFor(hostname: string): ReverseDomainName { + const segments = domainSegmentsIn(hostname); + const namespaceSegments = (function () { + const count = segments.length; + + /* + NOTE: This function does not handle domains with longer extensions + such as registry.example.team.com. In this case it would incorrectly only + return "com.team" even though "example" is also part of the domain. + Let's just hope this does not happen for now 🤞 + */ + + // 2-part domains, like unity.com + if (count < 3) return segments; + // Domains with two short extensions like my-school.ac.at + if (segments[count - 1].length <= 3 && segments[count - 2].length <= 3) + return segments.slice(count - 3); + // Domains with one extension such as registry.npmjs.org + return segments.slice(count - 2); + })(); + return namespaceSegments.reverse().join(".") as ReverseDomainName; +} + +export function isReverseDomainName(s: string): s is ReverseDomainName { + const segments = domainSegmentsIn(s); + if (segments === null || segments.length < 2) return false; + return segments.every((segment) => segmentRegex.test(segment)); +} diff --git a/src/utils/env.ts b/src/utils/env.ts index b26419ee..510074f1 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -7,21 +7,13 @@ import path from "path"; import fs from "fs"; import yaml from "yaml"; import { isIpAddress } from "../types/ip-address"; +import { + namespaceFor, + openUpmReverseDomainName, +} from "../types/reverse-domain-name"; + +export const env: Env = {}; -export const env: Env = { - auth: {}, - color: false, - cwd: "", - editorVersion: null, - manifestPath: "", - namespace: "", - region: "us", - registry: "", - systemUser: false, - upstream: false, - upstreamRegistry: "", - wsl: false, -}; // Parse env export const parseEnv = async function ( options: { _global: GlobalOptions } & Record, @@ -29,9 +21,9 @@ export const parseEnv = async function ( ) { // set defaults env.registry = "https://package.openupm.com"; - env.namespace = "com.openupm"; env.cwd = ""; env.manifestPath = ""; + env.namespace = openUpmReverseDomainName; env.upstream = true; env.color = true; env.upstreamRegistry = "https://packages.unity.com"; @@ -71,7 +63,7 @@ export const parseEnv = async function ( // TODO: Check hostname for null const hostname = url.parse(registry).hostname as string; if (isIpAddress(hostname)) env.namespace = hostname; - else env.namespace = hostname.split(".").reverse().slice(0, 2).join("."); + else env.namespace = namespaceFor(hostname); } // auth if (options._global.systemUser) env.systemUser = true; diff --git a/src/utils/pkg-name.ts b/src/utils/pkg-name.ts index 8a5ea67f..3bac07c9 100644 --- a/src/utils/pkg-name.ts +++ b/src/utils/pkg-name.ts @@ -1,4 +1,8 @@ -import {PkgName, PkgVersion, ReverseDomainName} from "../types/global"; +import { PkgName, PkgVersion } from "../types/global"; +import { + isReverseDomainName, + ReverseDomainName, +} from "../types/reverse-domain-name"; /** * Split package-name, which may include a version into the actual name of the @@ -9,7 +13,7 @@ export const splitPkgName = function (pkgName: PkgName): { version: PkgVersion | undefined; } { const segments = pkgName.split("@"); - const name = segments[0]; + const name = segments[0] as ReverseDomainName; const version = segments.length > 1 ? segments.slice(1, segments.length).join("@") diff --git a/test/test-reverse-domain-name.ts b/test/test-reverse-domain-name.ts new file mode 100644 index 00000000..c440afa7 --- /dev/null +++ b/test/test-reverse-domain-name.ts @@ -0,0 +1,50 @@ +import { describe } from "mocha"; +import { + isReverseDomainName, + namespaceFor, +} from "../src/types/reverse-domain-name"; +import should from "should"; + +describe("reverse-domain-name", function () { + describe("namespace", function () { + [ + ["unity.com", "com.unity"], + ["my-school.ac.at", "at.ac.my-school"], + ["openupm.com", "com.openupm"], + ["registry.npmjs.org", "org.npmjs"], + ].forEach(([hostName, expected]) => + it(`"${hostName}" should become "${expected}"`, function () { + const actual = namespaceFor(hostName); + should(actual).be.equal(expected); + }) + ); + }); + describe("validation", function () { + [ + "com.unity", + "com.openupm", + "at.ac.my-school", + "dev.comradevanti123", + ].forEach((s) => + it(`"${s}" should be reverse-domain-name`, () => + should(isReverseDomainName(s)).be.true()) + ); + [ + "", + " ", + // Single segments are not valid reverse-domain-names + "com", + // Invalid characters + "com.x💀x", + // No double hyphens + "com.my--school", + // No leading hyphens + "com.-unity", + // No trailing hyphens + "com.unity-", + ].forEach((s) => + it(`"${s}" should not be reverse-domain-name`, () => + should(isReverseDomainName(s)).be.false()) + ); + }); +}); diff --git a/test/types.ts b/test/types.ts index ee0ec0d5..a34f1657 100644 --- a/test/types.ts +++ b/test/types.ts @@ -1,4 +1,5 @@ -import { Contact, PkgVersion, ReverseDomainName } from "../src/types/global"; +import { Contact, PkgVersion } from "../src/types/global"; +import { ReverseDomainName } from "../src/types/reverse-domain-name"; type Maintainer = { username: string; email: string }; From deba217a038ed7230dce983984dce9614ccd01bb Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Wed, 22 Nov 2023 16:36:19 +0100 Subject: [PATCH 05/27] refactor: reformat files --- src/utils/editor-version.ts | 2 +- src/utils/manifest.ts | 6 +++--- src/utils/pkg-name.ts | 5 +---- src/utils/upm-config.ts | 4 ++-- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/utils/editor-version.ts b/src/utils/editor-version.ts index 015165dc..aed12f0a 100644 --- a/src/utils/editor-version.ts +++ b/src/utils/editor-version.ts @@ -1,4 +1,4 @@ -import {EditorVersion} from "../types/global"; +import { EditorVersion } from "../types/global"; /** * Compares two editor versions for ordering diff --git a/src/utils/manifest.ts b/src/utils/manifest.ts index 2320fb52..25fa3ed8 100644 --- a/src/utils/manifest.ts +++ b/src/utils/manifest.ts @@ -1,8 +1,8 @@ -import {PkgManifest} from "../types/global"; +import { PkgManifest } from "../types/global"; import fs from "fs"; -import {assertIsError} from "./error-type-guards"; +import { assertIsError } from "./error-type-guards"; import log from "../logger"; -import {env} from "./env"; +import { env } from "./env"; /** * Attempts to load the manifest from the path specified in env diff --git a/src/utils/pkg-name.ts b/src/utils/pkg-name.ts index 3bac07c9..28a44083 100644 --- a/src/utils/pkg-name.ts +++ b/src/utils/pkg-name.ts @@ -1,8 +1,5 @@ import { PkgName, PkgVersion } from "../types/global"; -import { - isReverseDomainName, - ReverseDomainName, -} from "../types/reverse-domain-name"; +import { ReverseDomainName } from "../types/reverse-domain-name"; /** * Split package-name, which may include a version into the actual name of the diff --git a/src/utils/upm-config.ts b/src/utils/upm-config.ts index ef689d4b..2cdfc2fe 100644 --- a/src/utils/upm-config.ts +++ b/src/utils/upm-config.ts @@ -1,4 +1,4 @@ -import {UPMConfig} from "../types/global"; +import { UPMConfig } from "../types/global"; import mkdirp from "mkdirp"; import path from "path"; import TOML from "@iarna/toml"; @@ -6,7 +6,7 @@ import fs from "fs"; import log from "../logger"; import isWsl from "is-wsl"; import execute from "./process"; -import {env} from "./env"; +import { env } from "./env"; /** * Gets the path to directory in which the upm config is stored From 312b80a0d0bcd2942e8660258f85a067f3ba8316 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Wed, 22 Nov 2023 16:36:26 +0100 Subject: [PATCH 06/27] conf: compile after change --- .idea/compiler.xml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .idea/compiler.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..1a2fb332 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file From 121385963e22db78c452ae086f6d0131b2788514 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Wed, 22 Nov 2023 16:36:42 +0100 Subject: [PATCH 07/27] style: reformat file --- tsconfig.json | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 9293beee..f0ecaf90 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,8 +25,7 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "commonjs", - /* Specify what module code is generated. */ + "module": "commonjs", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ @@ -80,15 +79,12 @@ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, - /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, - /* Ensure that casing is correct in imports. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */ - "strict": true, - /* Enable all strict type-checking options. */ + "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ @@ -110,8 +106,7 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true - /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, "include": [ "./src/**/*" From 1334e104b3ac270317da61fd7a95e0935de20c61 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Wed, 22 Nov 2023 16:36:48 +0100 Subject: [PATCH 08/27] fix: outdated path --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index f0ecaf90..d21ff3b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,7 +32,7 @@ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ "typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */ - "lib/types", "node_modules/@types" + "./src/types", "./node_modules/@types" ], // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ From d768fa791f0cfd1b594ceb15478818c5cd98aca0 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Wed, 22 Nov 2023 16:37:04 +0100 Subject: [PATCH 09/27] conf: tsconfig for tests Extends project tsconfig, but without emits --- test/tsconfig.json | 113 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 test/tsconfig.json diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 00000000..39fe5ba8 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,113 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + // "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": ["ES2020"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + // "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": false, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": false, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./lib", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + // "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + // "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + //"strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + //"skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": [ + "./**/*" + ] +} From fec81197664f479b25029c89edc34387738535ea Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 23 Nov 2023 12:30:10 +0100 Subject: [PATCH 10/27] refactor: domain-name type Rename to just domain-name since it should also work for regular domain names. Also it's a little shorter that way. The rules for what makes a valid name also changes a little bit. Now single segment names like just "com" are also valid. We don't need to do that much validation. --- src/cmd-add.ts | 4 ++-- src/cmd-deps.ts | 4 ++-- src/cmd-remove.ts | 4 ++-- src/cmd-search.ts | 4 ++-- src/cmd-view.ts | 6 ++---- src/registry-client.ts | 6 +++--- ...{reverse-domain-name.ts => domain-name.ts} | 21 ++++++++++++------- src/types/global.ts | 14 ++++++------- src/utils/env.ts | 5 +---- src/utils/pkg-name.ts | 14 ++++++------- ...rse-domain-name.ts => test-domain-name.ts} | 18 +++++++--------- test/types.ts | 4 ++-- 12 files changed, 49 insertions(+), 55 deletions(-) rename src/types/{reverse-domain-name.ts => domain-name.ts} (67%) rename test/{test-reverse-domain-name.ts => test-domain-name.ts} (69%) diff --git a/src/cmd-add.ts b/src/cmd-add.ts index cb1a79e6..9b409859 100644 --- a/src/cmd-add.ts +++ b/src/cmd-add.ts @@ -12,7 +12,7 @@ import { tryParseEditorVersion, } from "./utils/editor-version"; import { fetchPackageDependencies, fetchPackageInfo } from "./registry-client"; -import { isReverseDomainName } from "./types/reverse-domain-name"; +import { isDomainName } from "./types/domain-name"; export type AddOptions = { test?: boolean; @@ -228,7 +228,7 @@ const _add = async function ({ const entry = manifest.scopedRegistries.filter(filterEntry)[0]; // apply pkgsInScope const scopesSet = new Set(entry.scopes || []); - if (isReverseDomainName(env.namespace)) pkgsInScope.push(env.namespace); + if (isDomainName(env.namespace)) pkgsInScope.push(env.namespace); pkgsInScope.forEach((name) => { if (!scopesSet.has(name)) { scopesSet.add(name); diff --git a/src/cmd-deps.ts b/src/cmd-deps.ts index 99b9e972..5626db3c 100644 --- a/src/cmd-deps.ts +++ b/src/cmd-deps.ts @@ -3,7 +3,7 @@ import { atVersion, splitPkgName } from "./utils/pkg-name"; import { GlobalOptions, PkgName, PkgVersion } from "./types/global"; import { parseEnv } from "./utils/env"; import { fetchPackageDependencies } from "./registry-client"; -import { ReverseDomainName } from "./types/reverse-domain-name"; +import { DomainName } from "./types/domain-name"; export type DepsOptions = { deep?: boolean; @@ -26,7 +26,7 @@ const _deps = async function ({ version, deep, }: { - name: ReverseDomainName; + name: DomainName; version: PkgVersion | undefined; deep?: boolean; }) { diff --git a/src/cmd-remove.ts b/src/cmd-remove.ts index b338f69a..08fc2789 100644 --- a/src/cmd-remove.ts +++ b/src/cmd-remove.ts @@ -3,7 +3,7 @@ import { atVersion, splitPkgName } from "./utils/pkg-name"; import { GlobalOptions, PkgName, ScopedRegistry } from "./types/global"; import { loadManifest, saveManifest } from "./utils/manifest"; import { env, parseEnv } from "./utils/env"; -import { isReverseDomainName } from "./types/reverse-domain-name"; +import { isDomainName } from "./types/domain-name"; export type RemoveOptions = { _global: GlobalOptions; @@ -69,7 +69,7 @@ const _remove = async function (pkg: PkgName) { if (index > -1) { entry.scopes.splice(index, 1); const scopesSet = new Set(entry.scopes); - if (isReverseDomainName(env.namespace)) scopesSet.add(env.namespace); + if (isDomainName(env.namespace)) scopesSet.add(env.namespace); entry.scopes = Array.from(scopesSet).sort(); dirty = true; } diff --git a/src/cmd-search.ts b/src/cmd-search.ts index a0df138f..47aa73ca 100644 --- a/src/cmd-search.ts +++ b/src/cmd-search.ts @@ -14,7 +14,7 @@ import { } from "./types/global"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { env, parseEnv } from "./utils/env"; -import { ReverseDomainName } from "./types/reverse-domain-name"; +import { DomainName } from "./types/domain-name"; type DateString = string; @@ -30,7 +30,7 @@ export type SearchedPkgInfo = Omit & { export type OldSearchResult = | SearchedPkgInfo[] - | Record; + | Record; // Get npm fetch options const getNpmFetchOptions = function (): Options { diff --git a/src/cmd-view.ts b/src/cmd-view.ts index c12e9c0f..9699ed49 100644 --- a/src/cmd-view.ts +++ b/src/cmd-view.ts @@ -6,7 +6,7 @@ import { GlobalOptions, PkgInfo, PkgName } from "./types/global"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { env, parseEnv } from "./utils/env"; import { fetchPackageInfo } from "./registry-client"; -import { ReverseDomainName } from "./types/reverse-domain-name"; +import { DomainName } from "./types/domain-name"; export type ViewOptions = { _global: GlobalOptions; @@ -83,9 +83,7 @@ const printInfo = function (pkg: PkgInfo) { Object.keys(dependencies) .sort() .forEach((n) => - console.log( - chalk.yellow(n) + ` ${dependencies[n as ReverseDomainName]}` - ) + console.log(chalk.yellow(n) + ` ${dependencies[n as DomainName]}`) ); } diff --git a/src/registry-client.ts b/src/registry-client.ts index 80aa9df8..8f536b65 100644 --- a/src/registry-client.ts +++ b/src/registry-client.ts @@ -20,7 +20,7 @@ import { env } from "./utils/env"; import { atVersion, isInternalPackage } from "./utils/pkg-name"; import _ from "lodash"; import { tryGetLatestVersion } from "./utils/pkg-info"; -import { ReverseDomainName } from "./types/reverse-domain-name"; +import { DomainName } from "./types/domain-name"; export type NpmClient = { rawClient: RegClient; @@ -125,7 +125,7 @@ export const fetchPackageDependencies = async function ({ version, deep, }: { - name: ReverseDomainName; + name: DomainName; version: PkgVersion | undefined; deep?: boolean; }): Promise<[Dependency[], Dependency[]]> { @@ -227,7 +227,7 @@ export const fetchPackageDependencies = async function ({ pkgInfo.versions[entry.version]["dependencies"] ).map((x): NameVersionPair => { return { - name: x[0] as ReverseDomainName, + name: x[0] as DomainName, version: x[1] as PkgVersion, }; }); diff --git a/src/types/reverse-domain-name.ts b/src/types/domain-name.ts similarity index 67% rename from src/types/reverse-domain-name.ts rename to src/types/domain-name.ts index 7066c075..417c33ae 100644 --- a/src/types/reverse-domain-name.ts +++ b/src/types/domain-name.ts @@ -1,15 +1,15 @@ import { Brand } from "ts-brand"; /** - * A string matching the format of a reverse domain name. + * A string matching the format of a domain name. * @example com.unity * @example com.my-company */ -export type ReverseDomainName = Brand; +export type DomainName = Brand; -const segmentRegex = /^(?!.*--)[^-][a-zA-Z0-9-]{0,20}[^-]$/; +const segmentRegex = /^(?!.*--|^-.*|.*-$)[a-zA-Z0-9-]+$/; -export const openUpmReverseDomainName = "com.openupm" as ReverseDomainName; +export const openUpmReverseDomainName = "com.openupm" as DomainName; function domainSegmentsIn(hostName: string): string[] { return hostName.split("."); @@ -22,7 +22,7 @@ function domainSegmentsIn(hostName: string): string[] { * @example registry.npmjs.org becomes org.npmjs. * @example my-school.ac.at becomes at.ac.my-school */ -export function namespaceFor(hostname: string): ReverseDomainName { +export function namespaceFor(hostname: string): DomainName { const segments = domainSegmentsIn(hostname); const namespaceSegments = (function () { const count = segments.length; @@ -42,11 +42,16 @@ export function namespaceFor(hostname: string): ReverseDomainName { // Domains with one extension such as registry.npmjs.org return segments.slice(count - 2); })(); - return namespaceSegments.reverse().join(".") as ReverseDomainName; + return namespaceSegments.reverse().join(".") as DomainName; } -export function isReverseDomainName(s: string): s is ReverseDomainName { +/** + * Checks if a string is a domain name. Only does basic syntax validation. + * Does not check for correct segment-count etc. + * @param s The string + */ +export function isDomainName(s: string): s is DomainName { const segments = domainSegmentsIn(s); - if (segments === null || segments.length < 2) return false; + if (segments === null || segments.length === 0) return false; return segments.every((segment) => segmentRegex.test(segment)); } diff --git a/src/types/global.ts b/src/types/global.ts index 01fac0ae..7b0cf1d6 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -1,10 +1,10 @@ import { NpmAuth } from "another-npm-registry-client"; import { IpAddress } from "./ip-address"; -import { ReverseDomainName } from "./reverse-domain-name"; +import { DomainName } from "./domain-name"; export type PkgVersion = string; -export type PkgName = ReverseDomainName | `${ReverseDomainName}@${PkgVersion}`; +export type PkgName = DomainName | `${DomainName}@${PkgVersion}`; export type Region = "us" | "cn"; @@ -32,7 +32,7 @@ export type Env = { upstream: boolean; upstreamRegistry: string; registry: string; - namespace: ReverseDomainName | IpAddress; + namespace: DomainName | IpAddress; editorVersion: string | null; region: Region; manifestPath: string; @@ -59,7 +59,7 @@ export type PkgVersionInfo = { version: string; unity?: string; unityRelease?: string; - dependencies?: Record; + dependencies?: Record; license?: string; displayName?: string; description?: string; @@ -74,7 +74,7 @@ export type PkgVersionInfo = { }; export type PkgInfo = { - name: ReverseDomainName; + name: DomainName; _id?: PkgName; _rev?: string; _attachments?: Record; @@ -90,12 +90,12 @@ export type PkgInfo = { }; export type NameVersionPair = { - name: ReverseDomainName; + name: DomainName; version: PkgVersion | undefined; }; export type Dependency = { - name: ReverseDomainName; + name: DomainName; version: PkgVersion; upstream: boolean; self: boolean; diff --git a/src/utils/env.ts b/src/utils/env.ts index 510074f1..8c0a83ab 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -7,10 +7,7 @@ import path from "path"; import fs from "fs"; import yaml from "yaml"; import { isIpAddress } from "../types/ip-address"; -import { - namespaceFor, - openUpmReverseDomainName, -} from "../types/reverse-domain-name"; +import { namespaceFor, openUpmReverseDomainName } from "../types/domain-name"; export const env: Env = {}; diff --git a/src/utils/pkg-name.ts b/src/utils/pkg-name.ts index 28a44083..595b3cf1 100644 --- a/src/utils/pkg-name.ts +++ b/src/utils/pkg-name.ts @@ -1,16 +1,16 @@ import { PkgName, PkgVersion } from "../types/global"; -import { ReverseDomainName } from "../types/reverse-domain-name"; +import { DomainName } from "../types/domain-name"; /** * Split package-name, which may include a version into the actual name of the * package and the version if it exists */ export const splitPkgName = function (pkgName: PkgName): { - name: ReverseDomainName; + name: DomainName; version: PkgVersion | undefined; } { const segments = pkgName.split("@"); - const name = segments[0] as ReverseDomainName; + const name = segments[0] as DomainName; const version = segments.length > 1 ? segments.slice(1, segments.length).join("@") @@ -23,16 +23,14 @@ export const splitPkgName = function (pkgName: PkgName): { * @param name The name of the package * @param version The version of the package */ -export const atVersion = ( - name: ReverseDomainName, - version: PkgVersion -): PkgName => `${name}@${version}`; +export const atVersion = (name: DomainName, version: PkgVersion): PkgName => + `${name}@${version}`; /** * Detect if the given package name is an internal package * @param name The name of the package */ -export const isInternalPackage = (name: ReverseDomainName): boolean => { +export const isInternalPackage = (name: DomainName): boolean => { const internals = [ "com.unity.ugui", "com.unity.2d.sprite", diff --git a/test/test-reverse-domain-name.ts b/test/test-domain-name.ts similarity index 69% rename from test/test-reverse-domain-name.ts rename to test/test-domain-name.ts index c440afa7..de41f906 100644 --- a/test/test-reverse-domain-name.ts +++ b/test/test-domain-name.ts @@ -1,11 +1,8 @@ import { describe } from "mocha"; -import { - isReverseDomainName, - namespaceFor, -} from "../src/types/reverse-domain-name"; +import { isDomainName, namespaceFor } from "../src/types/domain-name"; import should from "should"; -describe("reverse-domain-name", function () { +describe("domain-name", function () { describe("namespace", function () { [ ["unity.com", "com.unity"], @@ -21,19 +18,18 @@ describe("reverse-domain-name", function () { }); describe("validation", function () { [ + "com", "com.unity", "com.openupm", "at.ac.my-school", "dev.comradevanti123", ].forEach((s) => - it(`"${s}" should be reverse-domain-name`, () => - should(isReverseDomainName(s)).be.true()) + it(`"${s}" should be domain-name`, () => + should(isDomainName(s)).be.true()) ); [ "", " ", - // Single segments are not valid reverse-domain-names - "com", // Invalid characters "com.x💀x", // No double hyphens @@ -43,8 +39,8 @@ describe("reverse-domain-name", function () { // No trailing hyphens "com.unity-", ].forEach((s) => - it(`"${s}" should not be reverse-domain-name`, () => - should(isReverseDomainName(s)).be.false()) + it(`"${s}" should not be domain-name`, () => + should(isDomainName(s)).be.false()) ); }); }); diff --git a/test/types.ts b/test/types.ts index a34f1657..13d88d9e 100644 --- a/test/types.ts +++ b/test/types.ts @@ -1,12 +1,12 @@ import { Contact, PkgVersion } from "../src/types/global"; -import { ReverseDomainName } from "../src/types/reverse-domain-name"; +import { DomainName } from "../src/types/domain-name"; type Maintainer = { username: string; email: string }; export type SearchEndpointResult = { objects: Array<{ package: { - name: ReverseDomainName; + name: DomainName; description?: string; date: string; scope: "unscoped"; From c543825099dfea46def0a3a9d7a3678419255a98 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 23 Nov 2023 13:38:34 +0100 Subject: [PATCH 11/27] refactor: pkg-info builder helper Add a builder function for making pkg-info objects for tests. Includes validation logic and auto-sets derived properties, such as also setting the package _id to the name --- test/data-pkg-info.ts | 108 +++++++++++++++++++++++ test/test-cmd-add.ts | 166 +++++++++-------------------------- test/test-cmd-deps.ts | 62 ++++--------- test/test-cmd-view.ts | 160 ++++++++++++++------------------- test/test-registry-client.ts | 7 +- 5 files changed, 234 insertions(+), 269 deletions(-) create mode 100644 test/data-pkg-info.ts diff --git a/test/data-pkg-info.ts b/test/data-pkg-info.ts new file mode 100644 index 00000000..1fbefd80 --- /dev/null +++ b/test/data-pkg-info.ts @@ -0,0 +1,108 @@ +import { PkgInfo, PkgVersionInfo } from "../src/types/global"; +import assert from "assert"; +import { DomainName, isDomainName } from "../src/types/domain-name"; + +/** + * Builder class for {@link PkgVersionInfo} + */ +class VersionInfoBuilder { + readonly version: PkgVersionInfo; + + constructor(name: DomainName, version: string) { + this.version = { + name, + _id: `${name}@${version}`, + version, + dependencies: {}, + contributors: [], + }; + } + + /** + * Add a dependency to this version + * @param name The name of the dependency + * @param version The version + */ + addDependency(name: string, version: string): VersionInfoBuilder { + assert(isDomainName(name), `${name} is domain name`); + this.version.dependencies![name] = version; + return this; + } + + /** + * Set an arbitrary value on the version + * @param key The key + * @param value The value + */ + set< + TKey extends keyof Omit< + PkgVersionInfo, + "version" | "name" | "dependencies" | "_id" + > + >(key: TKey, value: PkgVersionInfo[TKey]): VersionInfoBuilder { + this.version[key] = value; + return this; + } +} + +/** + * Builder class for {@link PkgInfo} + */ +class PackageInfoBuilder { + readonly package: PkgInfo; + + constructor(name: string) { + assert(isDomainName(name), `${name} is domain name`); + this.package = { + name, + _id: name, + versions: {}, + time: {}, + users: {}, + _attachments: {}, + }; + } + + /** + * Adds a version to this package + * @param version The name of the version + * @param build A builder function + */ + addVersion( + version: string, + build?: (builder: VersionInfoBuilder) => unknown + ): PackageInfoBuilder { + const builder = new VersionInfoBuilder(this.package.name, version); + if (build !== undefined) build(builder); + this.package.versions[version] = builder.version; + this.package["dist-tags"] = { + latest: version, + }; + return this; + } + + set< + TKey extends keyof Omit< + PkgInfo, + "name" | "version" | "versions" | "dist-tags" | "_id" + > + >(key: TKey, value: PkgInfo[TKey]): PackageInfoBuilder { + this.package[key] = value; + return this; + } +} + +/** + * Helper for building a {@link PkgInfo} object. Does validation and also + * sets repeated properties for you + * @param name The name of the package + * @param build A builder function + */ +export function buildPackageInfo( + name: string, + build?: (builder: PackageInfoBuilder) => unknown +): PkgInfo { + const builder = new PackageInfoBuilder(name); + if (build !== undefined) build(builder); + return builder.package; +} diff --git a/test/test-cmd-add.ts b/test/test-cmd-add.ts index b24d2a0d..b41e3839 100644 --- a/test/test-cmd-add.ts +++ b/test/test-cmd-add.ts @@ -1,6 +1,6 @@ import "should"; import { add, AddOptions } from "../src/cmd-add"; -import { PkgInfo, PkgManifest } from "../src/types/global"; +import { PkgManifest } from "../src/types/global"; import { exampleRegistryUrl, registerMissingPackage, @@ -15,6 +15,7 @@ import { shouldHaveDependency, shouldHaveManifest, } from "./manifest-assertions"; +import { buildPackageInfo } from "./data-pkg-info"; describe("cmd-add.ts", function () { const options: AddOptions = { @@ -49,129 +50,46 @@ describe("cmd-add.ts", function () { }; describe("add", function () { let mockConsole: MockConsole = null!; - const remotePkgInfoA: PkgInfo = { - name: "com.base.package-a", - versions: { - "0.1.0": { - name: "com.base.package-a", - version: "0.1.0", - dependencies: {}, - }, - "1.0.0": { - name: "com.base.package-a", - version: "1.0.0", - dependencies: {}, - }, - }, - "dist-tags": { - latest: "1.0.0", - }, - time: {}, - }; - const remotePkgInfoB: PkgInfo = { - name: "com.base.package-b", - versions: { - "1.0.0": { - name: "com.base.package-b", - version: "1.0.0", - dependencies: {}, - }, - }, - "dist-tags": { - latest: "1.0.0", - }, - time: {}, - }; - const remotePkgInfoC: PkgInfo = { - name: "com.base.package-c", - versions: { - "1.0.0": { - name: "com.base.package-c", - version: "1.0.0", - dependencies: { - "com.base.package-d": "1.0.0", - "com.unity.modules.x": "1.0.0", - }, - }, - }, - "dist-tags": { - latest: "1.0.0", - }, - time: {}, - }; - const remotePkgInfoD: PkgInfo = { - name: "com.base.package-d", - versions: { - "1.0.0": { - name: "com.base.package-d", - version: "1.0.0", - dependencies: { - "com.upstream.package-up": "1.0.0", - }, - }, - }, - "dist-tags": { - latest: "1.0.0", - }, - time: {}, - }; - const remotePkgInfoWithLowerEditorVersion: PkgInfo = { - name: "com.base.package-with-lower-editor-version", - versions: { - "1.0.0": { - name: "com.base.package-with-lower-editor-version", - version: "1.0.0", - unity: "2019.1", - unityRelease: "0b1", - }, - }, - "dist-tags": { - latest: "1.0.0", - }, - time: {}, - }; - const remotePkgInfoWithHigherEditorVersion: PkgInfo = { - name: "com.base.package-with-higher-editor-version", - versions: { - "1.0.0": { - name: "com.base.package-with-higher-editor-version", - version: "1.0.0", - unity: "2020.2", - }, - }, - "dist-tags": { - latest: "1.0.0", - }, - time: {}, - }; - const remotePkgInfoWithWrongEditorVersion: PkgInfo = { - name: "com.base.package-with-wrong-editor-version", - versions: { - "1.0.0": { - name: "com.base.package-with-wrong-editor-version", - version: "1.0.0", - unity: "2020", - }, - }, - "dist-tags": { - latest: "1.0.0", - }, - time: {}, - }; - const remotePkgInfoUp: PkgInfo = { - name: "com.upstream.package-up", - versions: { - "1.0.0": { - name: "com.upstream.package-up", - version: "1.0.0", - dependencies: {}, - }, - }, - "dist-tags": { - latest: "1.0.0", - }, - time: {}, - }; + const remotePkgInfoA = buildPackageInfo("com.base.package-a", (pkg) => + pkg.addVersion("0.1.0").addVersion("1.0.0") + ); + const remotePkgInfoB = buildPackageInfo("com.base.package-b", (pkg) => + pkg.addVersion("1.0.0") + ); + const remotePkgInfoC = buildPackageInfo("com.base.package-c", (pkg) => + pkg.addVersion("1.0.0", (version) => + version + .addDependency("com.base.package-d", "1.0.0") + .addDependency("com.unity.modules.x", "1.0.0") + ) + ); + const remotePkgInfoD = buildPackageInfo("com.base.package-d", (pkg) => + pkg.addVersion("1.0.0", (version) => + version.addDependency("com.upstream.package-up", "1.0.0") + ) + ); + const remotePkgInfoWithLowerEditorVersion = buildPackageInfo( + "com.base.package-with-lower-editor-version", + (pkg) => + pkg.addVersion("1.0.0", (version) => + version.set("unity", "2019.1").set("unityRelease", "0b1") + ) + ); + const remotePkgInfoWithHigherEditorVersion = buildPackageInfo( + "com.base.package-with-higher-editor-version", + (pkg) => + pkg.addVersion("1.0.0", (version) => version.set("unity", "2020.2")) + ); + const remotePkgInfoWithWrongEditorVersion = buildPackageInfo( + "com.base.package-with-wrong-editor-version", + (pkg) => + pkg.addVersion("1.0.0", (version) => version.set("unity", "2020")) + ); + + const remotePkgInfoUp = buildPackageInfo("com.upstream.package-up", (pkg) => + pkg.addVersion("1.0.0") + ); + const defaultManifest: PkgManifest = { dependencies: {}, }; diff --git a/test/test-cmd-deps.ts b/test/test-cmd-deps.ts index 082e01a9..7bd2ee4b 100644 --- a/test/test-cmd-deps.ts +++ b/test/test-cmd-deps.ts @@ -8,9 +8,9 @@ import { startMockRegistry, stopMockRegistry, } from "./mock-registry"; -import { PkgInfo } from "../src/types/global"; import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; +import { buildPackageInfo } from "./data-pkg-info"; describe("cmd-deps.ts", function () { const options: DepsOptions = { @@ -22,52 +22,20 @@ describe("cmd-deps.ts", function () { describe("deps", function () { let mockConsole: MockConsole = null!; - const remotePkgInfoA: PkgInfo = { - name: "com.example.package-a", - versions: { - "1.0.0": { - name: "com.example.package-a", - version: "1.0.0", - dependencies: { - "com.example.package-b": "1.0.0", - }, - }, - }, - "dist-tags": { - latest: "1.0.0", - }, - time: {}, - }; - const remotePkgInfoB: PkgInfo = { - name: "com.example.package-b", - versions: { - "1.0.0": { - name: "com.example.package-b", - version: "1.0.0", - dependencies: { - "com.example.package-up": "1.0.0", - }, - }, - }, - "dist-tags": { - latest: "1.0.0", - }, - time: {}, - }; - const remotePkgInfoUp: PkgInfo = { - name: "com.example.package-up", - versions: { - "1.0.0": { - name: "com.example.package-up", - version: "1.0.0", - dependencies: {}, - }, - }, - "dist-tags": { - latest: "1.0.0", - }, - time: {}, - }; + const remotePkgInfoA = buildPackageInfo("com.example.package-a", (pkg) => + pkg.addVersion("1.0.0", (version) => + version.addDependency("com.example.package-b", "1.0.0") + ) + ); + const remotePkgInfoB = buildPackageInfo("com.example.package-b", (pkg) => + pkg.addVersion("1.0.0", (version) => + version.addDependency("com.example.package-up", "1.0.0") + ) + ); + const remotePkgInfoUp = buildPackageInfo("com.example.package-up", (pkg) => + pkg.addVersion("1.0.0") + ); + beforeEach(function () { removeWorkDir("test-openupm-cli"); createWorkDir("test-openupm-cli", { manifest: true }); diff --git a/test/test-cmd-view.ts b/test/test-cmd-view.ts index 377a0930..7796d94c 100644 --- a/test/test-cmd-view.ts +++ b/test/test-cmd-view.ts @@ -1,6 +1,5 @@ import "should"; import { view, ViewOptions } from "../src/cmd-view"; -import { PkgInfo } from "../src/types/global"; import { exampleRegistryUrl, registerMissingPackage, @@ -11,6 +10,7 @@ import { } from "./mock-registry"; import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; +import { buildPackageInfo } from "./data-pkg-info"; describe("cmd-view.ts", function () { const options: ViewOptions = { @@ -29,98 +29,72 @@ describe("cmd-view.ts", function () { describe("view", function () { let mockConsole: MockConsole = null!; - const remotePkgInfoA: PkgInfo = { - name: "com.example.package-a", - versions: { - "1.0.0": { - name: "com.example.package-a", - displayName: "Package A", - author: { - name: "batman", - }, - version: "1.0.0", - unity: "2018.4", - description: "A demo package", - keywords: [""], - category: "Unity", - dependencies: { - "com.example.package-a": "^1.0.0", - }, - gitHead: "5c141ecfac59c389090a07540f44c8ac5d07a729", - readmeFilename: "README.md", - _id: "com.example.package-a@1.0.0", - _nodeVersion: "12.13.1", - _npmVersion: "6.12.1", - dist: { - integrity: - "sha512-MAh44bur7HGyfbCXH9WKfaUNS67aRMfO0VAbLkr+jwseb1hJue/I1pKsC7PKksuBYh4oqoo9Jov1cBcvjVgjmA==", - shasum: "516957cac4249f95cafab0290335def7d9703db7", - tarball: - "https://cdn.example.com/com.example.package-a/com.example.package-a-1.0.0.tgz", - }, - contributors: [], - }, - }, - time: { - modified: "2019-11-28T18:51:58.123Z", - created: "2019-11-28T18:51:58.123Z", - "1.0.0": "2019-11-28T18:51:58.123Z", - }, - users: {}, - "dist-tags": { - latest: "1.0.0", - }, - _rev: "3-418f950115c32bd0", - _id: "com.example.package-a", - readme: "A demo package", - _attachments: {}, - }; - const remotePkgInfoUp: PkgInfo = { - name: "com.example.package-up", - versions: { - "1.0.0": { - name: "com.example.package-up", - displayName: "Package A", - author: { - name: "batman", - }, - version: "1.0.0", - unity: "2018.4", - description: "A demo package", - keywords: [""], - category: "Unity", - dependencies: { - "com.example.package-up": "^1.0.0", - }, - gitHead: "5c141ecfac59c389090a07540f44c8ac5d07a729", - readmeFilename: "README.md", - _id: "com.example.package-up@1.0.0", - _nodeVersion: "12.13.1", - _npmVersion: "6.12.1", - dist: { - integrity: - "sha512-MAh44bur7HGyfbCXH9WKfaUNS67aRMfO0VAbLkr+jwseb1hJue/I1pKsC7PKksuBYh4oqoo9Jov1cBcvjVgjmA==", - shasum: "516957cac4249f95cafab0290335def7d9703db7", - tarball: - "https://cdn.example.com/com.example.package-up/com.example.package-up-1.0.0.tgz", - }, - contributors: [], - }, - }, - time: { - modified: "2019-11-28T18:51:58.123Z", - created: "2019-11-28T18:51:58.123Z", - "1.0.0": "2019-11-28T18:51:58.123Z", - }, - users: {}, - "dist-tags": { - latest: "1.0.0", - }, - _rev: "3-418f950115c32bd0", - _id: "com.example.package-up", - readme: "A demo package", - _attachments: {}, - }; + const remotePkgInfoA = buildPackageInfo("com.example.package-a", (pkg) => + pkg + .set("time", { + modified: "2019-11-28T18:51:58.123Z", + created: "2019-11-28T18:51:58.123Z", + "1.0.0": "2019-11-28T18:51:58.123Z", + }) + .set("_rev", "3-418f950115c32bd0") + .set("readme", "A demo package") + .addVersion("1.0.0", (version) => + version + .set("displayName", "Package A") + .set("author", { name: "batman" }) + .set("unity", "2018.4") + .set("description", "A demo package") + .set("keywords", [""]) + .set("category", "Unity") + .set("gitHead", "5c141ecfac59c389090a07540f44c8ac5d07a729") + .set("readmeFilename", "README.md") + .set("_nodeVersion", "12.13.1") + .set("_npmVersion", "6.12.1") + .set("dist", { + integrity: + "sha512-MAh44bur7HGyfbCXH9WKfaUNS67aRMfO0VAbLkr+jwseb1hJue/I1pKsC7PKksuBYh4oqoo9Jov1cBcvjVgjmA==", + shasum: "516957cac4249f95cafab0290335def7d9703db7", + tarball: + "https://cdn.example.com/com.example.package-a/com.example.package-a-1.0.0.tgz", + }) + .addDependency("com.example.package-a", "^1.0.0") + ) + ); + + const remotePkgInfoUp = buildPackageInfo("com.example.package-up", (pkg) => + pkg + .set("time", { + modified: "2019-11-28T18:51:58.123Z", + created: "2019-11-28T18:51:58.123Z", + "1.0.0": "2019-11-28T18:51:58.123Z", + }) + .set("_rev", "3-418f950115c32bd0") + .set("readme", "A demo package") + .addVersion("1.0.0", (version) => + version + .set("displayName", "Package A") + .set("author", { + name: "batman", + }) + .set("unity", "2018.4") + .set("description", "A demo package") + .set("keywords", [""]) + .set("category", "Unity") + .addDependency("com.example.package-up", "^1.0.0") + .set("gitHead", "5c141ecfac59c389090a07540f44c8ac5d07a729") + .set("readmeFilename", "README.md") + .set("_nodeVersion", "12.13.1") + .set("_npmVersion", "6.12.1") + .set("dist", { + integrity: + "sha512-MAh44bur7HGyfbCXH9WKfaUNS67aRMfO0VAbLkr+jwseb1hJue/I1pKsC7PKksuBYh4oqoo9Jov1cBcvjVgjmA==", + shasum: "516957cac4249f95cafab0290335def7d9703db7", + tarball: + "https://cdn.example.com/com.example.package-up/com.example.package-up-1.0.0.tgz", + }) + ) + ); + beforeEach(function () { removeWorkDir("test-openupm-cli"); createWorkDir("test-openupm-cli", { manifest: true }); diff --git a/test/test-registry-client.ts b/test/test-registry-client.ts index 668253d4..f942dd1b 100644 --- a/test/test-registry-client.ts +++ b/test/test-registry-client.ts @@ -11,6 +11,7 @@ import { stopMockRegistry, } from "./mock-registry"; import should from "should"; +import { buildPackageInfo } from "./data-pkg-info"; describe("registry-client", function () { describe("fetchPackageInfo", function () { @@ -27,11 +28,7 @@ describe("registry-client", function () { { checkPath: false } ) ).should.be.ok(); - const pkgInfoRemote: PkgInfo = { - name: "package-a", - versions: {}, - time: {}, - }; + const pkgInfoRemote = buildPackageInfo("package-a"); registerRemotePkg(pkgInfoRemote); const info = await fetchPackageInfo("package-a"); should(info).deepEqual(pkgInfoRemote); From e2ca7d1c08dd7ffb5242c56419781caa7e3f6bc4 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 23 Nov 2023 14:03:33 +0100 Subject: [PATCH 12/27] refactor: pkg-manifest builder helper Builder does validation and allows for shorter test setups --- test/data-pkg-manifest.ts | 85 +++++++++++++++++++++++++++++++++++++++ test/test-cmd-add.ts | 82 ++++++++++--------------------------- test/test-cmd-remove.ts | 25 ++++-------- 3 files changed, 114 insertions(+), 78 deletions(-) create mode 100644 test/data-pkg-manifest.ts diff --git a/test/data-pkg-manifest.ts b/test/data-pkg-manifest.ts new file mode 100644 index 00000000..02d5561d --- /dev/null +++ b/test/data-pkg-manifest.ts @@ -0,0 +1,85 @@ +import { PkgManifest } from "../src/types/global"; +import assert from "assert"; +import { DomainName, isDomainName } from "../src/types/domain-name"; +import { exampleRegistryUrl } from "./mock-registry"; + +/** + * Builder class for {@link PkgManifest} + */ +class PkgManifestBuilder { + readonly manifest: PkgManifest; + + constructor() { + this.manifest = { + dependencies: {}, + }; + } + + /** + * Add a scope to the manifests scoped registry + * @param name The name of the scope + */ + addScope(name: string): PkgManifestBuilder { + assert(isDomainName(name), `${name} is domain name`); + + if (this.manifest.scopedRegistries === undefined) + this.manifest.scopedRegistries = [ + { + name: "example.com", + scopes: ["com.example" as DomainName], + url: exampleRegistryUrl, + }, + ]; + + const registry = this.manifest.scopedRegistries![0]; + registry.scopes = [name, ...registry.scopes]; + registry.scopes.sort(); + + return this; + } + + /** + * Add a testable to the manifest + * @param name The packages name + */ + addTestable(name: string): PkgManifestBuilder { + assert(isDomainName(name), `${name} is domain name`); + if (this.manifest.testables === undefined) this.manifest.testables = []; + this.manifest.testables.push(name); + this.manifest.testables.sort(); + return this; + } + + /** + * Add a dependency to the manifests scoped registry + * @param name The packages name + * @param version The packages version + * @param withScope Whether to also add the package to the scope + * @param testable Whether to also add the package to the testables + */ + addDependency( + name: string, + version: string, + withScope: boolean, + testable: boolean + ): PkgManifestBuilder { + assert(isDomainName(name), `${name} is domain name`); + if (withScope) this.addScope(name); + if (testable) this.addTestable(name); + this.manifest.dependencies[name] = version; + return this; + } +} + +/** + * Builder function for {@link PkgManifest}. All dependencies will be put + * into a default scoped-registry referencing an example registry + * @param build A builder function. + */ +export function buildPackageManifest( + build?: (builder: PkgManifestBuilder) => unknown +) { + const builder = new PkgManifestBuilder(); + if (build !== undefined) build(builder); + return builder.manifest; +} diff --git a/test/test-cmd-add.ts b/test/test-cmd-add.ts index b41e3839..5d6cc415 100644 --- a/test/test-cmd-add.ts +++ b/test/test-cmd-add.ts @@ -1,6 +1,5 @@ import "should"; import { add, AddOptions } from "../src/cmd-add"; -import { PkgManifest } from "../src/types/global"; import { exampleRegistryUrl, registerMissingPackage, @@ -16,6 +15,7 @@ import { shouldHaveManifest, } from "./manifest-assertions"; import { buildPackageInfo } from "./data-pkg-info"; +import { buildPackageManifest } from "./data-pkg-manifest"; describe("cmd-add.ts", function () { const options: AddOptions = { @@ -85,69 +85,31 @@ describe("cmd-add.ts", function () { (pkg) => pkg.addVersion("1.0.0", (version) => version.set("unity", "2020")) ); - const remotePkgInfoUp = buildPackageInfo("com.upstream.package-up", (pkg) => pkg.addVersion("1.0.0") ); - const defaultManifest: PkgManifest = { - dependencies: {}, - }; - const expectedManifestA: PkgManifest = { - dependencies: { - "com.base.package-a": "1.0.0", - }, - scopedRegistries: [ - { - name: "example.com", - scopes: ["com.base.package-a", "com.example"], - url: exampleRegistryUrl, - }, - ], - }; - const expectedManifestAB: PkgManifest = { - dependencies: { - "com.base.package-a": "1.0.0", - "com.base.package-b": "1.0.0", - }, - scopedRegistries: [ - { - name: "example.com", - scopes: ["com.base.package-a", "com.base.package-b", "com.example"], - url: exampleRegistryUrl, - }, - ], - }; - const expectedManifestC: PkgManifest = { - dependencies: { - "com.base.package-c": "1.0.0", - }, - scopedRegistries: [ - { - name: "example.com", - scopes: ["com.base.package-c", "com.base.package-d", "com.example"], - url: exampleRegistryUrl, - }, - ], - }; - const expectedManifestUpstream: PkgManifest = { - dependencies: { - "com.upstream.package-up": "1.0.0", - }, - }; - const expectedManifestTestable: PkgManifest = { - dependencies: { - "com.base.package-a": "1.0.0", - }, - scopedRegistries: [ - { - name: "example.com", - scopes: ["com.base.package-a", "com.example"], - url: exampleRegistryUrl, - }, - ], - testables: ["com.base.package-a"], - }; + const defaultManifest = buildPackageManifest(); + const expectedManifestA = buildPackageManifest((manifest) => + manifest.addDependency("com.base.package-a", "1.0.0", true, false) + ); + const expectedManifestAB = buildPackageManifest((manifest) => + manifest + .addDependency("com.base.package-a", "1.0.0", true, false) + .addDependency("com.base.package-b", "1.0.0", true, false) + ); + const expectedManifestC = buildPackageManifest((manifest) => + manifest + .addDependency("com.base.package-c", "1.0.0", true, false) + .addScope("com.base.package-d") + ); + const expectedManifestUpstream = buildPackageManifest((manifest) => + manifest.addDependency("com.upstream.package-up", "1.0.0", false, false) + ); + const expectedManifestTestable = buildPackageManifest((manifest) => + manifest.addDependency("com.base.package-a", "1.0.0", true, true) + ); + beforeEach(function () { removeWorkDir("test-openupm-cli"); createWorkDir("test-openupm-cli", { diff --git a/test/test-cmd-remove.ts b/test/test-cmd-remove.ts index a288a5bc..ef042520 100644 --- a/test/test-cmd-remove.ts +++ b/test/test-cmd-remove.ts @@ -1,6 +1,5 @@ import "should"; import { remove } from "../src/cmd-remove"; -import { PkgManifest } from "../src/types/global"; import { exampleRegistryUrl } from "./mock-registry"; import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; @@ -9,27 +8,17 @@ import { shouldHaveRegistryWithScopes, shouldNotHaveDependency, } from "./manifest-assertions"; +import { buildPackageManifest } from "./data-pkg-manifest"; describe("cmd-remove.ts", function () { describe("remove", function () { let mockConsole: MockConsole = null!; - const defaultManifest: PkgManifest = { - dependencies: { - "com.example.package-a": "1.0.0", - "com.example.package-b": "1.0.0", - }, - scopedRegistries: [ - { - name: "example.com", - scopes: [ - "com.example", - "com.example.package-a", - "com.example.package-b", - ], - url: exampleRegistryUrl, - }, - ], - }; + const defaultManifest = buildPackageManifest((manifest) => + manifest + .addDependency("com.example.package-a", "1.0.0", true, false) + .addDependency("com.example.package-b", "1.0.0", true, false) + ); + beforeEach(function () { removeWorkDir("test-openupm-cli"); createWorkDir("test-openupm-cli", { From 655f95b07c6be304470ee9f02ce000ef5807b08c Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 23 Nov 2023 14:03:49 +0100 Subject: [PATCH 13/27] fix: type error --- test/test-manifest.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test-manifest.ts b/test/test-manifest.ts index 075527b0..ffa4a277 100644 --- a/test/test-manifest.ts +++ b/test/test-manifest.ts @@ -11,6 +11,7 @@ import { shouldHaveNoManifest, shouldNotHaveAnyDependencies, } from "./manifest-assertions"; +import { DomainName } from "../src/types/domain-name"; describe("manifest", function () { let mockConsole: MockConsole = null!; @@ -73,7 +74,7 @@ describe("manifest", function () { ).should.be.ok(); const manifest = shouldHaveManifest(); shouldNotHaveAnyDependencies(manifest); - manifest.dependencies["some-pack"] = "1.0.0"; + manifest.dependencies["some-pack" as DomainName] = "1.0.0"; saveManifest(manifest).should.be.ok(); const manifest2 = shouldHaveManifest(); manifest2.should.be.deepEqual(manifest); From 45a90f860469509c3c5e9872dcf6618dc7440bde Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 23 Nov 2023 14:30:54 +0100 Subject: [PATCH 14/27] fix: type errors Fix errors from passing strings to functions expecting domain names. Fix by using name fields from packages or asserting validity using "as" --- test/manifest-assertions.ts | 1 + test/mock-registry.ts | 2 + test/test-cmd-add.ts | 124 +++++++++++++++++------------------ test/test-cmd-deps.ts | 47 ++++++------- test/test-cmd-remove.ts | 39 ++++++----- test/test-cmd-search.ts | 3 +- test/test-cmd-view.ts | 26 +++++--- test/test-pkg-name.ts | 28 +++++--- test/test-registry-client.ts | 12 ++-- 9 files changed, 154 insertions(+), 128 deletions(-) diff --git a/test/manifest-assertions.ts b/test/manifest-assertions.ts index c1ba79c8..c5d35b84 100644 --- a/test/manifest-assertions.ts +++ b/test/manifest-assertions.ts @@ -24,6 +24,7 @@ export function shouldHaveDependency( export function shouldNotHaveAnyDependencies(manifest: PkgManifest) { should(manifest.dependencies).be.empty(); } + export function shouldNotHaveDependency(manifest: PkgManifest, name: PkgName) { should(manifest.dependencies[name]).be.undefined(); } diff --git a/test/mock-registry.ts b/test/mock-registry.ts index 113fd0d8..de9cf5f8 100644 --- a/test/mock-registry.ts +++ b/test/mock-registry.ts @@ -1,9 +1,11 @@ import { PkgInfo, PkgName } from "../src/types/global"; import nock from "nock"; import { SearchEndpointResult } from "./types"; +import { DomainName } from "../src/types/domain-name"; export const unityRegistryUrl = "https://packages.unity.com"; export const exampleRegistryUrl = "http://example.com"; +export const exampleRegistryReverseDomain = "com.example" as DomainName; export function startMockRegistry() { if (!nock.isActive()) nock.activate(); diff --git a/test/test-cmd-add.ts b/test/test-cmd-add.ts index 5d6cc415..3705a040 100644 --- a/test/test-cmd-add.ts +++ b/test/test-cmd-add.ts @@ -16,8 +16,23 @@ import { } from "./manifest-assertions"; import { buildPackageInfo } from "./data-pkg-info"; import { buildPackageManifest } from "./data-pkg-manifest"; +import { DomainName } from "../src/types/domain-name"; +import { atVersion } from "../src/utils/pkg-name"; describe("cmd-add.ts", function () { + const packageMissing = "pkg-not-exist" as DomainName; + const packageA = "com.base.package-a" as DomainName; + const packageB = "com.base.package-b" as DomainName; + const packageC = "com.base.package-c" as DomainName; + const packageD = "com.base.package-d" as DomainName; + const packageUp = "com.upstream.package-up" as DomainName; + const packageLowerEditor = + "com.base.package-with-lower-editor-version" as DomainName; + const packageHigherEditor = + "com.base.package-with-higher-editor-version" as DomainName; + const packageWrongEditor = + "com.base.package-with-wrong-editor-version" as DomainName; + const options: AddOptions = { _global: { registry: exampleRegistryUrl, @@ -50,64 +65,63 @@ describe("cmd-add.ts", function () { }; describe("add", function () { let mockConsole: MockConsole = null!; - const remotePkgInfoA = buildPackageInfo("com.base.package-a", (pkg) => + + const remotePkgInfoA = buildPackageInfo(packageA, (pkg) => pkg.addVersion("0.1.0").addVersion("1.0.0") ); - const remotePkgInfoB = buildPackageInfo("com.base.package-b", (pkg) => + const remotePkgInfoB = buildPackageInfo(packageB, (pkg) => pkg.addVersion("1.0.0") ); - const remotePkgInfoC = buildPackageInfo("com.base.package-c", (pkg) => + const remotePkgInfoC = buildPackageInfo(packageC, (pkg) => pkg.addVersion("1.0.0", (version) => version - .addDependency("com.base.package-d", "1.0.0") + .addDependency(packageD, "1.0.0") .addDependency("com.unity.modules.x", "1.0.0") ) ); - const remotePkgInfoD = buildPackageInfo("com.base.package-d", (pkg) => - pkg.addVersion("1.0.0", (version) => - version.addDependency("com.upstream.package-up", "1.0.0") - ) + const remotePkgInfoD = buildPackageInfo(packageD, (pkg) => + pkg.addVersion("1.0.0", (version) => { + return version.addDependency(packageUp, "1.0.0"); + }) ); const remotePkgInfoWithLowerEditorVersion = buildPackageInfo( - "com.base.package-with-lower-editor-version", + packageLowerEditor, (pkg) => pkg.addVersion("1.0.0", (version) => version.set("unity", "2019.1").set("unityRelease", "0b1") ) ); const remotePkgInfoWithHigherEditorVersion = buildPackageInfo( - "com.base.package-with-higher-editor-version", + packageHigherEditor, (pkg) => pkg.addVersion("1.0.0", (version) => version.set("unity", "2020.2")) ); const remotePkgInfoWithWrongEditorVersion = buildPackageInfo( - "com.base.package-with-wrong-editor-version", + packageWrongEditor, (pkg) => pkg.addVersion("1.0.0", (version) => version.set("unity", "2020")) ); - const remotePkgInfoUp = buildPackageInfo("com.upstream.package-up", (pkg) => + const remotePkgInfoUp = buildPackageInfo(packageUp, (pkg) => pkg.addVersion("1.0.0") ); const defaultManifest = buildPackageManifest(); const expectedManifestA = buildPackageManifest((manifest) => - manifest.addDependency("com.base.package-a", "1.0.0", true, false) + manifest.addDependency(packageA, "1.0.0", true, false) ); const expectedManifestAB = buildPackageManifest((manifest) => manifest - .addDependency("com.base.package-a", "1.0.0", true, false) - .addDependency("com.base.package-b", "1.0.0", true, false) + .addDependency(packageA, "1.0.0", true, false) + .addDependency(packageB, "1.0.0", true, false) ); const expectedManifestC = buildPackageManifest((manifest) => - manifest - .addDependency("com.base.package-c", "1.0.0", true, false) - .addScope("com.base.package-d") + manifest.addDependency(packageC, "1.0.0", true, false).addScope(packageD) ); const expectedManifestUpstream = buildPackageManifest((manifest) => - manifest.addDependency("com.upstream.package-up", "1.0.0", false, false) + manifest.addDependency(packageUp, "1.0.0", false, false) ); const expectedManifestTestable = buildPackageManifest((manifest) => - manifest.addDependency("com.base.package-a", "1.0.0", true, true) + manifest.addDependency(packageA, "1.0.0", true, true) ); beforeEach(function () { @@ -126,7 +140,7 @@ describe("cmd-add.ts", function () { registerRemotePkg(remotePkgInfoWithHigherEditorVersion); registerRemotePkg(remotePkgInfoWithWrongEditorVersion); registerRemoteUpstreamPkg(remotePkgInfoUp); - registerMissingPackage("pkg-not-exist"); + registerMissingPackage(packageMissing); mockConsole = attachMockConsole(); }); @@ -137,7 +151,7 @@ describe("cmd-add.ts", function () { }); it("add pkg", async function () { - const retCode = await add("com.base.package-a", options); + const retCode = await add(packageA, options); retCode.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestA); @@ -145,7 +159,7 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@1.0.0", async function () { - const retCode = await add("com.base.package-a@1.0.0", options); + const retCode = await add(atVersion(packageA, "1.0.0"), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestA); @@ -153,7 +167,7 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@latest", async function () { - const retCode = await add("com.base.package-a@latest", options); + const retCode = await add(atVersion(packageA, "latest"), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestA); @@ -161,9 +175,9 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@0.1.0 then pkg@1.0.0", async function () { - const retCode1 = await add("com.base.package-a@0.1.0", options); + const retCode1 = await add(atVersion(packageA, "0.1.0"), options); retCode1.should.equal(0); - const retCode2 = await add("com.base.package-a@1.0.0", options); + const retCode2 = await add(atVersion(packageA, "1.0.0"), options); retCode2.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestA); @@ -171,9 +185,9 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add exited pkg version", async function () { - const retCode1 = await add("com.base.package-a@1.0.0", options); + const retCode1 = await add(atVersion(packageA, "1.0.0"), options); retCode1.should.equal(0); - const retCode2 = await add("com.base.package-a@1.0.0", options); + const retCode2 = await add(atVersion(packageA, "1.0.0"), options); retCode2.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestA); @@ -181,7 +195,7 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@not-exist-version", async function () { - const retCode = await add("com.base.package-a@2.0.0", options); + const retCode = await add(atVersion(packageA, "2.0.0"), options); retCode.should.equal(1); const manifest = shouldHaveManifest(); manifest.should.deepEqual(defaultManifest); @@ -192,43 +206,40 @@ describe("cmd-add.ts", function () { }); it("add pkg@http", async function () { const gitUrl = "https://github.com/yo/com.base.package-a"; - const retCode = await add("com.base.package-a@" + gitUrl, options); + const retCode = await add(atVersion(packageA, gitUrl), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); - shouldHaveDependency(manifest, "com.base.package-a", gitUrl); + shouldHaveDependency(manifest, packageA, gitUrl); mockConsole.hasLineIncluding("out", "added").should.be.ok(); mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@git", async function () { const gitUrl = "git@github.com:yo/com.base.package-a"; - const retCode = await add("com.base.package-a@" + gitUrl, options); + const retCode = await add(atVersion(packageA, gitUrl), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); - shouldHaveDependency(manifest, "com.base.package-a", gitUrl); + shouldHaveDependency(manifest, packageA, gitUrl); mockConsole.hasLineIncluding("out", "added").should.be.ok(); mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@file", async function () { const fileUrl = "file../yo/com.base.package-a"; - const retCode = await add("com.base.package-a@" + fileUrl, options); + const retCode = await add(atVersion(packageA, fileUrl), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); - shouldHaveDependency(manifest, "com.base.package-a", fileUrl); + shouldHaveDependency(manifest, packageA, fileUrl); mockConsole.hasLineIncluding("out", "added").should.be.ok(); mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg-not-exist", async function () { - const retCode = await add("pkg-not-exist", options); + const retCode = await add(packageMissing, options); retCode.should.equal(1); const manifest = shouldHaveManifest(); manifest.should.deepEqual(defaultManifest); mockConsole.hasLineIncluding("out", "package not found").should.be.ok(); }); it("add more than one pkgs", async function () { - const retCode = await add( - ["com.base.package-a", "com.base.package-b"], - options - ); + const retCode = await add([packageA, packageB], options); retCode.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestAB); @@ -241,7 +252,7 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg from upstream", async function () { - const retCode = await add("com.upstream.package-up", upstreamOptions); + const retCode = await add(packageUp, upstreamOptions); retCode.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestUpstream); @@ -251,14 +262,14 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg-not-exist from upstream", async function () { - const retCode = await add("pkg-not-exist", upstreamOptions); + const retCode = await add(packageMissing, upstreamOptions); retCode.should.equal(1); const manifest = shouldHaveManifest(); manifest.should.deepEqual(defaultManifest); mockConsole.hasLineIncluding("out", "package not found").should.be.ok(); }); it("add pkg with nested dependencies", async function () { - const retCode = await add("com.base.package-c@latest", upstreamOptions); + const retCode = await add(atVersion(packageC, "latest"), upstreamOptions); retCode.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestC); @@ -266,7 +277,7 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg with tests", async function () { - const retCode = await add("com.base.package-a", testableOptions); + const retCode = await add(packageA, testableOptions); retCode.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestTestable); @@ -274,47 +285,32 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg with lower editor version", async function () { - const retCode = await add( - "com.base.package-with-lower-editor-version", - testableOptions - ); + const retCode = await add(packageLowerEditor, testableOptions); retCode.should.equal(0); mockConsole.hasLineIncluding("out", "added").should.be.ok(); mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg with higher editor version", async function () { - const retCode = await add( - "com.base.package-with-higher-editor-version", - testableOptions - ); + const retCode = await add(packageHigherEditor, testableOptions); retCode.should.equal(1); mockConsole .hasLineIncluding("out", "requires 2020.2 but found 2019.2.13f1") .should.be.ok(); }); it("force add pkg with higher editor version", async function () { - const retCode = await add( - "com.base.package-with-higher-editor-version", - forceOptions - ); + const retCode = await add(packageHigherEditor, forceOptions); retCode.should.equal(0); mockConsole .hasLineIncluding("out", "requires 2020.2 but found 2019.2.13f1") .should.be.ok(); }); it("add pkg with wrong editor version", async function () { - const retCode = await add( - "com.base.package-with-wrong-editor-version", - testableOptions - ); + const retCode = await add(packageWrongEditor, testableOptions); retCode.should.equal(1); mockConsole.hasLineIncluding("out", "2020 is not valid").should.be.ok(); }); it("force add pkg with wrong editor version", async function () { - const retCode = await add( - "com.base.package-with-wrong-editor-version", - forceOptions - ); + const retCode = await add(packageWrongEditor, forceOptions); retCode.should.equal(0); mockConsole.hasLineIncluding("out", "2020 is not valid").should.be.ok(); }); diff --git a/test/test-cmd-deps.ts b/test/test-cmd-deps.ts index 7bd2ee4b..82e1d0cb 100644 --- a/test/test-cmd-deps.ts +++ b/test/test-cmd-deps.ts @@ -11,6 +11,8 @@ import { import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; import { buildPackageInfo } from "./data-pkg-info"; +import { DomainName } from "../src/types/domain-name"; +import { atVersion } from "../src/utils/pkg-name"; describe("cmd-deps.ts", function () { const options: DepsOptions = { @@ -42,7 +44,7 @@ describe("cmd-deps.ts", function () { startMockRegistry(); registerRemotePkg(remotePkgInfoA); registerRemotePkg(remotePkgInfoB); - registerMissingPackage("pkg-not-exist"); + registerMissingPackage("pkg-not-exist" as DomainName); registerRemoteUpstreamPkg(remotePkgInfoUp); mockConsole = attachMockConsole(); @@ -53,53 +55,52 @@ describe("cmd-deps.ts", function () { mockConsole.detach(); }); it("deps pkg", async function () { - const retCode = await deps("com.example.package-a", options); + const retCode = await deps(remotePkgInfoA.name, options); retCode.should.equal(0); - mockConsole - .hasLineIncluding("out", "com.example.package-b") - .should.be.ok(); + mockConsole.hasLineIncluding("out", remotePkgInfoB.name).should.be.ok(); }); it("deps pkg --deep", async function () { - const retCode = await deps("com.example.package-a", { + const retCode = await deps(remotePkgInfoA.name, { ...options, deep: true, }); retCode.should.equal(0); - mockConsole - .hasLineIncluding("out", "com.example.package-b") - .should.be.ok(); - mockConsole - .hasLineIncluding("out", "com.example.package-up") - .should.be.ok(); + mockConsole.hasLineIncluding("out", remotePkgInfoB.name).should.be.ok(); + mockConsole.hasLineIncluding("out", remotePkgInfoUp.name).should.be.ok(); }); it("deps pkg@latest", async function () { - const retCode = await deps("com.example.package-a@latest", options); + const retCode = await deps( + atVersion(remotePkgInfoA.name, "latest"), + options + ); retCode.should.equal(0); - mockConsole - .hasLineIncluding("out", "com.example.package-b") - .should.be.ok(); + mockConsole.hasLineIncluding("out", remotePkgInfoB.name).should.be.ok(); }); it("deps pkg@1.0.0", async function () { - const retCode = await deps("com.example.package-a@1.0.0", options); + const retCode = await deps( + atVersion(remotePkgInfoA.name, "1.0.0"), + options + ); retCode.should.equal(0); - mockConsole - .hasLineIncluding("out", "com.example.package-b") - .should.be.ok(); + mockConsole.hasLineIncluding("out", remotePkgInfoB.name).should.be.ok(); }); it("deps pkg@not-exist-version", async function () { - const retCode = await deps("com.example.package-a@2.0.0", options); + const retCode = await deps( + atVersion(remotePkgInfoA.name, "2.0.0"), + options + ); retCode.should.equal(0); mockConsole .hasLineIncluding("out", "is not a valid choice") .should.be.ok(); }); it("deps pkg-not-exist", async function () { - const retCode = await deps("pkg-not-exist", options); + const retCode = await deps("pkg-not-exist" as DomainName, options); retCode.should.equal(0); mockConsole.hasLineIncluding("out", "not found").should.be.ok(); }); it("deps pkg upstream", async function () { - const retCode = await deps("com.example.package-up", options); + const retCode = await deps(remotePkgInfoUp.name, options); retCode.should.equal(0); }); }); diff --git a/test/test-cmd-remove.ts b/test/test-cmd-remove.ts index ef042520..8775d205 100644 --- a/test/test-cmd-remove.ts +++ b/test/test-cmd-remove.ts @@ -1,6 +1,9 @@ import "should"; import { remove } from "../src/cmd-remove"; -import { exampleRegistryUrl } from "./mock-registry"; +import { + exampleRegistryReverseDomain, + exampleRegistryUrl, +} from "./mock-registry"; import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; import { @@ -9,14 +12,21 @@ import { shouldNotHaveDependency, } from "./manifest-assertions"; import { buildPackageManifest } from "./data-pkg-manifest"; +import { DomainName } from "../src/types/domain-name"; +import { atVersion } from "../src/utils/pkg-name"; + +const packageA = "com.example.package-a" as DomainName; +const packageB = "com.example.package-b" as DomainName; +const missingPackage = "pkg-not-exist" as DomainName; describe("cmd-remove.ts", function () { describe("remove", function () { let mockConsole: MockConsole = null!; + const defaultManifest = buildPackageManifest((manifest) => manifest - .addDependency("com.example.package-a", "1.0.0", true, false) - .addDependency("com.example.package-b", "1.0.0", true, false) + .addDependency(packageA, "1.0.0", true, false) + .addDependency(packageB, "1.0.0", true, false) ); beforeEach(function () { @@ -37,13 +47,13 @@ describe("cmd-remove.ts", function () { chdir: getWorkDir("test-openupm-cli"), }, }; - const retCode = await remove("com.example.package-a", options); + const retCode = await remove(packageA, options); retCode.should.equal(0); const manifest = shouldHaveManifest(); - shouldNotHaveDependency(manifest, "com.example.package-a"); + shouldNotHaveDependency(manifest, packageA); shouldHaveRegistryWithScopes(manifest, [ - "com.example", - "com.example.package-b", + exampleRegistryReverseDomain, + packageB, ]); mockConsole.hasLineIncluding("out", "removed ").should.be.ok(); mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); @@ -55,7 +65,7 @@ describe("cmd-remove.ts", function () { chdir: getWorkDir("test-openupm-cli"), }, }; - const retCode = await remove("com.example.package-a@1.0.0", options); + const retCode = await remove(atVersion(packageA, "1.0.0"), options); retCode.should.equal(1); const manifest = shouldHaveManifest(); manifest.should.deepEqual(defaultManifest); @@ -68,7 +78,7 @@ describe("cmd-remove.ts", function () { chdir: getWorkDir("test-openupm-cli"), }, }; - const retCode = await remove("pkg-not-exist", options); + const retCode = await remove(missingPackage, options); retCode.should.equal(1); const manifest = shouldHaveManifest(); manifest.should.deepEqual(defaultManifest); @@ -81,15 +91,12 @@ describe("cmd-remove.ts", function () { chdir: getWorkDir("test-openupm-cli"), }, }; - const retCode = await remove( - ["com.example.package-a", "com.example.package-b"], - options - ); + const retCode = await remove([packageA, packageB], options); retCode.should.equal(0); const manifest = shouldHaveManifest(); - shouldNotHaveDependency(manifest, "com.example.package-a"); - shouldNotHaveDependency(manifest, "com.example.package-b"); - shouldHaveRegistryWithScopes(manifest, ["com.example"]); + shouldNotHaveDependency(manifest, packageA); + shouldNotHaveDependency(manifest, packageB); + shouldHaveRegistryWithScopes(manifest, [exampleRegistryReverseDomain]); mockConsole .hasLineIncluding("out", "removed com.example.package-a") .should.be.ok(); diff --git a/test/test-cmd-search.ts b/test/test-cmd-search.ts index 4e0d97eb..3a2548da 100644 --- a/test/test-cmd-search.ts +++ b/test/test-cmd-search.ts @@ -11,6 +11,7 @@ import { import { SearchEndpointResult } from "./types"; import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; +import { DomainName } from "../src/types/domain-name"; describe("cmd-search.ts", function () { let mockConsole: MockConsole = null!; @@ -37,7 +38,7 @@ describe("cmd-search.ts", function () { objects: [ { package: { - name: "com.example.package-a", + name: "com.example.package-a" as DomainName, scope: "unscoped", version: "1.0.0", description: "A demo package", diff --git a/test/test-cmd-view.ts b/test/test-cmd-view.ts index 7796d94c..3db30da2 100644 --- a/test/test-cmd-view.ts +++ b/test/test-cmd-view.ts @@ -11,6 +11,12 @@ import { import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; import { buildPackageInfo } from "./data-pkg-info"; +import { DomainName } from "../src/types/domain-name"; +import { atVersion } from "../src/utils/pkg-name"; + +const packageA = "com.example.package-a" as DomainName; +const packageUp = "com.example.package-up" as DomainName; +const packageMissing = "pkg-not-exist" as DomainName; describe("cmd-view.ts", function () { const options: ViewOptions = { @@ -29,7 +35,7 @@ describe("cmd-view.ts", function () { describe("view", function () { let mockConsole: MockConsole = null!; - const remotePkgInfoA = buildPackageInfo("com.example.package-a", (pkg) => + const remotePkgInfoA = buildPackageInfo(packageA, (pkg) => pkg .set("time", { modified: "2019-11-28T18:51:58.123Z", @@ -57,11 +63,11 @@ describe("cmd-view.ts", function () { tarball: "https://cdn.example.com/com.example.package-a/com.example.package-a-1.0.0.tgz", }) - .addDependency("com.example.package-a", "^1.0.0") + .addDependency(packageA, "^1.0.0") ) ); - const remotePkgInfoUp = buildPackageInfo("com.example.package-up", (pkg) => + const remotePkgInfoUp = buildPackageInfo(packageUp, (pkg) => pkg .set("time", { modified: "2019-11-28T18:51:58.123Z", @@ -80,7 +86,7 @@ describe("cmd-view.ts", function () { .set("description", "A demo package") .set("keywords", [""]) .set("category", "Unity") - .addDependency("com.example.package-up", "^1.0.0") + .addDependency(packageUp, "^1.0.0") .set("gitHead", "5c141ecfac59c389090a07540f44c8ac5d07a729") .set("readmeFilename", "README.md") .set("_nodeVersion", "12.13.1") @@ -100,7 +106,7 @@ describe("cmd-view.ts", function () { createWorkDir("test-openupm-cli", { manifest: true }); startMockRegistry(); registerRemotePkg(remotePkgInfoA); - registerMissingPackage("pkg-not-exist"); + registerMissingPackage(packageMissing); registerRemoteUpstreamPkg(remotePkgInfoUp); mockConsole = attachMockConsole(); }); @@ -110,31 +116,31 @@ describe("cmd-view.ts", function () { mockConsole.detach(); }); it("view pkg", async function () { - const retCode = await view("com.example.package-a", options); + const retCode = await view(packageA, options); retCode.should.equal(0); mockConsole .hasLineIncluding("out", "com.example.package-a@1.0.0") .should.be.ok(); }); it("view pkg@1.0.0", async function () { - const retCode = await view("com.example.package-a@1.0.0", options); + const retCode = await view(atVersion(packageA, "1.0.0"), options); retCode.should.equal(1); mockConsole.hasLineIncluding("out", "please replace").should.be.ok(); }); it("view pkg-not-exist", async function () { - const retCode = await view("pkg-not-exist", options); + const retCode = await view(packageMissing, options); retCode.should.equal(1); mockConsole.hasLineIncluding("out", "package not found").should.be.ok(); }); it("view pkg from upstream", async function () { - const retCode = await view("com.example.package-up", upstreamOptions); + const retCode = await view(packageUp, upstreamOptions); retCode.should.equal(0); mockConsole .hasLineIncluding("out", "com.example.package-up@1.0.0") .should.be.ok(); }); it("view pkg-not-exist from upstream", async function () { - const retCode = await view("pkg-not-exist", upstreamOptions); + const retCode = await view(packageMissing, upstreamOptions); retCode.should.equal(1); mockConsole.hasLineIncluding("out", "package not found").should.be.ok(); }); diff --git a/test/test-pkg-name.ts b/test/test-pkg-name.ts index 592efd26..ded07ee8 100644 --- a/test/test-pkg-name.ts +++ b/test/test-pkg-name.ts @@ -1,41 +1,47 @@ import "assert"; import "should"; import { isInternalPackage, splitPkgName } from "../src/utils/pkg-name"; +import { PkgName } from "../src/types/global"; +import { DomainName } from "../src/types/domain-name"; describe("pkg-name.ts", function () { describe("splitPkgName", function () { it("pkg@version", function () { - splitPkgName("pkg@1.0.0").should.deepEqual({ + splitPkgName("pkg@1.0.0" as PkgName).should.deepEqual({ name: "pkg", version: "1.0.0", }); }); it("pkg@latest", function () { - splitPkgName("pkg@latest").should.deepEqual({ + splitPkgName("pkg@latest" as PkgName).should.deepEqual({ name: "pkg", version: "latest", }); }); it("pkg", function () { - splitPkgName("pkg").should.deepEqual({ + splitPkgName("pkg" as PkgName).should.deepEqual({ name: "pkg", version: undefined, }); }); it("pkg@file", function () { - splitPkgName("pkg@file:../pkg").should.deepEqual({ + splitPkgName("pkg@file:../pkg" as PkgName).should.deepEqual({ name: "pkg", version: "file:../pkg", }); }); it("pkg@http", function () { - splitPkgName("pkg@https://github.com/owner/pkg").should.deepEqual({ + splitPkgName( + "pkg@https://github.com/owner/pkg" as PkgName + ).should.deepEqual({ name: "pkg", version: "https://github.com/owner/pkg", }); }); it("pkg@git", function () { - splitPkgName("pkg@git@github.com:owner/pkg.git").should.deepEqual({ + splitPkgName( + "pkg@git@github.com:owner/pkg.git" as PkgName + ).should.deepEqual({ name: "pkg", version: "git@github.com:owner/pkg.git", }); @@ -43,13 +49,17 @@ describe("pkg-name.ts", function () { }); describe("isInternalPackage", function () { it("test com.otherorg.software", function () { - isInternalPackage("com.otherorg.software").should.not.be.ok(); + isInternalPackage( + "com.otherorg.software" as DomainName + ).should.not.be.ok(); }); it("test com.unity.ugui", function () { - isInternalPackage("com.unity.ugui").should.be.ok(); + isInternalPackage("com.unity.ugui" as DomainName).should.be.ok(); }); it("test com.unity.modules.tilemap", function () { - isInternalPackage("com.unity.modules.tilemap").should.be.ok(); + isInternalPackage( + "com.unity.modules.tilemap" as DomainName + ).should.be.ok(); }); }); }); diff --git a/test/test-registry-client.ts b/test/test-registry-client.ts index f942dd1b..dffa5fbc 100644 --- a/test/test-registry-client.ts +++ b/test/test-registry-client.ts @@ -2,7 +2,6 @@ import "assert"; import "should"; import { parseEnv } from "../src/utils/env"; import { fetchPackageInfo } from "../src/registry-client"; -import { PkgInfo } from "../src/types/global"; import { exampleRegistryUrl, registerMissingPackage, @@ -12,6 +11,9 @@ import { } from "./mock-registry"; import should from "should"; import { buildPackageInfo } from "./data-pkg-info"; +import { DomainName } from "../src/types/domain-name"; + +const packageA = "package-a" as DomainName; describe("registry-client", function () { describe("fetchPackageInfo", function () { @@ -28,9 +30,9 @@ describe("registry-client", function () { { checkPath: false } ) ).should.be.ok(); - const pkgInfoRemote = buildPackageInfo("package-a"); + const pkgInfoRemote = buildPackageInfo(packageA); registerRemotePkg(pkgInfoRemote); - const info = await fetchPackageInfo("package-a"); + const info = await fetchPackageInfo(packageA); should(info).deepEqual(pkgInfoRemote); }); it("404", async function () { @@ -41,8 +43,8 @@ describe("registry-client", function () { ) ).should.be.ok(); - registerMissingPackage("package-a"); - const info = await fetchPackageInfo("package-a"); + registerMissingPackage(packageA); + const info = await fetchPackageInfo(packageA); (info === undefined).should.be.ok(); }); }); From 4a36bc7d88a51bf7aad46c8b0904d65835c0f496 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 23 Nov 2023 14:35:32 +0100 Subject: [PATCH 15/27] fix. incorrect import syntax --- src/types/another-npm-registry-client.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/types/another-npm-registry-client.d.ts b/src/types/another-npm-registry-client.d.ts index 1cdce144..af215530 100644 --- a/src/types/another-npm-registry-client.d.ts +++ b/src/types/another-npm-registry-client.d.ts @@ -1,5 +1,7 @@ +import { PkgInfo } from "./global"; +import request from "request"; + declare module "another-npm-registry-client" { - import request from "request"; export type NpmAuth = | { username: string; From e90704d3687ee5d621fbe127e531563a28e1923f Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 23 Nov 2023 14:42:15 +0100 Subject: [PATCH 16/27] fix: type from incorrect package The response object used by npm-registry-fetch is node-fetch Response, not request Response --- package-lock.json | 48 ++++++++++++++-------- package.json | 3 +- src/types/another-npm-registry-client.d.ts | 4 +- src/types/npm-registry-fetch.d.ts | 2 +- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 78a90b5a..b709e30b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,10 +43,11 @@ "@types/lodash": "^4.14.199", "@types/mocha": "^10.0.3", "@types/node": "^14.18.63", + "@types/node-fetch": "^2.6.9", "@types/npmlog": "^4.1.4", "@types/pkginfo": "^0.4.1", "@types/promptly": "^3.0.3", - "@types/request": "^2.48.11", + "@types/request": "^2.48.12", "@types/test-console": "^2.0.1", "@types/update-notifier": "^6.0.5", "@typescript-eslint/eslint-plugin": "^6.8.0", @@ -1286,9 +1287,10 @@ } }, "node_modules/@types/caseless": { - "version": "0.12.4", - "dev": true, - "license": "MIT" + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "dev": true }, "node_modules/@types/cli-table": { "version": "0.3.2", @@ -1358,9 +1360,10 @@ "license": "MIT" }, "node_modules/@types/node-fetch": { - "version": "2.6.6", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*", "form-data": "^4.0.0" @@ -1428,9 +1431,10 @@ } }, "node_modules/@types/request": { - "version": "2.48.11", + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", "dev": true, - "license": "MIT", "dependencies": { "@types/caseless": "*", "@types/node": "*", @@ -1440,8 +1444,9 @@ }, "node_modules/@types/request/node_modules/form-data": { "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", "dev": true, - "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -1475,9 +1480,10 @@ "license": "MIT" }, "node_modules/@types/tough-cookie": { - "version": "4.0.4", - "dev": true, - "license": "MIT" + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true }, "node_modules/@types/update-notifier": { "version": "6.0.5", @@ -11330,7 +11336,9 @@ "version": "2.0.0" }, "@types/caseless": { - "version": "0.12.4", + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", "dev": true }, "@types/cli-table": { @@ -11390,7 +11398,9 @@ "dev": true }, "@types/node-fetch": { - "version": "2.6.6", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==", "dev": true, "requires": { "@types/node": "*", @@ -11450,7 +11460,9 @@ } }, "@types/request": { - "version": "2.48.11", + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", "dev": true, "requires": { "@types/caseless": "*", @@ -11461,6 +11473,8 @@ "dependencies": { "form-data": { "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", "dev": true, "requires": { "asynckit": "^0.4.0", @@ -11490,7 +11504,9 @@ "dev": true }, "@types/tough-cookie": { - "version": "4.0.4", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, "@types/update-notifier": { diff --git a/package.json b/package.json index e9ea5b6a..0b0902eb 100644 --- a/package.json +++ b/package.json @@ -43,10 +43,11 @@ "@types/lodash": "^4.14.199", "@types/mocha": "^10.0.3", "@types/node": "^14.18.63", + "@types/node-fetch": "^2.6.9", "@types/npmlog": "^4.1.4", "@types/pkginfo": "^0.4.1", "@types/promptly": "^3.0.3", - "@types/request": "^2.48.11", + "@types/request": "^2.48.12", "@types/test-console": "^2.0.1", "@types/update-notifier": "^6.0.5", "@typescript-eslint/eslint-plugin": "^6.8.0", diff --git a/src/types/another-npm-registry-client.d.ts b/src/types/another-npm-registry-client.d.ts index af215530..b74b52ad 100644 --- a/src/types/another-npm-registry-client.d.ts +++ b/src/types/another-npm-registry-client.d.ts @@ -1,5 +1,5 @@ +import { Response } from "request"; import { PkgInfo } from "./global"; -import request from "request"; declare module "another-npm-registry-client" { export type NpmAuth = @@ -26,7 +26,7 @@ declare module "another-npm-registry-client" { error: Error | null, data: TData, raw: string, - res: request.Response + res: Response ) => void; export default class RegClient { diff --git a/src/types/npm-registry-fetch.d.ts b/src/types/npm-registry-fetch.d.ts index 3dedc984..ea3f1149 100644 --- a/src/types/npm-registry-fetch.d.ts +++ b/src/types/npm-registry-fetch.d.ts @@ -1,4 +1,4 @@ -import { Response } from "request"; +import { Response } from "node-fetch"; declare module "npm-registry-fetch" { class HttpErrorBase extends Error { From 6a24a1a4c08c1f74661efc509b98aca2fc8c498b Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 23 Nov 2023 14:45:12 +0100 Subject: [PATCH 17/27] style: reformat file --- test/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tsconfig.json b/test/tsconfig.json index 39fe5ba8..1781818b 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -105,7 +105,7 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - //"skipLibCheck": true /* Skip type checking all .d.ts files. */ + // "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, "include": [ "./**/*" From f89b920ffa621d427090ec18d3e80cb097bf09cc Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 23 Nov 2023 15:24:55 +0100 Subject: [PATCH 18/27] refactor: move function Function acts on domain-types and so should be in a different file --- src/registry-client.ts | 4 ++-- src/types/domain-name.ts | 15 +++++++++++++++ src/utils/pkg-name.ts | 15 --------------- test/test-domain-name.ts | 22 +++++++++++++++++++++- test/test-pkg-name.ts | 18 +----------------- 5 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/registry-client.ts b/src/registry-client.ts index 8f536b65..eb5b9e1f 100644 --- a/src/registry-client.ts +++ b/src/registry-client.ts @@ -17,10 +17,10 @@ import { Registry, } from "./types/global"; import { env } from "./utils/env"; -import { atVersion, isInternalPackage } from "./utils/pkg-name"; +import { atVersion } from "./utils/pkg-name"; import _ from "lodash"; import { tryGetLatestVersion } from "./utils/pkg-info"; -import { DomainName } from "./types/domain-name"; +import { DomainName, isInternalPackage } from "./types/domain-name"; export type NpmClient = { rawClient: RegClient; diff --git a/src/types/domain-name.ts b/src/types/domain-name.ts index 417c33ae..08762bfc 100644 --- a/src/types/domain-name.ts +++ b/src/types/domain-name.ts @@ -55,3 +55,18 @@ export function isDomainName(s: string): s is DomainName { if (segments === null || segments.length === 0) return false; return segments.every((segment) => segmentRegex.test(segment)); } + +/** + * Detect if the given package name is an internal package + * @param name The name of the package + */ +export const isInternalPackage = (name: DomainName): boolean => { + const internals = [ + "com.unity.ugui", + "com.unity.2d.sprite", + "com.unity.2d.tilemap", + "com.unity.package-manager-ui", + "com.unity.ugui", + ]; + return /com.unity.modules/i.test(name) || internals.includes(name); +}; diff --git a/src/utils/pkg-name.ts b/src/utils/pkg-name.ts index 595b3cf1..65f1b882 100644 --- a/src/utils/pkg-name.ts +++ b/src/utils/pkg-name.ts @@ -25,18 +25,3 @@ export const splitPkgName = function (pkgName: PkgName): { */ export const atVersion = (name: DomainName, version: PkgVersion): PkgName => `${name}@${version}`; - -/** - * Detect if the given package name is an internal package - * @param name The name of the package - */ -export const isInternalPackage = (name: DomainName): boolean => { - const internals = [ - "com.unity.ugui", - "com.unity.2d.sprite", - "com.unity.2d.tilemap", - "com.unity.package-manager-ui", - "com.unity.ugui", - ]; - return /com.unity.modules/i.test(name) || internals.includes(name); -}; diff --git a/test/test-domain-name.ts b/test/test-domain-name.ts index de41f906..80d6f570 100644 --- a/test/test-domain-name.ts +++ b/test/test-domain-name.ts @@ -1,5 +1,10 @@ import { describe } from "mocha"; -import { isDomainName, namespaceFor } from "../src/types/domain-name"; +import { + DomainName, + isDomainName, + isInternalPackage, + namespaceFor, +} from "../src/types/domain-name"; import should from "should"; describe("domain-name", function () { @@ -43,4 +48,19 @@ describe("domain-name", function () { should(isDomainName(s)).be.false()) ); }); + describe("internal package", function () { + it("test com.otherorg.software", function () { + isInternalPackage( + "com.otherorg.software" as DomainName + ).should.not.be.ok(); + }); + it("test com.unity.ugui", function () { + isInternalPackage("com.unity.ugui" as DomainName).should.be.ok(); + }); + it("test com.unity.modules.tilemap", function () { + isInternalPackage( + "com.unity.modules.tilemap" as DomainName + ).should.be.ok(); + }); + }); }); diff --git a/test/test-pkg-name.ts b/test/test-pkg-name.ts index ded07ee8..8e7c602b 100644 --- a/test/test-pkg-name.ts +++ b/test/test-pkg-name.ts @@ -1,8 +1,7 @@ import "assert"; import "should"; -import { isInternalPackage, splitPkgName } from "../src/utils/pkg-name"; +import { splitPkgName } from "../src/utils/pkg-name"; import { PkgName } from "../src/types/global"; -import { DomainName } from "../src/types/domain-name"; describe("pkg-name.ts", function () { describe("splitPkgName", function () { @@ -47,19 +46,4 @@ describe("pkg-name.ts", function () { }); }); }); - describe("isInternalPackage", function () { - it("test com.otherorg.software", function () { - isInternalPackage( - "com.otherorg.software" as DomainName - ).should.not.be.ok(); - }); - it("test com.unity.ugui", function () { - isInternalPackage("com.unity.ugui" as DomainName).should.be.ok(); - }); - it("test com.unity.modules.tilemap", function () { - isInternalPackage( - "com.unity.modules.tilemap" as DomainName - ).should.be.ok(); - }); - }); }); From fd029b198c8db3ace3032cf4a2cb948dca43e94b Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 23 Nov 2023 15:38:02 +0100 Subject: [PATCH 19/27] refactor: add package-url type Introduce package-url type which represents an url pointing to a package. Also add a few tests --- src/cmd-add.ts | 5 ++--- src/types/global.ts | 3 ++- src/types/package-url.ts | 20 ++++++++++++++++++++ src/utils/pkg-version.ts | 10 ---------- test/test-package-url.ts | 23 +++++++++++++++++++++++ 5 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 src/types/package-url.ts delete mode 100644 src/utils/pkg-version.ts create mode 100644 test/test-package-url.ts diff --git a/src/cmd-add.ts b/src/cmd-add.ts index 9b409859..a4625437 100644 --- a/src/cmd-add.ts +++ b/src/cmd-add.ts @@ -1,12 +1,11 @@ import log from "./logger"; import url from "url"; -import { isUrlVersion } from "./utils/pkg-version"; +import { isPackageUrl } from "./types/package-url"; import { atVersion, splitPkgName } from "./utils/pkg-name"; import { GlobalOptions, PkgName, ScopedRegistry } from "./types/global"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { loadManifest, saveManifest } from "./utils/manifest"; import { env, parseEnv } from "./utils/env"; - import { compareEditorVersion, tryParseEditorVersion, @@ -78,7 +77,7 @@ const _add = async function ({ } // packages that added to scope registry const pkgsInScope: PkgName[] = []; - if (version === undefined || !isUrlVersion(version)) { + if (version === undefined || !isPackageUrl(version)) { // verify name let pkgInfo = await fetchPackageInfo(name); if (!pkgInfo && env.upstream) { diff --git a/src/types/global.ts b/src/types/global.ts index 7b0cf1d6..c61dae22 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -1,8 +1,9 @@ import { NpmAuth } from "another-npm-registry-client"; import { IpAddress } from "./ip-address"; import { DomainName } from "./domain-name"; +import { PackageUrl } from "./package-url"; -export type PkgVersion = string; +export type PkgVersion = PackageUrl | string; export type PkgName = DomainName | `${DomainName}@${PkgVersion}`; diff --git a/src/types/package-url.ts b/src/types/package-url.ts new file mode 100644 index 00000000..d831aec9 --- /dev/null +++ b/src/types/package-url.ts @@ -0,0 +1,20 @@ +import { Brand } from "ts-brand"; +import { PkgVersion } from "./global"; + +/** + * A string of an url pointing to a local or remote package + */ +export type PackageUrl = Brand; + +const isGit = (version: PkgVersion): boolean => version.startsWith("git"); + +const isHttp = (version: PkgVersion): boolean => version.startsWith("http"); + +const isLocal = (version: PkgVersion): boolean => version.startsWith("file"); + +/** + * Checks if a version is a package-url + * @param version The version + */ +export const isPackageUrl = (version: PkgVersion): version is PackageUrl => + isGit(version) || isHttp(version) || isLocal(version); diff --git a/src/utils/pkg-version.ts b/src/utils/pkg-version.ts deleted file mode 100644 index 171f37cd..00000000 --- a/src/utils/pkg-version.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { PkgVersion } from "../types/global"; - -const isGit = (version: PkgVersion): boolean => version.startsWith("git"); - -const isHttp = (version: PkgVersion): boolean => version.startsWith("http"); - -const isLocal = (version: PkgVersion): boolean => version.startsWith("file"); - -export const isUrlVersion = (version: PkgVersion): boolean => - isGit(version) || isHttp(version) || isLocal(version); diff --git a/test/test-package-url.ts b/test/test-package-url.ts new file mode 100644 index 00000000..70f0ed38 --- /dev/null +++ b/test/test-package-url.ts @@ -0,0 +1,23 @@ +import { describe } from "mocha"; +import should from "should"; +import { isPackageUrl } from "../src/types/package-url"; + +describe("package-url", function () { + describe("validation", function () { + [ + "https://github.com/yo/com.base.package-a", + "git@github.com:yo/com.base.package-a", + "file../yo/com.base.package-a", + ].forEach((url) => + it(`"${url}" is a package-url`, function () { + should(isPackageUrl(url)).be.true(); + }) + ); + + ["", "com.base.package.a"].forEach((url) => + it(`"${url}" is not a package-url`, function () { + should(isPackageUrl(url)).not.be.true(); + }) + ); + }); +}); From 72bae32e169f81c3a1c8d38cce6eff5669d1d971 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 24 Nov 2023 19:45:48 +0100 Subject: [PATCH 20/27] refactor: add semantic-version type Add type-checking for semantic-version strings. Currently not much validation --- package-lock.json | 586 +++++++++------------------------- package.json | 1 + src/cmd-add.ts | 8 +- src/cmd-deps.ts | 4 + src/registry-client.ts | 19 +- src/types/global.ts | 25 +- src/types/package-url.ts | 9 +- src/types/semantic-version.ts | 16 + src/utils/pkg-info.ts | 7 +- src/utils/pkg-name.ts | 2 +- test/data-pkg-info.ts | 12 +- test/data-pkg-manifest.ts | 2 + test/test-cmd-add.ts | 38 ++- test/test-cmd-deps.ts | 5 +- test/test-cmd-remove.ts | 6 +- test/test-cmd-search.ts | 3 +- test/test-cmd-view.ts | 14 +- test/test-manifest.ts | 4 +- test/test-pgk-info.ts | 5 +- test/test-semantic-version.ts | 18 ++ 20 files changed, 293 insertions(+), 491 deletions(-) create mode 100644 src/types/semantic-version.ts create mode 100644 test/test-semantic-version.ts diff --git a/package-lock.json b/package-lock.json index b709e30b..01fd0ae9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "npmlog": "^6.0.2", "pkginfo": "^0.4.1", "promptly": "^3.2.0", + "semver": "^7.5.4", "ts-brand": "^0.0.2", "update-notifier": "^5.1.0", "yaml": "^2.0.1" @@ -214,6 +215,15 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/eslint-parser": { "version": "7.18.2", "dev": true, @@ -231,6 +241,15 @@ "eslint": "^7.5.0 || ^8.0.0" } }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.23.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", @@ -285,6 +304,15 @@ "yallist": "^3.0.2" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -886,29 +914,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/@npmcli/fs/node_modules/lru-cache": { - "version": "6.0.0", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.3.7", - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@npmcli/move-file": { "version": "2.0.0", "license": "MIT", @@ -1214,31 +1219,6 @@ "semantic-release": ">=19.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@semantic-release/npm/node_modules/semver": { - "version": "7.3.7", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@semantic-release/release-notes-generator": { "version": "10.0.3", "dev": true, @@ -1680,31 +1660,6 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/parser": { "version": "6.8.0", "dev": true, @@ -1812,31 +1767,6 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { "version": "6.8.0", "dev": true, @@ -1861,31 +1791,6 @@ "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.4", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "6.8.0", "dev": true, @@ -2047,16 +1952,6 @@ "node": ">=0.10.0" } }, - "node_modules/another-npm-registry-client/node_modules/lru-cache": { - "version": "6.0.0", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/another-npm-registry-client/node_modules/npmlog": { "version": "4.1.2", "license": "ISC", @@ -2068,19 +1963,6 @@ "set-blocking": "~2.0.0" } }, - "node_modules/another-npm-registry-client/node_modules/semver": { - "version": "7.3.7", - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/another-npm-registry-client/node_modules/string-width": { "version": "1.0.2", "license": "MIT", @@ -2900,6 +2782,15 @@ "node": ">=10" } }, + "node_modules/conventional-changelog-writer/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/conventional-commits-filter": { "version": "2.0.7", "dev": true, @@ -3402,31 +3293,6 @@ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/eslint-plugin-vue/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-vue/node_modules/semver": { - "version": "7.3.7", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "dev": true, @@ -4923,6 +4789,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/make-error": { "version": "1.3.6", "dev": true, @@ -5075,20 +4949,6 @@ "node": ">=10" } }, - "node_modules/meow/node_modules/semver": { - "version": "7.3.7", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/meow/node_modules/type-fest": { "version": "0.18.1", "dev": true, @@ -5733,29 +5593,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/npm-registry-fetch/node_modules/semver": { - "version": "7.3.7", - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-registry-fetch/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/npm-registry-fetch/node_modules/validate-npm-package-name": { "version": "4.0.0", "license": "ISC", @@ -8290,6 +8127,14 @@ "node": ">=8" } }, + "node_modules/package-json/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/parent-module": { "version": "1.0.1", "dev": true, @@ -9139,10 +8984,10 @@ "node": ">=8" } }, - "node_modules/semantic-release/node_modules/semver": { - "version": "7.3.7", - "dev": true, - "license": "ISC", + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -9153,14 +8998,6 @@ "node": ">=10" } }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/semver-diff": { "version": "3.1.1", "license": "MIT", @@ -9171,17 +9008,36 @@ "node": ">=8" } }, - "node_modules/semver-regex": { - "version": "3.1.4", - "dev": true, - "license": "MIT", - "engines": { + "node_modules/semver-diff/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-regex": { + "version": "3.1.4", + "dev": true, + "license": "MIT", + "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/serialize-javascript": { "version": "6.0.0", "dev": true, @@ -10084,29 +9940,6 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, - "node_modules/update-notifier/node_modules/lru-cache": { - "version": "6.0.0", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/update-notifier/node_modules/semver": { - "version": "7.3.7", - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/uri-js": { "version": "4.4.1", "license": "BSD-2-Clause", @@ -10222,31 +10055,6 @@ "node": ">=4.0" } }, - "node_modules/vue-eslint-parser/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/vue-eslint-parser/node_modules/semver": { - "version": "7.3.7", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "dev": true, @@ -10572,6 +10380,14 @@ "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "@babel/eslint-parser": { @@ -10581,6 +10397,14 @@ "eslint-scope": "^5.1.1", "eslint-visitor-keys": "^2.1.0", "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "@babel/generator": { @@ -10630,6 +10454,12 @@ "yallist": "^3.0.2" } }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -11048,20 +10878,6 @@ "requires": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.7", - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "@npmcli/move-file": { @@ -11289,22 +11105,6 @@ "registry-auth-token": "^4.0.0", "semver": "^7.1.2", "tempy": "^1.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.7", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "@semantic-release/release-notes-generator": { @@ -11608,22 +11408,6 @@ "natural-compare": "^1.4.0", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "@typescript-eslint/parser": { @@ -11670,22 +11454,6 @@ "is-glob": "^4.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "@typescript-eslint/utils": { @@ -11699,22 +11467,6 @@ "@typescript-eslint/types": "6.8.0", "@typescript-eslint/typescript-estree": "6.8.0", "semver": "^7.5.4" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "@typescript-eslint/visitor-keys": { @@ -11824,12 +11576,6 @@ "number-is-nan": "^1.0.0" } }, - "lru-cache": { - "version": "6.0.0", - "requires": { - "yallist": "^4.0.0" - } - }, "npmlog": { "version": "4.1.2", "optional": true, @@ -11840,12 +11586,6 @@ "set-blocking": "~2.0.0" } }, - "semver": { - "version": "7.3.7", - "requires": { - "lru-cache": "^6.0.0" - } - }, "string-width": { "version": "1.0.2", "optional": true, @@ -12356,6 +12096,14 @@ "semver": "^6.0.0", "split": "^1.0.0", "through2": "^4.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "conventional-commits-filter": { @@ -12706,22 +12454,6 @@ "postcss-selector-parser": "^6.0.9", "semver": "^7.3.5", "vue-eslint-parser": "^8.0.1" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.7", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "eslint-scope": { @@ -13602,6 +13334,13 @@ "version": "3.1.0", "requires": { "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } } }, "make-error": { @@ -13704,13 +13443,6 @@ "validate-npm-package-license": "^3.0.1" } }, - "semver": { - "version": "7.3.7", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, "type-fest": { "version": "0.18.1", "dev": true @@ -15715,20 +15447,6 @@ "validate-npm-package-name": "^4.0.0" } }, - "semver": { - "version": "7.3.7", - "requires": { - "lru-cache": "^6.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "requires": { - "yallist": "^4.0.0" - } - } - } - }, "validate-npm-package-name": { "version": "4.0.0", "requires": { @@ -15876,6 +15594,13 @@ "registry-auth-token": "^4.0.0", "registry-url": "^5.0.0", "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } } }, "parent-module": { @@ -16399,25 +16124,38 @@ "resolve-from": { "version": "5.0.0", "dev": true - }, - "semver": { - "version": "7.3.7", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } } } }, "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + } + } }, "semver-diff": { "version": "3.1.1", "requires": { "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } } }, "semver-regex": { @@ -17011,20 +16749,6 @@ "semver": "^7.3.4", "semver-diff": "^3.1.1", "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.7", - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "uri-js": { @@ -17103,20 +16827,6 @@ "estraverse": { "version": "5.3.0", "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.7", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } } } }, diff --git a/package.json b/package.json index 0b0902eb..36cbf1c4 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "npmlog": "^6.0.2", "pkginfo": "^0.4.1", "promptly": "^3.2.0", + "semver": "^7.5.4", "ts-brand": "^0.0.2", "update-notifier": "^5.1.0", "yaml": "^2.0.1" diff --git a/src/cmd-add.ts b/src/cmd-add.ts index a4625437..f4910605 100644 --- a/src/cmd-add.ts +++ b/src/cmd-add.ts @@ -12,6 +12,7 @@ import { } from "./utils/editor-version"; import { fetchPackageDependencies, fetchPackageInfo } from "./registry-client"; import { isDomainName } from "./types/domain-name"; +import { SemanticVersion } from "./types/semantic-version"; export type AddOptions = { test?: boolean; @@ -89,10 +90,11 @@ const _add = async function ({ return { code: 1, dirty }; } // verify version - const versions = Object.keys(pkgInfo.versions); + const versions = Object.keys(pkgInfo.versions) as SemanticVersion[]; // eslint-disable-next-line require-atomic-updates - if (!version || version == "latest") version = tryGetLatestVersion(pkgInfo); - if (versions.filter((x) => x == version).length <= 0) { + if (!version || version === "latest") + version = tryGetLatestVersion(pkgInfo); + if (versions.filter((x) => x === version).length <= 0) { log.warn( "404", `version ${version} is not a valid choice of: ${versions diff --git a/src/cmd-deps.ts b/src/cmd-deps.ts index 5626db3c..c20c0e44 100644 --- a/src/cmd-deps.ts +++ b/src/cmd-deps.ts @@ -4,6 +4,7 @@ import { GlobalOptions, PkgName, PkgVersion } from "./types/global"; import { parseEnv } from "./utils/env"; import { fetchPackageDependencies } from "./registry-client"; import { DomainName } from "./types/domain-name"; +import { isPackageUrl } from "./types/package-url"; export type DepsOptions = { deep?: boolean; @@ -30,6 +31,9 @@ const _deps = async function ({ version: PkgVersion | undefined; deep?: boolean; }) { + if (version !== undefined && isPackageUrl(version)) + throw new Error("Cannot get dependencies for url-version"); + // eslint-disable-next-line no-unused-vars const [depsValid, depsInvalid] = await fetchPackageDependencies({ name, diff --git a/src/registry-client.ts b/src/registry-client.ts index eb5b9e1f..7ece0d2a 100644 --- a/src/registry-client.ts +++ b/src/registry-client.ts @@ -13,7 +13,6 @@ import { NameVersionPair, PkgInfo, PkgName, - PkgVersion, Registry, } from "./types/global"; import { env } from "./utils/env"; @@ -21,6 +20,7 @@ import { atVersion } from "./utils/pkg-name"; import _ from "lodash"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { DomainName, isInternalPackage } from "./types/domain-name"; +import { SemanticVersion } from "./types/semantic-version"; export type NpmClient = { rawClient: RegClient; @@ -126,7 +126,7 @@ export const fetchPackageDependencies = async function ({ deep, }: { name: DomainName; - version: PkgVersion | undefined; + version: SemanticVersion | "latest" | undefined; deep?: boolean; }): Promise<[Dependency[], Dependency[]]> { log.verbose( @@ -145,7 +145,7 @@ export const fetchPackageDependencies = async function ({ const depsInvalid = []; // cached dict: {pkg-name: pkgInfo} const cachedPacakgeInfoDict: Record< - PkgVersion, + DomainName, { pkgInfo: PkgInfo; upstream: boolean } > = {}; while (pendingList.length > 0) { @@ -160,7 +160,7 @@ export const fetchPackageDependencies = async function ({ internal: isInternalPackage(entry.name), upstream: false, self: entry.name == name, - version: "", + version: "" as SemanticVersion, reason: null, }; if (!depObj.internal) { @@ -223,12 +223,15 @@ export const fetchPackageDependencies = async function ({ } // add dependencies to pending list if (depObj.self || deep) { - const deps: NameVersionPair[] = _.toPairs( - pkgInfo.versions[entry.version]["dependencies"] + const deps: NameVersionPair[] = ( + _.toPairs(pkgInfo.versions[entry.version]["dependencies"]) as [ + DomainName, + SemanticVersion + ][] ).map((x): NameVersionPair => { return { - name: x[0] as DomainName, - version: x[1] as PkgVersion, + name: x[0], + version: x[1], }; }); deps.forEach((x) => pendingList.push(x)); diff --git a/src/types/global.ts b/src/types/global.ts index c61dae22..85f14d75 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -2,8 +2,9 @@ import { NpmAuth } from "another-npm-registry-client"; import { IpAddress } from "./ip-address"; import { DomainName } from "./domain-name"; import { PackageUrl } from "./package-url"; +import { SemanticVersion } from "./semantic-version"; -export type PkgVersion = PackageUrl | string; +export type PkgVersion = PackageUrl | SemanticVersion | "latest"; export type PkgName = DomainName | `${DomainName}@${PkgVersion}`; @@ -57,10 +58,10 @@ export type PkgVersionInfo = { _npmVersion?: string; _rev?: string; name: string; - version: string; + version: SemanticVersion; unity?: string; unityRelease?: string; - dependencies?: Record; + dependencies?: Record; license?: string; displayName?: string; description?: string; @@ -80,24 +81,28 @@ export type PkgInfo = { _rev?: string; _attachments?: Record; readme?: string; - versions: Record; - "dist-tags"?: { latest?: PkgVersion }; - version?: PkgVersion; + versions: Record; + "dist-tags"?: { latest?: SemanticVersion }; + version?: SemanticVersion; description?: string; keywords?: string[]; - time: Record<"created" | "modified" | PkgVersion, string>; + time: { + [key: SemanticVersion]: string; + created?: string; + modified?: string; + }; date?: Date; users?: Record; }; export type NameVersionPair = { name: DomainName; - version: PkgVersion | undefined; + version: SemanticVersion | "latest" | undefined; }; export type Dependency = { name: DomainName; - version: PkgVersion; + version: SemanticVersion; upstream: boolean; self: boolean; internal: boolean; @@ -112,7 +117,7 @@ export type ScopedRegistry = { }; export type PkgManifest = { - dependencies: Record; + dependencies: Record; scopedRegistries?: ScopedRegistry[]; testables?: string[]; }; diff --git a/src/types/package-url.ts b/src/types/package-url.ts index d831aec9..59e9b25a 100644 --- a/src/types/package-url.ts +++ b/src/types/package-url.ts @@ -1,20 +1,19 @@ import { Brand } from "ts-brand"; -import { PkgVersion } from "./global"; /** * A string of an url pointing to a local or remote package */ export type PackageUrl = Brand; -const isGit = (version: PkgVersion): boolean => version.startsWith("git"); +const isGit = (version: string): boolean => version.startsWith("git"); -const isHttp = (version: PkgVersion): boolean => version.startsWith("http"); +const isHttp = (version: string): boolean => version.startsWith("http"); -const isLocal = (version: PkgVersion): boolean => version.startsWith("file"); +const isLocal = (version: string): boolean => version.startsWith("file"); /** * Checks if a version is a package-url * @param version The version */ -export const isPackageUrl = (version: PkgVersion): version is PackageUrl => +export const isPackageUrl = (version: string): version is PackageUrl => isGit(version) || isHttp(version) || isLocal(version); diff --git a/src/types/semantic-version.ts b/src/types/semantic-version.ts new file mode 100644 index 00000000..ff3b932f --- /dev/null +++ b/src/types/semantic-version.ts @@ -0,0 +1,16 @@ +import { Brand } from "ts-brand"; +import semver from "semver/preload"; + +/** + * A string with a semantic-version format + * @see https://semver.org/ + */ +export type SemanticVersion = Brand; + +/** + * Checks if a string is a semantic version + * @param s The string + */ +export function isSemanticVersion(s: string): s is SemanticVersion { + return semver.parse(s) !== null; +} diff --git a/src/utils/pkg-info.ts b/src/utils/pkg-info.ts index 1b225b13..98a149d3 100644 --- a/src/utils/pkg-info.ts +++ b/src/utils/pkg-info.ts @@ -1,4 +1,5 @@ import { PkgInfo, PkgVersion } from "../types/global"; +import { SemanticVersion } from "../types/semantic-version"; const hasLatestDistTag = ( pkgInfo: Partial @@ -11,9 +12,9 @@ const hasLatestDistTag = ( * @param pkgInfo The package. All properties are assumed to be potentially missing */ export const tryGetLatestVersion = function (pkgInfo: { - "dist-tags"?: { latest?: PkgVersion }; - version?: PkgVersion; -}): PkgVersion | undefined { + "dist-tags"?: { latest?: SemanticVersion }; + version?: SemanticVersion; +}): SemanticVersion | undefined { if (hasLatestDistTag(pkgInfo)) return pkgInfo["dist-tags"].latest; else if (pkgInfo.version) return pkgInfo.version; }; diff --git a/src/utils/pkg-name.ts b/src/utils/pkg-name.ts index 65f1b882..e3298123 100644 --- a/src/utils/pkg-name.ts +++ b/src/utils/pkg-name.ts @@ -13,7 +13,7 @@ export const splitPkgName = function (pkgName: PkgName): { const name = segments[0] as DomainName; const version = segments.length > 1 - ? segments.slice(1, segments.length).join("@") + ? (segments.slice(1, segments.length).join("@") as PkgVersion) : undefined; return { name, version }; }; diff --git a/test/data-pkg-info.ts b/test/data-pkg-info.ts index 1fbefd80..afcb6dcd 100644 --- a/test/data-pkg-info.ts +++ b/test/data-pkg-info.ts @@ -1,6 +1,10 @@ import { PkgInfo, PkgVersionInfo } from "../src/types/global"; import assert from "assert"; import { DomainName, isDomainName } from "../src/types/domain-name"; +import { + isSemanticVersion, + SemanticVersion, +} from "../src/types/semantic-version"; /** * Builder class for {@link PkgVersionInfo} @@ -8,7 +12,7 @@ import { DomainName, isDomainName } from "../src/types/domain-name"; class VersionInfoBuilder { readonly version: PkgVersionInfo; - constructor(name: DomainName, version: string) { + constructor(name: DomainName, version: SemanticVersion) { this.version = { name, _id: `${name}@${version}`, @@ -25,6 +29,7 @@ class VersionInfoBuilder { */ addDependency(name: string, version: string): VersionInfoBuilder { assert(isDomainName(name), `${name} is domain name`); + assert(isSemanticVersion(version), `${version} is semantic version`); this.version.dependencies![name] = version; return this; } @@ -51,8 +56,7 @@ class VersionInfoBuilder { class PackageInfoBuilder { readonly package: PkgInfo; - constructor(name: string) { - assert(isDomainName(name), `${name} is domain name`); + constructor(name: DomainName) { this.package = { name, _id: name, @@ -72,6 +76,7 @@ class PackageInfoBuilder { version: string, build?: (builder: VersionInfoBuilder) => unknown ): PackageInfoBuilder { + assert(isSemanticVersion(version), `${version} is semantic version`); const builder = new VersionInfoBuilder(this.package.name, version); if (build !== undefined) build(builder); this.package.versions[version] = builder.version; @@ -102,6 +107,7 @@ export function buildPackageInfo( name: string, build?: (builder: PackageInfoBuilder) => unknown ): PkgInfo { + assert(isDomainName(name), `${name} is domain name`); const builder = new PackageInfoBuilder(name); if (build !== undefined) build(builder); return builder.package; diff --git a/test/data-pkg-manifest.ts b/test/data-pkg-manifest.ts index 02d5561d..bd0173d0 100644 --- a/test/data-pkg-manifest.ts +++ b/test/data-pkg-manifest.ts @@ -2,6 +2,7 @@ import { PkgManifest } from "../src/types/global"; import assert from "assert"; import { DomainName, isDomainName } from "../src/types/domain-name"; import { exampleRegistryUrl } from "./mock-registry"; +import { isSemanticVersion } from "../src/types/semantic-version"; /** * Builder class for {@link PkgManifest} @@ -64,6 +65,7 @@ class PkgManifestBuilder { testable: boolean ): PkgManifestBuilder { assert(isDomainName(name), `${name} is domain name`); + assert(isSemanticVersion(version), `${version} is semantic version`); if (withScope) this.addScope(name); if (testable) this.addTestable(name); this.manifest.dependencies[name] = version; diff --git a/test/test-cmd-add.ts b/test/test-cmd-add.ts index 3705a040..6981fdcb 100644 --- a/test/test-cmd-add.ts +++ b/test/test-cmd-add.ts @@ -18,6 +18,8 @@ import { buildPackageInfo } from "./data-pkg-info"; import { buildPackageManifest } from "./data-pkg-manifest"; import { DomainName } from "../src/types/domain-name"; import { atVersion } from "../src/utils/pkg-name"; +import { PackageUrl } from "../src/types/package-url"; +import { SemanticVersion } from "../src/types/semantic-version"; describe("cmd-add.ts", function () { const packageMissing = "pkg-not-exist" as DomainName; @@ -159,7 +161,10 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@1.0.0", async function () { - const retCode = await add(atVersion(packageA, "1.0.0"), options); + const retCode = await add( + atVersion(packageA, "1.0.0" as SemanticVersion), + options + ); retCode.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestA); @@ -175,9 +180,15 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@0.1.0 then pkg@1.0.0", async function () { - const retCode1 = await add(atVersion(packageA, "0.1.0"), options); + const retCode1 = await add( + atVersion(packageA, "0.1.0" as SemanticVersion), + options + ); retCode1.should.equal(0); - const retCode2 = await add(atVersion(packageA, "1.0.0"), options); + const retCode2 = await add( + atVersion(packageA, "1.0.0" as SemanticVersion), + options + ); retCode2.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestA); @@ -185,9 +196,15 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add exited pkg version", async function () { - const retCode1 = await add(atVersion(packageA, "1.0.0"), options); + const retCode1 = await add( + atVersion(packageA, "1.0.0" as SemanticVersion), + options + ); retCode1.should.equal(0); - const retCode2 = await add(atVersion(packageA, "1.0.0"), options); + const retCode2 = await add( + atVersion(packageA, "1.0.0" as SemanticVersion), + options + ); retCode2.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestA); @@ -195,7 +212,10 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@not-exist-version", async function () { - const retCode = await add(atVersion(packageA, "2.0.0"), options); + const retCode = await add( + atVersion(packageA, "2.0.0" as SemanticVersion), + options + ); retCode.should.equal(1); const manifest = shouldHaveManifest(); manifest.should.deepEqual(defaultManifest); @@ -205,7 +225,7 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "1.0.0").should.be.ok(); }); it("add pkg@http", async function () { - const gitUrl = "https://github.com/yo/com.base.package-a"; + const gitUrl = "https://github.com/yo/com.base.package-a" as PackageUrl; const retCode = await add(atVersion(packageA, gitUrl), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); @@ -214,7 +234,7 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@git", async function () { - const gitUrl = "git@github.com:yo/com.base.package-a"; + const gitUrl = "git@github.com:yo/com.base.package-a" as PackageUrl; const retCode = await add(atVersion(packageA, gitUrl), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); @@ -223,7 +243,7 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@file", async function () { - const fileUrl = "file../yo/com.base.package-a"; + const fileUrl = "file../yo/com.base.package-a" as PackageUrl; const retCode = await add(atVersion(packageA, fileUrl), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); diff --git a/test/test-cmd-deps.ts b/test/test-cmd-deps.ts index 82e1d0cb..e6de4e0a 100644 --- a/test/test-cmd-deps.ts +++ b/test/test-cmd-deps.ts @@ -13,6 +13,7 @@ import { attachMockConsole, MockConsole } from "./mock-console"; import { buildPackageInfo } from "./data-pkg-info"; import { DomainName } from "../src/types/domain-name"; import { atVersion } from "../src/utils/pkg-name"; +import { SemanticVersion } from "../src/types/semantic-version"; describe("cmd-deps.ts", function () { const options: DepsOptions = { @@ -78,7 +79,7 @@ describe("cmd-deps.ts", function () { }); it("deps pkg@1.0.0", async function () { const retCode = await deps( - atVersion(remotePkgInfoA.name, "1.0.0"), + atVersion(remotePkgInfoA.name, "1.0.0" as SemanticVersion), options ); retCode.should.equal(0); @@ -86,7 +87,7 @@ describe("cmd-deps.ts", function () { }); it("deps pkg@not-exist-version", async function () { const retCode = await deps( - atVersion(remotePkgInfoA.name, "2.0.0"), + atVersion(remotePkgInfoA.name, "2.0.0" as SemanticVersion), options ); retCode.should.equal(0); diff --git a/test/test-cmd-remove.ts b/test/test-cmd-remove.ts index 8775d205..8907dc14 100644 --- a/test/test-cmd-remove.ts +++ b/test/test-cmd-remove.ts @@ -14,6 +14,7 @@ import { import { buildPackageManifest } from "./data-pkg-manifest"; import { DomainName } from "../src/types/domain-name"; import { atVersion } from "../src/utils/pkg-name"; +import { SemanticVersion } from "../src/types/semantic-version"; const packageA = "com.example.package-a" as DomainName; const packageB = "com.example.package-b" as DomainName; @@ -65,7 +66,10 @@ describe("cmd-remove.ts", function () { chdir: getWorkDir("test-openupm-cli"), }, }; - const retCode = await remove(atVersion(packageA, "1.0.0"), options); + const retCode = await remove( + atVersion(packageA, "1.0.0" as SemanticVersion), + options + ); retCode.should.equal(1); const manifest = shouldHaveManifest(); manifest.should.deepEqual(defaultManifest); diff --git a/test/test-cmd-search.ts b/test/test-cmd-search.ts index 3a2548da..8551cb66 100644 --- a/test/test-cmd-search.ts +++ b/test/test-cmd-search.ts @@ -12,6 +12,7 @@ import { SearchEndpointResult } from "./types"; import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; import { DomainName } from "../src/types/domain-name"; +import { SemanticVersion } from "../src/types/semantic-version"; describe("cmd-search.ts", function () { let mockConsole: MockConsole = null!; @@ -40,7 +41,7 @@ describe("cmd-search.ts", function () { package: { name: "com.example.package-a" as DomainName, scope: "unscoped", - version: "1.0.0", + version: "1.0.0" as SemanticVersion, description: "A demo package", date: "2019-10-02T04:02:38.335Z", links: {}, diff --git a/test/test-cmd-view.ts b/test/test-cmd-view.ts index 3db30da2..cbfca63f 100644 --- a/test/test-cmd-view.ts +++ b/test/test-cmd-view.ts @@ -13,6 +13,7 @@ import { attachMockConsole, MockConsole } from "./mock-console"; import { buildPackageInfo } from "./data-pkg-info"; import { DomainName } from "../src/types/domain-name"; import { atVersion } from "../src/utils/pkg-name"; +import { SemanticVersion } from "../src/types/semantic-version"; const packageA = "com.example.package-a" as DomainName; const packageUp = "com.example.package-up" as DomainName; @@ -40,7 +41,7 @@ describe("cmd-view.ts", function () { .set("time", { modified: "2019-11-28T18:51:58.123Z", created: "2019-11-28T18:51:58.123Z", - "1.0.0": "2019-11-28T18:51:58.123Z", + ["1.0.0" as SemanticVersion]: "2019-11-28T18:51:58.123Z", }) .set("_rev", "3-418f950115c32bd0") .set("readme", "A demo package") @@ -63,7 +64,7 @@ describe("cmd-view.ts", function () { tarball: "https://cdn.example.com/com.example.package-a/com.example.package-a-1.0.0.tgz", }) - .addDependency(packageA, "^1.0.0") + .addDependency(packageA, "1.0.0") ) ); @@ -72,7 +73,7 @@ describe("cmd-view.ts", function () { .set("time", { modified: "2019-11-28T18:51:58.123Z", created: "2019-11-28T18:51:58.123Z", - "1.0.0": "2019-11-28T18:51:58.123Z", + ["1.0.0" as SemanticVersion]: "2019-11-28T18:51:58.123Z", }) .set("_rev", "3-418f950115c32bd0") .set("readme", "A demo package") @@ -86,7 +87,7 @@ describe("cmd-view.ts", function () { .set("description", "A demo package") .set("keywords", [""]) .set("category", "Unity") - .addDependency(packageUp, "^1.0.0") + .addDependency(packageUp, "1.0.0") .set("gitHead", "5c141ecfac59c389090a07540f44c8ac5d07a729") .set("readmeFilename", "README.md") .set("_nodeVersion", "12.13.1") @@ -123,7 +124,10 @@ describe("cmd-view.ts", function () { .should.be.ok(); }); it("view pkg@1.0.0", async function () { - const retCode = await view(atVersion(packageA, "1.0.0"), options); + const retCode = await view( + atVersion(packageA, "1.0.0" as SemanticVersion), + options + ); retCode.should.equal(1); mockConsole.hasLineIncluding("out", "please replace").should.be.ok(); }); diff --git a/test/test-manifest.ts b/test/test-manifest.ts index ffa4a277..58fded17 100644 --- a/test/test-manifest.ts +++ b/test/test-manifest.ts @@ -12,6 +12,7 @@ import { shouldNotHaveAnyDependencies, } from "./manifest-assertions"; import { DomainName } from "../src/types/domain-name"; +import { SemanticVersion } from "../src/types/semantic-version"; describe("manifest", function () { let mockConsole: MockConsole = null!; @@ -74,7 +75,8 @@ describe("manifest", function () { ).should.be.ok(); const manifest = shouldHaveManifest(); shouldNotHaveAnyDependencies(manifest); - manifest.dependencies["some-pack" as DomainName] = "1.0.0"; + manifest.dependencies["some-pack" as DomainName] = + "1.0.0" as SemanticVersion; saveManifest(manifest).should.be.ok(); const manifest2 = shouldHaveManifest(); manifest2.should.be.deepEqual(manifest); diff --git a/test/test-pgk-info.ts b/test/test-pgk-info.ts index b78d3331..d8d988cd 100644 --- a/test/test-pgk-info.ts +++ b/test/test-pgk-info.ts @@ -2,11 +2,14 @@ import { tryGetLatestVersion } from "../src/utils/pkg-info"; import "should"; import { describe } from "mocha"; import should from "should"; +import { SemanticVersion } from "../src/types/semantic-version"; describe("pkg-info", function () { describe("tryGetLatestVersion", function () { it("from dist-tags", async function () { - const version = tryGetLatestVersion({ "dist-tags": { latest: "1.0.0" } }); + const version = tryGetLatestVersion({ + "dist-tags": { latest: "1.0.0" as SemanticVersion }, + }); should(version).equal("1.0.0"); }); }); diff --git a/test/test-semantic-version.ts b/test/test-semantic-version.ts new file mode 100644 index 00000000..7088b111 --- /dev/null +++ b/test/test-semantic-version.ts @@ -0,0 +1,18 @@ +import { describe } from "mocha"; +import should from "should"; +import { isSemanticVersion } from "../src/types/semantic-version"; + +describe("semantic-version", function () { + describe("validate", function () { + ["1.2.3", "1.2.3-alpha"].forEach((input) => + it(`"${input}" is a semantic version`, function () { + should(isSemanticVersion(input)).be.true(); + }) + ); + ["", " ", "wow", "1", "1.2"].forEach((input) => + it(`"${input}" is not a semantic version`, function () { + should(isSemanticVersion(input)).not.be.true(); + }) + ); + }); +}); From 8e19b6867ec48ded4064fe89cd21735374a506a3 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 24 Nov 2023 19:56:40 +0100 Subject: [PATCH 21/27] refactor: add package-id type Package at specific version --- src/types/global.ts | 3 ++- src/types/package-id.ts | 33 +++++++++++++++++++++++++++++++++ src/utils/string-utils.ts | 8 ++++++++ test/data-pkg-info.ts | 3 ++- test/test-package-id.ts | 24 ++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 src/types/package-id.ts create mode 100644 src/utils/string-utils.ts create mode 100644 test/test-package-id.ts diff --git a/src/types/global.ts b/src/types/global.ts index 85f14d75..b49b6222 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -3,6 +3,7 @@ import { IpAddress } from "./ip-address"; import { DomainName } from "./domain-name"; import { PackageUrl } from "./package-url"; import { SemanticVersion } from "./semantic-version"; +import { PackageId } from "./package-id"; export type PkgVersion = PackageUrl | SemanticVersion | "latest"; @@ -53,7 +54,7 @@ export type Contact = { }; export type PkgVersionInfo = { - _id?: PkgName; + _id?: PackageId; _nodeVersion?: string; _npmVersion?: string; _rev?: string; diff --git a/src/types/package-id.ts b/src/types/package-id.ts new file mode 100644 index 00000000..5290502b --- /dev/null +++ b/src/types/package-id.ts @@ -0,0 +1,33 @@ +import { DomainName, isDomainName } from "./domain-name"; +import { isSemanticVersion, SemanticVersion } from "./semantic-version"; +import { trySplitAtFirstOccurrenceOf } from "../utils/string-utils"; + +/** + * Represents a package at a specific version. The version is here is a + * concrete semantic version. + * @example com.abc.my-package@1.2.3 + */ +export type PackageId = `${DomainName}@${SemanticVersion}`; + +/** + * Checks if a string is a package-id + * @param s The string + */ +export function isPackageId(s: string): s is PackageId { + const [name, version] = trySplitAtFirstOccurrenceOf(s, "@"); + return ( + isDomainName(name) && version !== undefined && isSemanticVersion(version) + ); +} + +/** + * Constructs a package-id + * @param name The package name + * @param version The version + */ +export function packageId( + name: DomainName, + version: SemanticVersion +): PackageId { + return `${name}@${version}`; +} diff --git a/src/utils/string-utils.ts b/src/utils/string-utils.ts new file mode 100644 index 00000000..1e57281b --- /dev/null +++ b/src/utils/string-utils.ts @@ -0,0 +1,8 @@ +export function trySplitAtFirstOccurrenceOf( + s: string, + split: string +): [string, string | undefined] { + const elements = s.split(split); + if (elements.length === 1) return [s, undefined]; + return [elements[0], elements.slice(1).join(split)]; +} diff --git a/test/data-pkg-info.ts b/test/data-pkg-info.ts index afcb6dcd..278089be 100644 --- a/test/data-pkg-info.ts +++ b/test/data-pkg-info.ts @@ -5,6 +5,7 @@ import { isSemanticVersion, SemanticVersion, } from "../src/types/semantic-version"; +import { packageId } from "../src/types/package-id"; /** * Builder class for {@link PkgVersionInfo} @@ -15,7 +16,7 @@ class VersionInfoBuilder { constructor(name: DomainName, version: SemanticVersion) { this.version = { name, - _id: `${name}@${version}`, + _id: packageId(name, version), version, dependencies: {}, contributors: [], diff --git a/test/test-package-id.ts b/test/test-package-id.ts new file mode 100644 index 00000000..a0c3aa3a --- /dev/null +++ b/test/test-package-id.ts @@ -0,0 +1,24 @@ +import { describe } from "mocha"; +import should from "should"; +import { isPackageId } from "../src/types/package-id"; + +describe("package-id", function () { + describe("validate", function () { + ["com.my-package@1.2.3"].forEach((s) => + it(`"${s}" should be package-id`, () => should(isPackageId(s)).be.true()) + ); + + [ + "", + " ", + // Missing version + "com.my-package", + // Incomplete version + "com.my-package@1", + "com.my-package@1.2", + ].forEach((s) => + it(`"${s}" should not be package-id`, () => + should(isPackageId(s)).be.false()) + ); + }); +}); From b8670b3d452c489e4346bb786281e75e1e245b38 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 24 Nov 2023 20:35:44 +0100 Subject: [PATCH 22/27] misc: remove outdated eslint suppress --- src/cmd-deps.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmd-deps.ts b/src/cmd-deps.ts index c20c0e44..e697c167 100644 --- a/src/cmd-deps.ts +++ b/src/cmd-deps.ts @@ -34,7 +34,6 @@ const _deps = async function ({ if (version !== undefined && isPackageUrl(version)) throw new Error("Cannot get dependencies for url-version"); - // eslint-disable-next-line no-unused-vars const [depsValid, depsInvalid] = await fetchPackageDependencies({ name, version, From 433ac4bd48c0b417434e4e7eaafd8b3565eb94da Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 24 Nov 2023 20:57:15 +0100 Subject: [PATCH 23/27] refactor: add package-reference type --- src/cmd-add.ts | 28 +++++++------ src/cmd-deps.ts | 22 ++++++---- src/cmd-remove.ts | 25 +++++++----- src/cmd-search.ts | 13 ++---- src/cmd-view.ts | 20 +++++++--- src/registry-client.ts | 42 +++++++++---------- src/types/global.ts | 12 ++---- src/types/package-reference.ts | 73 ++++++++++++++++++++++++++++++++++ src/utils/pkg-info.ts | 6 ++- src/utils/pkg-name.ts | 27 ------------- test/manifest-assertions.ts | 16 +++++--- test/mock-registry.ts | 4 +- test/test-cmd-add.ts | 27 +++++++------ test/test-cmd-deps.ts | 9 ++--- test/test-cmd-remove.ts | 4 +- test/test-cmd-view.ts | 4 +- test/test-package-reference.ts | 50 +++++++++++++++++++++++ test/test-pkg-name.ts | 49 ----------------------- test/types.ts | 5 ++- 19 files changed, 254 insertions(+), 182 deletions(-) create mode 100644 src/types/package-reference.ts delete mode 100644 src/utils/pkg-name.ts create mode 100644 test/test-package-reference.ts delete mode 100644 test/test-pkg-name.ts diff --git a/src/cmd-add.ts b/src/cmd-add.ts index f4910605..1380f34f 100644 --- a/src/cmd-add.ts +++ b/src/cmd-add.ts @@ -1,8 +1,7 @@ import log from "./logger"; import url from "url"; import { isPackageUrl } from "./types/package-url"; -import { atVersion, splitPkgName } from "./utils/pkg-name"; -import { GlobalOptions, PkgName, ScopedRegistry } from "./types/global"; +import { GlobalOptions, ScopedRegistry } from "./types/global"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { loadManifest, saveManifest } from "./utils/manifest"; import { env, parseEnv } from "./utils/env"; @@ -11,8 +10,13 @@ import { tryParseEditorVersion, } from "./utils/editor-version"; import { fetchPackageDependencies, fetchPackageInfo } from "./registry-client"; -import { isDomainName } from "./types/domain-name"; +import { DomainName, isDomainName } from "./types/domain-name"; import { SemanticVersion } from "./types/semantic-version"; +import { + packageReference, + PackageReference, + splitPackageReference, +} from "./types/package-reference"; export type AddOptions = { test?: boolean; @@ -28,7 +32,7 @@ type AddResult = { }; export const add = async function ( - pkgs: PkgName | PkgName[], + pkgs: PackageReference | PackageReference[], options: AddOptions ): Promise { if (!Array.isArray(pkgs)) pkgs = [pkgs]; @@ -56,7 +60,7 @@ const _add = async function ({ testables, force, }: { - pkg: PkgName; + pkg: PackageReference; testables?: boolean; force?: boolean; }): Promise { @@ -65,9 +69,9 @@ const _add = async function ({ // is upstream package flag let isUpstreamPackage = false; // parse name - const split = splitPkgName(pkg); - const name = split.name; - let version = split.version; + const split = splitPackageReference(pkg); + const name = split[0]; + let version = split[1]; // load manifest const manifest = loadManifest(); @@ -77,7 +81,7 @@ const _add = async function ({ manifest.dependencies = {}; } // packages that added to scope registry - const pkgsInScope: PkgName[] = []; + const pkgsInScope: DomainName[] = []; if (version === undefined || !isPackageUrl(version)) { // verify name let pkgInfo = await fetchPackageInfo(name); @@ -172,7 +176,7 @@ const _add = async function ({ if (!depObj.resolved) log.notice( "suggest", - `to install ${atVersion( + `to install ${packageReference( depObj.name, depObj.version )} or a replaceable version manually` @@ -195,7 +199,7 @@ const _add = async function ({ manifest.dependencies[name] = version; if (!oldVersion) { // Log the added package - log.notice("manifest", `added ${atVersion(name, version)}`); + log.notice("manifest", `added ${packageReference(name, version)}`); dirty = true; } else if (oldVersion != version) { // Log the modified package version @@ -203,7 +207,7 @@ const _add = async function ({ dirty = true; } else { // Log the existed package - log.notice("manifest", `existed ${atVersion(name, version)}`); + log.notice("manifest", `existed ${packageReference(name, version)}`); } if (!isUpstreamPackage) { // add to scopedRegistries diff --git a/src/cmd-deps.ts b/src/cmd-deps.ts index e697c167..0ab8fd37 100644 --- a/src/cmd-deps.ts +++ b/src/cmd-deps.ts @@ -1,22 +1,30 @@ import log from "./logger"; -import { atVersion, splitPkgName } from "./utils/pkg-name"; -import { GlobalOptions, PkgName, PkgVersion } from "./types/global"; +import { GlobalOptions } from "./types/global"; import { parseEnv } from "./utils/env"; import { fetchPackageDependencies } from "./registry-client"; import { DomainName } from "./types/domain-name"; import { isPackageUrl } from "./types/package-url"; +import { + packageReference, + PackageReference, + splitPackageReference, + VersionReference, +} from "./types/package-reference"; export type DepsOptions = { deep?: boolean; _global: GlobalOptions; }; -export const deps = async function (pkg: PkgName, options: DepsOptions) { +export const deps = async function ( + pkg: PackageReference, + options: DepsOptions +) { // parse env const envOk = await parseEnv(options, { checkPath: false }); if (!envOk) return 1; // parse name - const { name, version } = splitPkgName(pkg); + const [name, version] = splitPackageReference(pkg); // deps await _deps({ name, version, deep: options.deep }); return 0; @@ -28,7 +36,7 @@ const _deps = async function ({ deep, }: { name: DomainName; - version: PkgVersion | undefined; + version: VersionReference | undefined; deep?: boolean; }) { if (version !== undefined && isPackageUrl(version)) @@ -42,7 +50,7 @@ const _deps = async function ({ depsValid .filter((x) => !x.self) .forEach((x) => - log.notice("dependency", `${atVersion(x.name, x.version)}`) + log.notice("dependency", `${packageReference(x.name, x.version)}`) ); depsInvalid .filter((x) => !x.self) @@ -50,6 +58,6 @@ const _deps = async function ({ let reason = "unknown"; if (x.reason == "package404") reason = "missing dependency"; else if (x.reason == "version404") reason = "missing dependency version"; - log.warn(reason, atVersion(x.name, x.version)); + log.warn(reason, packageReference(x.name, x.version)); }); }; diff --git a/src/cmd-remove.ts b/src/cmd-remove.ts index 08fc2789..b6c93c93 100644 --- a/src/cmd-remove.ts +++ b/src/cmd-remove.ts @@ -1,16 +1,20 @@ import log from "./logger"; -import { atVersion, splitPkgName } from "./utils/pkg-name"; -import { GlobalOptions, PkgName, ScopedRegistry } from "./types/global"; +import { GlobalOptions, ScopedRegistry } from "./types/global"; import { loadManifest, saveManifest } from "./utils/manifest"; import { env, parseEnv } from "./utils/env"; import { isDomainName } from "./types/domain-name"; +import { + packageReference, + PackageReference, + splitPackageReference, +} from "./types/package-reference"; export type RemoveOptions = { _global: GlobalOptions; }; export const remove = async function ( - pkgs: PkgName[] | PkgName, + pkgs: PackageReference[] | PackageReference, options: RemoveOptions ) { if (!Array.isArray(pkgs)) pkgs = [pkgs]; @@ -30,15 +34,18 @@ export const remove = async function ( return result.code; }; -const _remove = async function (pkg: PkgName) { +const _remove = async function (pkg: PackageReference) { // dirty flag let dirty = false; // parse name - const split = splitPkgName(pkg); - const name = split.name; - let version = split.version; + const split = splitPackageReference(pkg); + const name = split[0]; + let version = split[1]; if (version) { - log.warn("", `please replace '${atVersion(name, version)}' with '${name}'`); + log.warn( + "", + `please replace '${packageReference(name, version)}' with '${name}'` + ); return { code: 1, dirty }; } // load manifest @@ -50,7 +57,7 @@ const _remove = async function (pkg: PkgName) { if (manifest.dependencies) { version = manifest.dependencies[name]; if (version) { - log.notice("manifest", `removed ${atVersion(name, version)}`); + log.notice("manifest", `removed ${packageReference(name, version)}`); delete manifest.dependencies[name]; dirty = true; } else pkgsNotFound.push(pkg); diff --git a/src/cmd-search.ts b/src/cmd-search.ts index 47aa73ca..196f111b 100644 --- a/src/cmd-search.ts +++ b/src/cmd-search.ts @@ -5,27 +5,22 @@ import log from "./logger"; import { is404Error, isHttpError } from "./utils/error-type-guards"; import * as os from "os"; import assert from "assert"; -import { - GlobalOptions, - PkgInfo, - PkgName, - PkgVersion, - Registry, -} from "./types/global"; +import { GlobalOptions, PkgInfo, Registry } from "./types/global"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { env, parseEnv } from "./utils/env"; import { DomainName } from "./types/domain-name"; +import { SemanticVersion } from "./types/semantic-version"; type DateString = string; -type TableRow = [PkgName, PkgVersion, DateString, ""]; +type TableRow = [DomainName, SemanticVersion, DateString, ""]; export type SearchOptions = { _global: GlobalOptions; }; export type SearchedPkgInfo = Omit & { - versions: Record; + versions: Record; }; export type OldSearchResult = diff --git a/src/cmd-view.ts b/src/cmd-view.ts index 9699ed49..49e3d51d 100644 --- a/src/cmd-view.ts +++ b/src/cmd-view.ts @@ -1,25 +1,35 @@ import chalk from "chalk"; import log from "./logger"; import assert from "assert"; -import { atVersion, splitPkgName } from "./utils/pkg-name"; -import { GlobalOptions, PkgInfo, PkgName } from "./types/global"; +import { GlobalOptions, PkgInfo } from "./types/global"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { env, parseEnv } from "./utils/env"; import { fetchPackageInfo } from "./registry-client"; import { DomainName } from "./types/domain-name"; +import { + packageReference, + PackageReference, + splitPackageReference, +} from "./types/package-reference"; export type ViewOptions = { _global: GlobalOptions; }; -export const view = async function (pkg: PkgName, options: ViewOptions) { +export const view = async function ( + pkg: PackageReference, + options: ViewOptions +) { // parse env const envOk = await parseEnv(options, { checkPath: false }); if (!envOk) return 1; // parse name - const { name, version } = splitPkgName(pkg); + const [name, version] = splitPackageReference(pkg); if (version) { - log.warn("", `please replace '${atVersion(name, version)}' with '${name}'`); + log.warn( + "", + `please replace '${packageReference(name, version)}' with '${name}'` + ); return 1; } // verify name diff --git a/src/registry-client.ts b/src/registry-client.ts index 7ece0d2a..1dc2d069 100644 --- a/src/registry-client.ts +++ b/src/registry-client.ts @@ -8,19 +8,13 @@ import RegClient, { import log from "./logger"; import request from "request"; import assert, { AssertionError } from "assert"; -import { - Dependency, - NameVersionPair, - PkgInfo, - PkgName, - Registry, -} from "./types/global"; +import { Dependency, NameVersionPair, PkgInfo, Registry } from "./types/global"; import { env } from "./utils/env"; -import { atVersion } from "./utils/pkg-name"; import _ from "lodash"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { DomainName, isInternalPackage } from "./types/domain-name"; import { SemanticVersion } from "./types/semantic-version"; +import { packageReference } from "./types/package-reference"; export type NpmClient = { rawClient: RegClient; @@ -97,7 +91,7 @@ export const getNpmClient = (): NpmClient => { }; // Fetch package info json from registry export const fetchPackageInfo = async function ( - name: PkgName, + name: DomainName, registry?: Registry ): Promise { if (!registry) registry = env.registry; @@ -131,9 +125,7 @@ export const fetchPackageDependencies = async function ({ }): Promise<[Dependency[], Dependency[]]> { log.verbose( "dependency", - `fetch: ${ - version !== undefined ? atVersion(name, version) : name - } deep=${deep}` + `fetch: ${packageReference(name, version)} deep=${deep}` ); // a list of pending dependency {name, version} const pendingList: NameVersionPair[] = [{ name, version }]; @@ -156,11 +148,16 @@ export const fetchPackageDependencies = async function ({ processedList.push(entry); // create valid depedenency structure const depObj: Dependency = { - ...entry, + name: entry.name, + /* + NOTE: entry.version could also be "latest" or undefiend. + Later code guarantees that in that case depObj.version will be replaced + with a valid-semantic version. So we can assert the value here safely + */ + version: entry.version as SemanticVersion, internal: isInternalPackage(entry.name), upstream: false, self: entry.name == name, - version: "" as SemanticVersion, reason: null, }; if (!depObj.internal) { @@ -210,9 +207,10 @@ export const fetchPackageDependencies = async function ({ if (!versions.find((x) => x == entry.version)) { log.warn( "404", - `package ${ - version !== undefined ? atVersion(name, version) : name - } is not a valid choice of ${versions.reverse().join(", ")}` + `package ${packageReference( + name, + version + )} is not a valid choice of ${versions.reverse().join(", ")}` ); depObj.reason = "version404"; // eslint-disable-next-line require-atomic-updates @@ -240,13 +238,9 @@ export const fetchPackageDependencies = async function ({ depsValid.push(depObj); log.verbose( "dependency", - `${ - entry.version !== undefined - ? atVersion(entry.name, entry.version) - : entry.name - } ${depObj.internal ? "[internal] " : ""}${ - depObj.upstream ? "[upstream]" : "" - }` + `${packageReference(entry.name, entry.version)} ${ + depObj.internal ? "[internal] " : "" + }${depObj.upstream ? "[upstream]" : ""}` ); } } diff --git a/src/types/global.ts b/src/types/global.ts index b49b6222..4644d006 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -5,10 +5,6 @@ import { PackageUrl } from "./package-url"; import { SemanticVersion } from "./semantic-version"; import { PackageId } from "./package-id"; -export type PkgVersion = PackageUrl | SemanticVersion | "latest"; - -export type PkgName = DomainName | `${DomainName}@${PkgVersion}`; - export type Region = "us" | "cn"; export type Registry = string; @@ -78,7 +74,7 @@ export type PkgVersionInfo = { export type PkgInfo = { name: DomainName; - _id?: PkgName; + _id?: DomainName; _rev?: string; _attachments?: Record; readme?: string; @@ -103,7 +99,7 @@ export type NameVersionPair = { export type Dependency = { name: DomainName; - version: SemanticVersion; + version?: SemanticVersion; upstream: boolean; self: boolean; internal: boolean; @@ -114,11 +110,11 @@ export type Dependency = { export type ScopedRegistry = { name: string; url: string; - scopes: PkgName[]; + scopes: DomainName[]; }; export type PkgManifest = { - dependencies: Record; + dependencies: Record; scopedRegistries?: ScopedRegistry[]; testables?: string[]; }; diff --git a/src/types/package-reference.ts b/src/types/package-reference.ts new file mode 100644 index 00000000..9fc30d13 --- /dev/null +++ b/src/types/package-reference.ts @@ -0,0 +1,73 @@ +import { DomainName, isDomainName } from "./domain-name"; +import { isSemanticVersion, SemanticVersion } from "./semantic-version"; +import { isPackageUrl, PackageUrl } from "./package-url"; +import { trySplitAtFirstOccurrenceOf } from "../utils/string-utils"; +import assert from "assert"; + +/** + * A string with the format of one of the supported version tags. + * NOTE: Currently we only support "latest" + */ +type PackageTag = "latest"; + +/** + * Reference to a version, either directly by a semantic version or via an + * url or tag. + */ +export type VersionReference = SemanticVersion | PackageUrl | PackageTag; + +/** + * References a package. Could be just the name or a reference to a specific + * version. Not as specific as a {@link PackageId} as other version-formats + * besides semantic versions, such as "latest" are also allowed + */ +export type PackageReference = DomainName | `${DomainName}@${VersionReference}`; + +/** + * Checks if a string is a version-reference + * @param s The string + */ +function isVersionReference(s: string): s is VersionReference { + return s === "latest" || isSemanticVersion(s) || isPackageUrl(s); +} + +/** + * Checks if a string is a package-reference + * @param s The string + */ +export function isPackageReference(s: string): s is PackageReference { + const [name, version] = trySplitAtFirstOccurrenceOf(s, "@"); + return ( + isDomainName(name) && (version === undefined || isVersionReference(version)) + ); +} + +/** + * Split a package-reference into the name and version if present. + * @param reference The reference + */ +export function splitPackageReference( + reference: PackageReference +): [DomainName, VersionReference | undefined] { + return trySplitAtFirstOccurrenceOf(reference, "@") as [ + DomainName, + VersionReference | undefined + ]; +} + +/** + * Constructs a package-reference + * @param name The package-name. Will be validated to be a {@link DomainName} + * @param version Optional version-reference. Will be validated to be a {@link VersionReference} + */ +export function packageReference( + name: string, + version?: string +): PackageReference { + assert(isDomainName(name), `${name} is valid package-name`); + assert( + version === undefined || isVersionReference(version), + `"${version}" is valid version-reference` + ); + return version !== undefined ? `${name}@${version}` : name; +} diff --git a/src/utils/pkg-info.ts b/src/utils/pkg-info.ts index 98a149d3..62616842 100644 --- a/src/utils/pkg-info.ts +++ b/src/utils/pkg-info.ts @@ -1,9 +1,11 @@ -import { PkgInfo, PkgVersion } from "../types/global"; +import { PkgInfo } from "../types/global"; import { SemanticVersion } from "../types/semantic-version"; const hasLatestDistTag = ( pkgInfo: Partial -): pkgInfo is Partial & { "dist-tags": { latest: PkgVersion } } => { +): pkgInfo is Partial & { + "dist-tags": { latest: SemanticVersion }; +} => { return pkgInfo["dist-tags"]?.["latest"] !== undefined; }; diff --git a/src/utils/pkg-name.ts b/src/utils/pkg-name.ts deleted file mode 100644 index e3298123..00000000 --- a/src/utils/pkg-name.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { PkgName, PkgVersion } from "../types/global"; -import { DomainName } from "../types/domain-name"; - -/** - * Split package-name, which may include a version into the actual name of the - * package and the version if it exists - */ -export const splitPkgName = function (pkgName: PkgName): { - name: DomainName; - version: PkgVersion | undefined; -} { - const segments = pkgName.split("@"); - const name = segments[0] as DomainName; - const version = - segments.length > 1 - ? (segments.slice(1, segments.length).join("@") as PkgVersion) - : undefined; - return { name, version }; -}; - -/** - * Merges a package name and version to create a package name for that specific version - * @param name The name of the package - * @param version The version of the package - */ -export const atVersion = (name: DomainName, version: PkgVersion): PkgName => - `${name}@${version}`; diff --git a/test/manifest-assertions.ts b/test/manifest-assertions.ts index c5d35b84..9cbbd2c6 100644 --- a/test/manifest-assertions.ts +++ b/test/manifest-assertions.ts @@ -1,6 +1,9 @@ -import { PkgManifest, PkgName, PkgVersion } from "../src/types/global"; +import { PkgManifest } from "../src/types/global"; import { loadManifest } from "../src/utils/manifest"; import should from "should"; +import { DomainName } from "../src/types/domain-name"; +import { SemanticVersion } from "../src/types/semantic-version"; +import { PackageUrl } from "../src/types/package-url"; export function shouldHaveManifest(): PkgManifest { const manifest = loadManifest(); @@ -15,8 +18,8 @@ export function shouldHaveNoManifest() { export function shouldHaveDependency( manifest: PkgManifest, - name: PkgName, - version: PkgVersion + name: DomainName, + version: SemanticVersion | PackageUrl ) { should(manifest.dependencies[name]).equal(version); } @@ -25,13 +28,16 @@ export function shouldNotHaveAnyDependencies(manifest: PkgManifest) { should(manifest.dependencies).be.empty(); } -export function shouldNotHaveDependency(manifest: PkgManifest, name: PkgName) { +export function shouldNotHaveDependency( + manifest: PkgManifest, + name: DomainName +) { should(manifest.dependencies[name]).be.undefined(); } export function shouldHaveRegistryWithScopes( manifest: PkgManifest, - scopes: PkgName[] + scopes: DomainName[] ) { should(manifest.scopedRegistries).not.be.undefined(); manifest diff --git a/test/mock-registry.ts b/test/mock-registry.ts index de9cf5f8..99fb9e26 100644 --- a/test/mock-registry.ts +++ b/test/mock-registry.ts @@ -1,4 +1,4 @@ -import { PkgInfo, PkgName } from "../src/types/global"; +import { PkgInfo } from "../src/types/global"; import nock from "nock"; import { SearchEndpointResult } from "./types"; import { DomainName } from "../src/types/domain-name"; @@ -25,7 +25,7 @@ export function registerRemoteUpstreamPkg(pkg: PkgInfo) { nock(exampleRegistryUrl).persist().get(`/${pkg.name}`).reply(404); } -export function registerMissingPackage(name: PkgName) { +export function registerMissingPackage(name: DomainName) { nock(exampleRegistryUrl).persist().get(`/${name}`).reply(404); nock(unityRegistryUrl).persist().get(`/${name}`).reply(404); } diff --git a/test/test-cmd-add.ts b/test/test-cmd-add.ts index 6981fdcb..8a930f54 100644 --- a/test/test-cmd-add.ts +++ b/test/test-cmd-add.ts @@ -17,9 +17,9 @@ import { import { buildPackageInfo } from "./data-pkg-info"; import { buildPackageManifest } from "./data-pkg-manifest"; import { DomainName } from "../src/types/domain-name"; -import { atVersion } from "../src/utils/pkg-name"; import { PackageUrl } from "../src/types/package-url"; import { SemanticVersion } from "../src/types/semantic-version"; +import { packageReference } from "../src/types/package-reference"; describe("cmd-add.ts", function () { const packageMissing = "pkg-not-exist" as DomainName; @@ -162,7 +162,7 @@ describe("cmd-add.ts", function () { }); it("add pkg@1.0.0", async function () { const retCode = await add( - atVersion(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, "1.0.0" as SemanticVersion), options ); retCode.should.equal(0); @@ -172,7 +172,7 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "open Unity").should.be.ok(); }); it("add pkg@latest", async function () { - const retCode = await add(atVersion(packageA, "latest"), options); + const retCode = await add(packageReference(packageA, "latest"), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestA); @@ -181,12 +181,12 @@ describe("cmd-add.ts", function () { }); it("add pkg@0.1.0 then pkg@1.0.0", async function () { const retCode1 = await add( - atVersion(packageA, "0.1.0" as SemanticVersion), + packageReference(packageA, "0.1.0" as SemanticVersion), options ); retCode1.should.equal(0); const retCode2 = await add( - atVersion(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, "1.0.0" as SemanticVersion), options ); retCode2.should.equal(0); @@ -197,12 +197,12 @@ describe("cmd-add.ts", function () { }); it("add exited pkg version", async function () { const retCode1 = await add( - atVersion(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, "1.0.0" as SemanticVersion), options ); retCode1.should.equal(0); const retCode2 = await add( - atVersion(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, "1.0.0" as SemanticVersion), options ); retCode2.should.equal(0); @@ -213,7 +213,7 @@ describe("cmd-add.ts", function () { }); it("add pkg@not-exist-version", async function () { const retCode = await add( - atVersion(packageA, "2.0.0" as SemanticVersion), + packageReference(packageA, "2.0.0" as SemanticVersion), options ); retCode.should.equal(1); @@ -226,7 +226,7 @@ describe("cmd-add.ts", function () { }); it("add pkg@http", async function () { const gitUrl = "https://github.com/yo/com.base.package-a" as PackageUrl; - const retCode = await add(atVersion(packageA, gitUrl), options); + const retCode = await add(packageReference(packageA, gitUrl), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); shouldHaveDependency(manifest, packageA, gitUrl); @@ -235,7 +235,7 @@ describe("cmd-add.ts", function () { }); it("add pkg@git", async function () { const gitUrl = "git@github.com:yo/com.base.package-a" as PackageUrl; - const retCode = await add(atVersion(packageA, gitUrl), options); + const retCode = await add(packageReference(packageA, gitUrl), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); shouldHaveDependency(manifest, packageA, gitUrl); @@ -244,7 +244,7 @@ describe("cmd-add.ts", function () { }); it("add pkg@file", async function () { const fileUrl = "file../yo/com.base.package-a" as PackageUrl; - const retCode = await add(atVersion(packageA, fileUrl), options); + const retCode = await add(packageReference(packageA, fileUrl), options); retCode.should.equal(0); const manifest = shouldHaveManifest(); shouldHaveDependency(manifest, packageA, fileUrl); @@ -289,7 +289,10 @@ describe("cmd-add.ts", function () { mockConsole.hasLineIncluding("out", "package not found").should.be.ok(); }); it("add pkg with nested dependencies", async function () { - const retCode = await add(atVersion(packageC, "latest"), upstreamOptions); + const retCode = await add( + packageReference(packageC, "latest"), + upstreamOptions + ); retCode.should.equal(0); const manifest = shouldHaveManifest(); manifest.should.deepEqual(expectedManifestC); diff --git a/test/test-cmd-deps.ts b/test/test-cmd-deps.ts index e6de4e0a..c299430c 100644 --- a/test/test-cmd-deps.ts +++ b/test/test-cmd-deps.ts @@ -12,8 +12,7 @@ import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; import { buildPackageInfo } from "./data-pkg-info"; import { DomainName } from "../src/types/domain-name"; -import { atVersion } from "../src/utils/pkg-name"; -import { SemanticVersion } from "../src/types/semantic-version"; +import { packageReference } from "../src/types/package-reference"; describe("cmd-deps.ts", function () { const options: DepsOptions = { @@ -71,7 +70,7 @@ describe("cmd-deps.ts", function () { }); it("deps pkg@latest", async function () { const retCode = await deps( - atVersion(remotePkgInfoA.name, "latest"), + packageReference(remotePkgInfoA.name, "latest"), options ); retCode.should.equal(0); @@ -79,7 +78,7 @@ describe("cmd-deps.ts", function () { }); it("deps pkg@1.0.0", async function () { const retCode = await deps( - atVersion(remotePkgInfoA.name, "1.0.0" as SemanticVersion), + packageReference(remotePkgInfoA.name, "1.0.0"), options ); retCode.should.equal(0); @@ -87,7 +86,7 @@ describe("cmd-deps.ts", function () { }); it("deps pkg@not-exist-version", async function () { const retCode = await deps( - atVersion(remotePkgInfoA.name, "2.0.0" as SemanticVersion), + packageReference(remotePkgInfoA.name, "2.0.0"), options ); retCode.should.equal(0); diff --git a/test/test-cmd-remove.ts b/test/test-cmd-remove.ts index 8907dc14..8a04e403 100644 --- a/test/test-cmd-remove.ts +++ b/test/test-cmd-remove.ts @@ -13,8 +13,8 @@ import { } from "./manifest-assertions"; import { buildPackageManifest } from "./data-pkg-manifest"; import { DomainName } from "../src/types/domain-name"; -import { atVersion } from "../src/utils/pkg-name"; import { SemanticVersion } from "../src/types/semantic-version"; +import { packageReference } from "../src/types/package-reference"; const packageA = "com.example.package-a" as DomainName; const packageB = "com.example.package-b" as DomainName; @@ -67,7 +67,7 @@ describe("cmd-remove.ts", function () { }, }; const retCode = await remove( - atVersion(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, "1.0.0" as SemanticVersion), options ); retCode.should.equal(1); diff --git a/test/test-cmd-view.ts b/test/test-cmd-view.ts index cbfca63f..94783b71 100644 --- a/test/test-cmd-view.ts +++ b/test/test-cmd-view.ts @@ -12,8 +12,8 @@ import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; import { buildPackageInfo } from "./data-pkg-info"; import { DomainName } from "../src/types/domain-name"; -import { atVersion } from "../src/utils/pkg-name"; import { SemanticVersion } from "../src/types/semantic-version"; +import { packageReference } from "../src/types/package-reference"; const packageA = "com.example.package-a" as DomainName; const packageUp = "com.example.package-up" as DomainName; @@ -125,7 +125,7 @@ describe("cmd-view.ts", function () { }); it("view pkg@1.0.0", async function () { const retCode = await view( - atVersion(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, "1.0.0" as SemanticVersion), options ); retCode.should.equal(1); diff --git a/test/test-package-reference.ts b/test/test-package-reference.ts new file mode 100644 index 00000000..9dea5cc3 --- /dev/null +++ b/test/test-package-reference.ts @@ -0,0 +1,50 @@ +import { describe } from "mocha"; +import should from "should"; +import { + isPackageReference, + packageReference, + PackageReference, + splitPackageReference, +} from "../src/types/package-reference"; + +describe("package-reference", function () { + describe("validation", function () { + [ + "com.abc.my-package", + "com.abc.my-package@1.2.3", + "com.abc.my-package@file://./my-package", + "com.abc.my-package@latest", + ].forEach((input) => + it(`"${input}" should be package-reference`, function () { + should(isPackageReference(input)).be.true(); + }) + ); + [ + // Not valid domain name + "-hello", + ].forEach((input) => + it(`"${input}" should not be package-reference`, function () { + should(isPackageReference(input)).not.be.true(); + }) + ); + }); + + describe("split", function () { + function shouldSplitCorrectly(name: string, version?: string) { + const [actualName, actualVersion] = splitPackageReference( + packageReference(name, version) + ); + should(actualName).be.equal(name); + should(actualVersion).be.equal(version); + } + + it("should split package without version", () => + shouldSplitCorrectly("com.abc.my-package")); + it("should split package with semantic version", () => + shouldSplitCorrectly("com.abc.my-package", "1.0.0")); + it("should split package with file-url", () => + shouldSplitCorrectly("com.abc.my-package", "file://./my-package")); + it("should split package with latest-tag", () => + shouldSplitCorrectly("com.abc.my-package", "latest")); + }); +}); diff --git a/test/test-pkg-name.ts b/test/test-pkg-name.ts deleted file mode 100644 index 8e7c602b..00000000 --- a/test/test-pkg-name.ts +++ /dev/null @@ -1,49 +0,0 @@ -import "assert"; -import "should"; -import { splitPkgName } from "../src/utils/pkg-name"; -import { PkgName } from "../src/types/global"; - -describe("pkg-name.ts", function () { - describe("splitPkgName", function () { - it("pkg@version", function () { - splitPkgName("pkg@1.0.0" as PkgName).should.deepEqual({ - name: "pkg", - version: "1.0.0", - }); - }); - it("pkg@latest", function () { - splitPkgName("pkg@latest" as PkgName).should.deepEqual({ - name: "pkg", - version: "latest", - }); - }); - it("pkg", function () { - splitPkgName("pkg" as PkgName).should.deepEqual({ - name: "pkg", - version: undefined, - }); - }); - it("pkg@file", function () { - splitPkgName("pkg@file:../pkg" as PkgName).should.deepEqual({ - name: "pkg", - version: "file:../pkg", - }); - }); - it("pkg@http", function () { - splitPkgName( - "pkg@https://github.com/owner/pkg" as PkgName - ).should.deepEqual({ - name: "pkg", - version: "https://github.com/owner/pkg", - }); - }); - it("pkg@git", function () { - splitPkgName( - "pkg@git@github.com:owner/pkg.git" as PkgName - ).should.deepEqual({ - name: "pkg", - version: "git@github.com:owner/pkg.git", - }); - }); - }); -}); diff --git a/test/types.ts b/test/types.ts index 13d88d9e..e2863c7c 100644 --- a/test/types.ts +++ b/test/types.ts @@ -1,5 +1,6 @@ -import { Contact, PkgVersion } from "../src/types/global"; +import { Contact } from "../src/types/global"; import { DomainName } from "../src/types/domain-name"; +import { SemanticVersion } from "../src/types/semantic-version"; type Maintainer = { username: string; email: string }; @@ -10,7 +11,7 @@ export type SearchEndpointResult = { description?: string; date: string; scope: "unscoped"; - version: PkgVersion; + version: SemanticVersion; links: Record; author: Contact; publisher: Maintainer; From 06d00c5475fb97af71283a5ee8a2db08aaf07934 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 24 Nov 2023 21:01:32 +0100 Subject: [PATCH 24/27] refactor: semantic-version constructor --- src/registry-client.ts | 2 +- src/types/semantic-version.ts | 10 ++++++++++ test/test-cmd-add.ts | 14 +++++++------- test/test-cmd-remove.ts | 4 ++-- test/test-cmd-search.ts | 5 ++--- test/test-cmd-view.ts | 8 ++++---- test/test-manifest.ts | 8 +++++--- test/test-pgk-info.ts | 4 ++-- 8 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/registry-client.ts b/src/registry-client.ts index 1dc2d069..27f69c86 100644 --- a/src/registry-client.ts +++ b/src/registry-client.ts @@ -13,7 +13,7 @@ import { env } from "./utils/env"; import _ from "lodash"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { DomainName, isInternalPackage } from "./types/domain-name"; -import { SemanticVersion } from "./types/semantic-version"; +import {semanticVersion, SemanticVersion} from "./types/semantic-version"; import { packageReference } from "./types/package-reference"; export type NpmClient = { diff --git a/src/types/semantic-version.ts b/src/types/semantic-version.ts index ff3b932f..d7e16b45 100644 --- a/src/types/semantic-version.ts +++ b/src/types/semantic-version.ts @@ -1,5 +1,6 @@ import { Brand } from "ts-brand"; import semver from "semver/preload"; +import assert from "assert"; /** * A string with a semantic-version format @@ -14,3 +15,12 @@ export type SemanticVersion = Brand; export function isSemanticVersion(s: string): s is SemanticVersion { return semver.parse(s) !== null; } + +/** + * Constructs a semantic version from a string. + * @param s The string. Will be validated. + */ +export function semanticVersion(s: string): SemanticVersion { + assert(isSemanticVersion(s), `"${s}" is a semantic version`); + return s; +} diff --git a/test/test-cmd-add.ts b/test/test-cmd-add.ts index 8a930f54..11450716 100644 --- a/test/test-cmd-add.ts +++ b/test/test-cmd-add.ts @@ -18,7 +18,7 @@ import { buildPackageInfo } from "./data-pkg-info"; import { buildPackageManifest } from "./data-pkg-manifest"; import { DomainName } from "../src/types/domain-name"; import { PackageUrl } from "../src/types/package-url"; -import { SemanticVersion } from "../src/types/semantic-version"; +import { semanticVersion } from "../src/types/semantic-version"; import { packageReference } from "../src/types/package-reference"; describe("cmd-add.ts", function () { @@ -162,7 +162,7 @@ describe("cmd-add.ts", function () { }); it("add pkg@1.0.0", async function () { const retCode = await add( - packageReference(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, semanticVersion("1.0.0")), options ); retCode.should.equal(0); @@ -181,12 +181,12 @@ describe("cmd-add.ts", function () { }); it("add pkg@0.1.0 then pkg@1.0.0", async function () { const retCode1 = await add( - packageReference(packageA, "0.1.0" as SemanticVersion), + packageReference(packageA, semanticVersion("0.1.0")), options ); retCode1.should.equal(0); const retCode2 = await add( - packageReference(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, semanticVersion("1.0.0")), options ); retCode2.should.equal(0); @@ -197,12 +197,12 @@ describe("cmd-add.ts", function () { }); it("add exited pkg version", async function () { const retCode1 = await add( - packageReference(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, semanticVersion("1.0.0")), options ); retCode1.should.equal(0); const retCode2 = await add( - packageReference(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, semanticVersion("1.0.0")), options ); retCode2.should.equal(0); @@ -213,7 +213,7 @@ describe("cmd-add.ts", function () { }); it("add pkg@not-exist-version", async function () { const retCode = await add( - packageReference(packageA, "2.0.0" as SemanticVersion), + packageReference(packageA, semanticVersion("2.0.0")), options ); retCode.should.equal(1); diff --git a/test/test-cmd-remove.ts b/test/test-cmd-remove.ts index 8a04e403..9cb37560 100644 --- a/test/test-cmd-remove.ts +++ b/test/test-cmd-remove.ts @@ -13,7 +13,7 @@ import { } from "./manifest-assertions"; import { buildPackageManifest } from "./data-pkg-manifest"; import { DomainName } from "../src/types/domain-name"; -import { SemanticVersion } from "../src/types/semantic-version"; +import { semanticVersion } from "../src/types/semantic-version"; import { packageReference } from "../src/types/package-reference"; const packageA = "com.example.package-a" as DomainName; @@ -67,7 +67,7 @@ describe("cmd-remove.ts", function () { }, }; const retCode = await remove( - packageReference(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, semanticVersion("1.0.0")), options ); retCode.should.equal(1); diff --git a/test/test-cmd-search.ts b/test/test-cmd-search.ts index 8551cb66..7919fb80 100644 --- a/test/test-cmd-search.ts +++ b/test/test-cmd-search.ts @@ -1,7 +1,6 @@ import nock from "nock"; import "should"; import { search, SearchOptions } from "../src/cmd-search"; - import { exampleRegistryUrl, registerSearchResult, @@ -12,7 +11,7 @@ import { SearchEndpointResult } from "./types"; import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; import { DomainName } from "../src/types/domain-name"; -import { SemanticVersion } from "../src/types/semantic-version"; +import { semanticVersion } from "../src/types/semantic-version"; describe("cmd-search.ts", function () { let mockConsole: MockConsole = null!; @@ -41,7 +40,7 @@ describe("cmd-search.ts", function () { package: { name: "com.example.package-a" as DomainName, scope: "unscoped", - version: "1.0.0" as SemanticVersion, + version: semanticVersion("1.0.0"), description: "A demo package", date: "2019-10-02T04:02:38.335Z", links: {}, diff --git a/test/test-cmd-view.ts b/test/test-cmd-view.ts index 94783b71..12fd1541 100644 --- a/test/test-cmd-view.ts +++ b/test/test-cmd-view.ts @@ -12,7 +12,7 @@ import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; import { buildPackageInfo } from "./data-pkg-info"; import { DomainName } from "../src/types/domain-name"; -import { SemanticVersion } from "../src/types/semantic-version"; +import { semanticVersion } from "../src/types/semantic-version"; import { packageReference } from "../src/types/package-reference"; const packageA = "com.example.package-a" as DomainName; @@ -41,7 +41,7 @@ describe("cmd-view.ts", function () { .set("time", { modified: "2019-11-28T18:51:58.123Z", created: "2019-11-28T18:51:58.123Z", - ["1.0.0" as SemanticVersion]: "2019-11-28T18:51:58.123Z", + [semanticVersion("1.0.0")]: "2019-11-28T18:51:58.123Z", }) .set("_rev", "3-418f950115c32bd0") .set("readme", "A demo package") @@ -73,7 +73,7 @@ describe("cmd-view.ts", function () { .set("time", { modified: "2019-11-28T18:51:58.123Z", created: "2019-11-28T18:51:58.123Z", - ["1.0.0" as SemanticVersion]: "2019-11-28T18:51:58.123Z", + [semanticVersion("1.0.0")]: "2019-11-28T18:51:58.123Z", }) .set("_rev", "3-418f950115c32bd0") .set("readme", "A demo package") @@ -125,7 +125,7 @@ describe("cmd-view.ts", function () { }); it("view pkg@1.0.0", async function () { const retCode = await view( - packageReference(packageA, "1.0.0" as SemanticVersion), + packageReference(packageA, semanticVersion("1.0.0")), options ); retCode.should.equal(1); diff --git a/test/test-manifest.ts b/test/test-manifest.ts index 58fded17..0b478842 100644 --- a/test/test-manifest.ts +++ b/test/test-manifest.ts @@ -12,7 +12,10 @@ import { shouldNotHaveAnyDependencies, } from "./manifest-assertions"; import { DomainName } from "../src/types/domain-name"; -import { SemanticVersion } from "../src/types/semantic-version"; +import { + semanticVersion, + SemanticVersion, +} from "../src/types/semantic-version"; describe("manifest", function () { let mockConsole: MockConsole = null!; @@ -75,8 +78,7 @@ describe("manifest", function () { ).should.be.ok(); const manifest = shouldHaveManifest(); shouldNotHaveAnyDependencies(manifest); - manifest.dependencies["some-pack" as DomainName] = - "1.0.0" as SemanticVersion; + manifest.dependencies["some-pack" as DomainName] = semanticVersion("1.0.0"); saveManifest(manifest).should.be.ok(); const manifest2 = shouldHaveManifest(); manifest2.should.be.deepEqual(manifest); diff --git a/test/test-pgk-info.ts b/test/test-pgk-info.ts index d8d988cd..1e4c5ac0 100644 --- a/test/test-pgk-info.ts +++ b/test/test-pgk-info.ts @@ -2,13 +2,13 @@ import { tryGetLatestVersion } from "../src/utils/pkg-info"; import "should"; import { describe } from "mocha"; import should from "should"; -import { SemanticVersion } from "../src/types/semantic-version"; +import { semanticVersion } from "../src/types/semantic-version"; describe("pkg-info", function () { describe("tryGetLatestVersion", function () { it("from dist-tags", async function () { const version = tryGetLatestVersion({ - "dist-tags": { latest: "1.0.0" as SemanticVersion }, + "dist-tags": { latest: semanticVersion("1.0.0") }, }); should(version).equal("1.0.0"); }); From 15c4121278f31661c01a51a51848778a3c5bcf61 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 24 Nov 2023 21:08:58 +0100 Subject: [PATCH 25/27] refactor: domain-name constructor --- src/cmd-view.ts | 6 ++---- src/registry-client.ts | 2 +- src/types/domain-name.ts | 13 ++++++++++++- test/data-pkg-manifest.ts | 4 ++-- test/mock-registry.ts | 8 +++++--- test/test-cmd-add.ts | 29 ++++++++++++++++------------- test/test-cmd-deps.ts | 6 +++--- test/test-cmd-remove.ts | 8 ++++---- test/test-cmd-search.ts | 4 ++-- test/test-cmd-view.ts | 8 ++++---- test/test-domain-name.ts | 12 ++++-------- test/test-manifest.ts | 9 +++------ test/test-package-reference.ts | 1 - test/test-registry-client.ts | 4 ++-- test/tsconfig.json | 6 ++---- 15 files changed, 62 insertions(+), 58 deletions(-) diff --git a/src/cmd-view.ts b/src/cmd-view.ts index 49e3d51d..0208cf66 100644 --- a/src/cmd-view.ts +++ b/src/cmd-view.ts @@ -90,11 +90,9 @@ const printInfo = function (pkg: PkgInfo) { if (dependencies && Object.keys(dependencies).length > 0) { console.log(); console.log("dependencies"); - Object.keys(dependencies) + (Object.keys(dependencies) as DomainName[]) .sort() - .forEach((n) => - console.log(chalk.yellow(n) + ` ${dependencies[n as DomainName]}`) - ); + .forEach((n) => console.log(chalk.yellow(n) + ` ${dependencies[n]}`)); } console.log(); diff --git a/src/registry-client.ts b/src/registry-client.ts index 27f69c86..1dc2d069 100644 --- a/src/registry-client.ts +++ b/src/registry-client.ts @@ -13,7 +13,7 @@ import { env } from "./utils/env"; import _ from "lodash"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { DomainName, isInternalPackage } from "./types/domain-name"; -import {semanticVersion, SemanticVersion} from "./types/semantic-version"; +import { SemanticVersion } from "./types/semantic-version"; import { packageReference } from "./types/package-reference"; export type NpmClient = { diff --git a/src/types/domain-name.ts b/src/types/domain-name.ts index 08762bfc..ecb94487 100644 --- a/src/types/domain-name.ts +++ b/src/types/domain-name.ts @@ -1,4 +1,5 @@ import { Brand } from "ts-brand"; +import assert from "assert"; /** * A string matching the format of a domain name. @@ -9,7 +10,7 @@ export type DomainName = Brand; const segmentRegex = /^(?!.*--|^-.*|.*-$)[a-zA-Z0-9-]+$/; -export const openUpmReverseDomainName = "com.openupm" as DomainName; +export const openUpmReverseDomainName = domainName("com.openupm"); function domainSegmentsIn(hostName: string): string[] { return hostName.split("."); @@ -70,3 +71,13 @@ export const isInternalPackage = (name: DomainName): boolean => { ]; return /com.unity.modules/i.test(name) || internals.includes(name); }; + +/** + * Constructs a domain-name from a string. + * @param s The string. + * @throws assert.AssertionError if string is not in valid format + */ +export function domainName(s: string): DomainName { + assert(isDomainName(s), `"${s}" is a domain name`); + return s; +} diff --git a/test/data-pkg-manifest.ts b/test/data-pkg-manifest.ts index bd0173d0..ea6404fe 100644 --- a/test/data-pkg-manifest.ts +++ b/test/data-pkg-manifest.ts @@ -1,6 +1,6 @@ import { PkgManifest } from "../src/types/global"; import assert from "assert"; -import { DomainName, isDomainName } from "../src/types/domain-name"; +import { domainName, isDomainName } from "../src/types/domain-name"; import { exampleRegistryUrl } from "./mock-registry"; import { isSemanticVersion } from "../src/types/semantic-version"; @@ -27,7 +27,7 @@ class PkgManifestBuilder { this.manifest.scopedRegistries = [ { name: "example.com", - scopes: ["com.example" as DomainName], + scopes: [domainName("com.example")], url: exampleRegistryUrl, }, ]; diff --git a/test/mock-registry.ts b/test/mock-registry.ts index 99fb9e26..1b35517c 100644 --- a/test/mock-registry.ts +++ b/test/mock-registry.ts @@ -1,11 +1,12 @@ import { PkgInfo } from "../src/types/global"; import nock from "nock"; import { SearchEndpointResult } from "./types"; -import { DomainName } from "../src/types/domain-name"; +import { domainName, isDomainName } from "../src/types/domain-name"; +import assert from "assert"; export const unityRegistryUrl = "https://packages.unity.com"; export const exampleRegistryUrl = "http://example.com"; -export const exampleRegistryReverseDomain = "com.example" as DomainName; +export const exampleRegistryReverseDomain = domainName("com.example"); export function startMockRegistry() { if (!nock.isActive()) nock.activate(); @@ -25,7 +26,8 @@ export function registerRemoteUpstreamPkg(pkg: PkgInfo) { nock(exampleRegistryUrl).persist().get(`/${pkg.name}`).reply(404); } -export function registerMissingPackage(name: DomainName) { +export function registerMissingPackage(name: string) { + assert(isDomainName(name)); nock(exampleRegistryUrl).persist().get(`/${name}`).reply(404); nock(unityRegistryUrl).persist().get(`/${name}`).reply(404); } diff --git a/test/test-cmd-add.ts b/test/test-cmd-add.ts index 11450716..ab5910da 100644 --- a/test/test-cmd-add.ts +++ b/test/test-cmd-add.ts @@ -16,24 +16,27 @@ import { } from "./manifest-assertions"; import { buildPackageInfo } from "./data-pkg-info"; import { buildPackageManifest } from "./data-pkg-manifest"; -import { DomainName } from "../src/types/domain-name"; +import { domainName } from "../src/types/domain-name"; import { PackageUrl } from "../src/types/package-url"; import { semanticVersion } from "../src/types/semantic-version"; import { packageReference } from "../src/types/package-reference"; describe("cmd-add.ts", function () { - const packageMissing = "pkg-not-exist" as DomainName; - const packageA = "com.base.package-a" as DomainName; - const packageB = "com.base.package-b" as DomainName; - const packageC = "com.base.package-c" as DomainName; - const packageD = "com.base.package-d" as DomainName; - const packageUp = "com.upstream.package-up" as DomainName; - const packageLowerEditor = - "com.base.package-with-lower-editor-version" as DomainName; - const packageHigherEditor = - "com.base.package-with-higher-editor-version" as DomainName; - const packageWrongEditor = - "com.base.package-with-wrong-editor-version" as DomainName; + const packageMissing = domainName("pkg-not-exist"); + const packageA = domainName("com.base.package-a"); + const packageB = domainName("com.base.package-b"); + const packageC = domainName("com.base.package-c"); + const packageD = domainName("com.base.package-d"); + const packageUp = domainName("com.upstream.package-up"); + const packageLowerEditor = domainName( + "com.base.package-with-lower-editor-version" + ); + const packageHigherEditor = domainName( + "com.base.package-with-higher-editor-version" + ); + const packageWrongEditor = domainName( + "com.base.package-with-wrong-editor-version" + ); const options: AddOptions = { _global: { diff --git a/test/test-cmd-deps.ts b/test/test-cmd-deps.ts index c299430c..fd91acb5 100644 --- a/test/test-cmd-deps.ts +++ b/test/test-cmd-deps.ts @@ -11,7 +11,7 @@ import { import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; import { buildPackageInfo } from "./data-pkg-info"; -import { DomainName } from "../src/types/domain-name"; +import { domainName } from "../src/types/domain-name"; import { packageReference } from "../src/types/package-reference"; describe("cmd-deps.ts", function () { @@ -44,7 +44,7 @@ describe("cmd-deps.ts", function () { startMockRegistry(); registerRemotePkg(remotePkgInfoA); registerRemotePkg(remotePkgInfoB); - registerMissingPackage("pkg-not-exist" as DomainName); + registerMissingPackage("pkg-not-exist"); registerRemoteUpstreamPkg(remotePkgInfoUp); mockConsole = attachMockConsole(); @@ -95,7 +95,7 @@ describe("cmd-deps.ts", function () { .should.be.ok(); }); it("deps pkg-not-exist", async function () { - const retCode = await deps("pkg-not-exist" as DomainName, options); + const retCode = await deps(domainName("pkg-not-exist"), options); retCode.should.equal(0); mockConsole.hasLineIncluding("out", "not found").should.be.ok(); }); diff --git a/test/test-cmd-remove.ts b/test/test-cmd-remove.ts index 9cb37560..6ad76d58 100644 --- a/test/test-cmd-remove.ts +++ b/test/test-cmd-remove.ts @@ -12,13 +12,13 @@ import { shouldNotHaveDependency, } from "./manifest-assertions"; import { buildPackageManifest } from "./data-pkg-manifest"; -import { DomainName } from "../src/types/domain-name"; +import { domainName } from "../src/types/domain-name"; import { semanticVersion } from "../src/types/semantic-version"; import { packageReference } from "../src/types/package-reference"; -const packageA = "com.example.package-a" as DomainName; -const packageB = "com.example.package-b" as DomainName; -const missingPackage = "pkg-not-exist" as DomainName; +const packageA = domainName("com.example.package-a"); +const packageB = domainName("com.example.package-b"); +const missingPackage = domainName("pkg-not-exist"); describe("cmd-remove.ts", function () { describe("remove", function () { diff --git a/test/test-cmd-search.ts b/test/test-cmd-search.ts index 7919fb80..2c26f1e2 100644 --- a/test/test-cmd-search.ts +++ b/test/test-cmd-search.ts @@ -10,7 +10,7 @@ import { import { SearchEndpointResult } from "./types"; import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; -import { DomainName } from "../src/types/domain-name"; +import { domainName } from "../src/types/domain-name"; import { semanticVersion } from "../src/types/semantic-version"; describe("cmd-search.ts", function () { @@ -38,7 +38,7 @@ describe("cmd-search.ts", function () { objects: [ { package: { - name: "com.example.package-a" as DomainName, + name: domainName("com.example.package-a"), scope: "unscoped", version: semanticVersion("1.0.0"), description: "A demo package", diff --git a/test/test-cmd-view.ts b/test/test-cmd-view.ts index 12fd1541..e8a1ae9c 100644 --- a/test/test-cmd-view.ts +++ b/test/test-cmd-view.ts @@ -11,13 +11,13 @@ import { import { createWorkDir, getWorkDir, removeWorkDir } from "./mock-work-dir"; import { attachMockConsole, MockConsole } from "./mock-console"; import { buildPackageInfo } from "./data-pkg-info"; -import { DomainName } from "../src/types/domain-name"; +import { domainName } from "../src/types/domain-name"; import { semanticVersion } from "../src/types/semantic-version"; import { packageReference } from "../src/types/package-reference"; -const packageA = "com.example.package-a" as DomainName; -const packageUp = "com.example.package-up" as DomainName; -const packageMissing = "pkg-not-exist" as DomainName; +const packageA = domainName("com.example.package-a"); +const packageUp = domainName("com.example.package-up"); +const packageMissing = domainName("pkg-not-exist"); describe("cmd-view.ts", function () { const options: ViewOptions = { diff --git a/test/test-domain-name.ts b/test/test-domain-name.ts index 80d6f570..c9f9c779 100644 --- a/test/test-domain-name.ts +++ b/test/test-domain-name.ts @@ -1,6 +1,6 @@ import { describe } from "mocha"; import { - DomainName, + domainName, isDomainName, isInternalPackage, namespaceFor, @@ -50,17 +50,13 @@ describe("domain-name", function () { }); describe("internal package", function () { it("test com.otherorg.software", function () { - isInternalPackage( - "com.otherorg.software" as DomainName - ).should.not.be.ok(); + isInternalPackage(domainName("com.otherorg.software")).should.not.be.ok(); }); it("test com.unity.ugui", function () { - isInternalPackage("com.unity.ugui" as DomainName).should.be.ok(); + isInternalPackage(domainName("com.unity.ugui")).should.be.ok(); }); it("test com.unity.modules.tilemap", function () { - isInternalPackage( - "com.unity.modules.tilemap" as DomainName - ).should.be.ok(); + isInternalPackage(domainName("com.unity.modules.tilemap")).should.be.ok(); }); }); }); diff --git a/test/test-manifest.ts b/test/test-manifest.ts index 0b478842..8ab18eb4 100644 --- a/test/test-manifest.ts +++ b/test/test-manifest.ts @@ -11,11 +11,8 @@ import { shouldHaveNoManifest, shouldNotHaveAnyDependencies, } from "./manifest-assertions"; -import { DomainName } from "../src/types/domain-name"; -import { - semanticVersion, - SemanticVersion, -} from "../src/types/semantic-version"; +import { domainName } from "../src/types/domain-name"; +import { semanticVersion } from "../src/types/semantic-version"; describe("manifest", function () { let mockConsole: MockConsole = null!; @@ -78,7 +75,7 @@ describe("manifest", function () { ).should.be.ok(); const manifest = shouldHaveManifest(); shouldNotHaveAnyDependencies(manifest); - manifest.dependencies["some-pack" as DomainName] = semanticVersion("1.0.0"); + manifest.dependencies[domainName("some-pack")] = semanticVersion("1.0.0"); saveManifest(manifest).should.be.ok(); const manifest2 = shouldHaveManifest(); manifest2.should.be.deepEqual(manifest); diff --git a/test/test-package-reference.ts b/test/test-package-reference.ts index 9dea5cc3..dcf30671 100644 --- a/test/test-package-reference.ts +++ b/test/test-package-reference.ts @@ -3,7 +3,6 @@ import should from "should"; import { isPackageReference, packageReference, - PackageReference, splitPackageReference, } from "../src/types/package-reference"; diff --git a/test/test-registry-client.ts b/test/test-registry-client.ts index dffa5fbc..da03aaaa 100644 --- a/test/test-registry-client.ts +++ b/test/test-registry-client.ts @@ -11,9 +11,9 @@ import { } from "./mock-registry"; import should from "should"; import { buildPackageInfo } from "./data-pkg-info"; -import { DomainName } from "../src/types/domain-name"; +import { domainName } from "../src/types/domain-name"; -const packageA = "package-a" as DomainName; +const packageA = domainName("package-a"); describe("registry-client", function () { describe("fetchPackageInfo", function () { diff --git a/test/tsconfig.json b/test/tsconfig.json index 1781818b..6392488d 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -58,7 +58,7 @@ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ // "outDir": "./lib", /* Specify an output folder for all emitted files. */ // "removeComments": true, /* Disable emitting comments. */ - "noEmit": true, /* Disable emitting files from a compilation. */ + "noEmit": true /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ @@ -107,7 +107,5 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ // "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "include": [ - "./**/*" - ] + "include": ["./**/*"] } From 73e0df92a594c3587ee81d2e84733c576e0b8c48 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sat, 25 Nov 2023 13:50:50 +0100 Subject: [PATCH 26/27] refactor: add registry-url type --- src/cmd-login.ts | 39 +++++++++++++++------------------ src/cmd-search.ts | 5 +++-- src/registry-client.ts | 5 +++-- src/types/global.ts | 15 ++++++------- src/types/registry-url.ts | 45 +++++++++++++++++++++++++++++++++++++++ src/utils/env.ts | 31 ++++++++++++++------------- test/test-cmd-login.ts | 34 +++++++---------------------- test/test-registry-url.ts | 45 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 144 insertions(+), 75 deletions(-) create mode 100644 src/types/registry-url.ts create mode 100644 test/test-registry-url.ts diff --git a/src/cmd-login.ts b/src/cmd-login.ts index e5198e47..fbb010d3 100644 --- a/src/cmd-login.ts +++ b/src/cmd-login.ts @@ -6,13 +6,18 @@ import { assertIsNpmClientError, getNpmClient } from "./registry-client"; import log from "./logger"; -import { GlobalOptions, Registry } from "./types/global"; +import { GlobalOptions } from "./types/global"; import { getUpmConfigDir, loadUpmConfig, saveUpmConfig, } from "./utils/upm-config"; import { parseEnv } from "./utils/env"; +import { + RegistryUrl, + registryUrl, + removeTrailingSlash, +} from "./types/registry-url"; export type LoginOptions = { username?: string; @@ -33,9 +38,9 @@ export const login = async function (options: LoginOptions) { options.password = await promptly.password("Password: "); if (!options.email) options.email = await promptly.prompt("Email: "); if (!options._global.registry) - options._global.registry = await promptly.prompt("Registry: ", { - validator: [validateRegistry], - }); + options._global.registry = (await promptly.prompt("Registry: ", { + validator: [registryUrl], + })) as RegistryUrl; let token: string | null = null; let _auth: string | null = null; if (options.basicAuth) { @@ -48,7 +53,7 @@ export const login = async function (options: LoginOptions) { username: options.username, password: options.password, email: options.email, - registry: options._global.registry, + registry: options._global.registry as RegistryUrl, }); if (result.code == 1) return result.code; if (!result.token) { @@ -58,7 +63,7 @@ export const login = async function (options: LoginOptions) { token = result.token; // write npm token await writeNpmToken({ - registry: options._global.registry, + registry: options._global.registry as RegistryUrl, token: result.token, }); } @@ -69,7 +74,7 @@ export const login = async function (options: LoginOptions) { alwaysAuth: options.alwaysAuth || false, basicAuth: options.basicAuth || false, email: options.email, - registry: options._global.registry, + registry: options._global.registry as RegistryUrl, token, }); }; @@ -86,7 +91,7 @@ const npmLogin = async function ({ username: string; password: string; email: string; - registry: Registry; + registry: RegistryUrl; }) { const client = getNpmClient(); try { @@ -125,7 +130,7 @@ const writeNpmToken = async function ({ registry, token, }: { - registry: Registry; + registry: RegistryUrl; token: string; }) { const configPath = getNpmrcPath(); @@ -160,7 +165,7 @@ export const getNpmrcPath = function () { */ export const generateNpmrcLines = function ( content: string, - registry: Registry, + registry: RegistryUrl, token: string ) { let lines = content ? content.split("\n") : []; @@ -189,16 +194,6 @@ export const generateNpmrcLines = function ( return lines; }; -/** - * http protocal validator - * @param {*} value - */ -export const validateRegistry = function (value: Registry): Registry { - if (!/http(s?):\/\//.test(value)) - throw new Error("The registry address should starts with http(s)://"); - return value; -}; - /** * Write npm token to Unity */ @@ -214,7 +209,7 @@ const writeUnityToken = async function ({ alwaysAuth: boolean; basicAuth: boolean; email: string; - registry: Registry; + registry: RegistryUrl; token: string | null; }) { // Create config dir if necessary @@ -223,7 +218,7 @@ const writeUnityToken = async function ({ const config = (await loadUpmConfig(configDir)) || {}; if (!config.npmAuth) config.npmAuth = {}; // Remove ending slash of registry - if (registry.endsWith("/")) registry = registry.replace(/\/$/, ""); + registry = removeTrailingSlash(registry); if (basicAuth) { if (_auth === null) throw new Error("Auth is null"); diff --git a/src/cmd-search.ts b/src/cmd-search.ts index 196f111b..e1d4a4ec 100644 --- a/src/cmd-search.ts +++ b/src/cmd-search.ts @@ -5,11 +5,12 @@ import log from "./logger"; import { is404Error, isHttpError } from "./utils/error-type-guards"; import * as os from "os"; import assert from "assert"; -import { GlobalOptions, PkgInfo, Registry } from "./types/global"; +import { GlobalOptions, PkgInfo } from "./types/global"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { env, parseEnv } from "./utils/env"; import { DomainName } from "./types/domain-name"; import { SemanticVersion } from "./types/semantic-version"; +import { RegistryUrl } from "./types/registry-url"; type DateString = string; @@ -40,7 +41,7 @@ const getNpmFetchOptions = function (): Options { const searchEndpoint = async function ( keyword: string, - registry?: Registry + registry?: RegistryUrl ): Promise { if (!registry) registry = env.registry; try { diff --git a/src/registry-client.ts b/src/registry-client.ts index 1dc2d069..99d20e32 100644 --- a/src/registry-client.ts +++ b/src/registry-client.ts @@ -8,13 +8,14 @@ import RegClient, { import log from "./logger"; import request from "request"; import assert, { AssertionError } from "assert"; -import { Dependency, NameVersionPair, PkgInfo, Registry } from "./types/global"; +import { Dependency, NameVersionPair, PkgInfo } from "./types/global"; import { env } from "./utils/env"; import _ from "lodash"; import { tryGetLatestVersion } from "./utils/pkg-info"; import { DomainName, isInternalPackage } from "./types/domain-name"; import { SemanticVersion } from "./types/semantic-version"; import { packageReference } from "./types/package-reference"; +import { RegistryUrl } from "./types/registry-url"; export type NpmClient = { rawClient: RegClient; @@ -92,7 +93,7 @@ export const getNpmClient = (): NpmClient => { // Fetch package info json from registry export const fetchPackageInfo = async function ( name: DomainName, - registry?: Registry + registry?: RegistryUrl ): Promise { if (!registry) registry = env.registry; const pkgPath = `${registry}/${name}`; diff --git a/src/types/global.ts b/src/types/global.ts index 4644d006..f9803a9a 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -4,11 +4,10 @@ import { DomainName } from "./domain-name"; import { PackageUrl } from "./package-url"; import { SemanticVersion } from "./semantic-version"; import { PackageId } from "./package-id"; +import { RegistryUrl } from "./registry-url"; export type Region = "us" | "cn"; -export type Registry = string; - export type EditorVersion = { major: number; minor: number; @@ -26,11 +25,11 @@ export type Env = { color: boolean; systemUser: boolean; wsl: boolean; - npmAuth?: Record; - auth: Record; + npmAuth?: Record; + auth: Record; upstream: boolean; - upstreamRegistry: string; - registry: string; + upstreamRegistry: RegistryUrl; + registry: RegistryUrl; namespace: DomainName | IpAddress; editorVersion: string | null; region: Region; @@ -120,7 +119,7 @@ export type PkgManifest = { }; export type GlobalOptions = { - registry?: Registry; + registry?: string; verbose?: boolean; color?: boolean; upstream?: boolean; @@ -136,5 +135,5 @@ export type UpmAuth = { } & ({ token: string } | { _auth: string }); export type UPMConfig = { - npmAuth?: Record; + npmAuth?: Record; }; diff --git a/src/types/registry-url.ts b/src/types/registry-url.ts new file mode 100644 index 00000000..b3f20478 --- /dev/null +++ b/src/types/registry-url.ts @@ -0,0 +1,45 @@ +import { Brand } from "ts-brand"; +import assert from "assert"; + +/** + * A string of a http-based registry-url + */ +export type RegistryUrl = Brand; + +/** + * Checks that a string is a valid registry + * @param s The string + */ +export function isRegistryUrl(s: string): s is RegistryUrl { + return /http(s?):\/\//.test(s); +} + +/** + * Constructs a registry-url + * @param s The string + * @throws assert.AssertionError if string does not have valid format + */ +export function registryUrl(s: string): RegistryUrl { + assert(isRegistryUrl(s), `"${s}" is url`); + return s; +} + +/** + * Removes trailing slash from a registry-url + * @param registry The url + */ +export function removeTrailingSlash(registry: RegistryUrl): RegistryUrl { + if (registry.endsWith("/")) return registry.slice(0, -1) as RegistryUrl; + return registry; +} + +/** + * Attempts to coerce a string into a registry-url, by + * - Prepending http if it is missing + * - Removing trailing slashes + * @param s The string + */ +export function coerceRegistryUrl(s: string): RegistryUrl { + if (!s.toLowerCase().startsWith("http")) s = "http://" + s; + return removeTrailingSlash(registryUrl(s)); +} diff --git a/src/utils/env.ts b/src/utils/env.ts index 8c0a83ab..bca13744 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -1,13 +1,18 @@ import { Env, GlobalOptions } from "../types/global"; import log from "../logger"; import chalk from "chalk"; -import url from "url"; import { loadUpmConfig } from "./upm-config"; import path from "path"; import fs from "fs"; import yaml from "yaml"; import { isIpAddress } from "../types/ip-address"; import { namespaceFor, openUpmReverseDomainName } from "../types/domain-name"; +import { + coerceRegistryUrl, + RegistryUrl, + registryUrl, +} from "../types/registry-url"; +import url from "url"; export const env: Env = {}; @@ -17,13 +22,13 @@ export const parseEnv = async function ( { checkPath }: { checkPath: unknown } ) { // set defaults - env.registry = "https://package.openupm.com"; + env.registry = registryUrl("https://package.openupm.com"); env.cwd = ""; env.manifestPath = ""; env.namespace = openUpmReverseDomainName; env.upstream = true; env.color = true; - env.upstreamRegistry = "https://packages.unity.com"; + env.upstreamRegistry = registryUrl("https://packages.unity.com"); env.systemUser = false; env.wsl = false; env.editorVersion = null; @@ -45,20 +50,16 @@ export const parseEnv = async function ( if (options._global.upstream === false) env.upstream = false; // region cn if (options._global.cn === true) { - env.registry = "https://package.openupm.cn"; - env.upstreamRegistry = "https://packages.unity.cn"; + env.registry = registryUrl("https://package.openupm.cn"); + env.upstreamRegistry = registryUrl("https://packages.unity.cn"); env.region = "cn"; log.notice("region", "cn"); } // registry if (options._global.registry) { - let registry = options._global.registry; - if (!registry.toLowerCase().startsWith("http")) - registry = "http://" + registry; - if (registry.endsWith("/")) registry = registry.slice(0, -1); - env.registry = registry; + env.registry = coerceRegistryUrl(options._global.registry); // TODO: Check hostname for null - const hostname = url.parse(registry).hostname as string; + const hostname = url.parse(env.registry).hostname as string; if (isIpAddress(hostname)) env.namespace = hostname; else env.namespace = namespaceFor(hostname); } @@ -68,9 +69,9 @@ export const parseEnv = async function ( const upmConfig = await loadUpmConfig(); if (upmConfig) { env.npmAuth = upmConfig.npmAuth; - if (env.npmAuth) { - for (const reg in env.npmAuth) { - const regAuth = env.npmAuth[reg]; + if (env.npmAuth !== undefined) { + (Object.keys(env.npmAuth) as RegistryUrl[]).forEach((reg) => { + const regAuth = env.npmAuth![reg]; if ("token" in regAuth) { env.auth[reg] = { token: regAuth.token, @@ -93,7 +94,7 @@ export const parseEnv = async function ( ); log.warn("env.auth", regAuth); } - } + }); } } // log.verbose("env.npmAuth", env.npmAuth); diff --git a/test/test-cmd-login.ts b/test/test-cmd-login.ts index 2c42d653..62dc4f85 100644 --- a/test/test-cmd-login.ts +++ b/test/test-cmd-login.ts @@ -1,38 +1,20 @@ import "nock"; -import should from "should"; -import { - generateNpmrcLines, - getNpmrcPath, - validateRegistry, -} from "../src/cmd-login"; +import { generateNpmrcLines, getNpmrcPath } from "../src/cmd-login"; +import { registryUrl } from "../src/types/registry-url"; describe("cmd-login.ts", function () { - describe("validateRegistry", function () { - it("should validate http", async function () { - validateRegistry("http://registry.npmjs.org/").should.be.ok(); - }); - it("should validate https", async function () { - validateRegistry("https://registry.npmjs.org/").should.be.ok(); - }); - it("should reject without http protocal", async function () { - should(function () { - validateRegistry("registry.npmjs.org/"); - }).throw("The registry address should starts with http(s)://"); - }); - }); - describe("generateNpmrcLines", function () { it("should append token to empty content", async function () { generateNpmrcLines( "", - "http://registry.npmjs.org/", + registryUrl("http://registry.npmjs.org/"), "123-456-789" ).should.deepEqual(["//registry.npmjs.org/:_authToken=123-456-789"]); }); it("should append token to exist contents", async function () { generateNpmrcLines( "registry=https://registry.npmjs.org/", - "http://registry.npmjs.org/", + registryUrl(" registry(http://registry.npmjs.org/"), "123-456-789" ).should.deepEqual([ "registry=https://registry.npmjs.org/", @@ -42,7 +24,7 @@ describe("cmd-login.ts", function () { it("should replace token to exist contents", async function () { generateNpmrcLines( "registry=https://registry.npmjs.org/\n//127.0.0.1:4873/:_authToken=blar-blar-blar\n//registry.npmjs.org/:_authToken=blar-blar-blar", - "http://registry.npmjs.org/", + registryUrl("http://registry.npmjs.org/"), "123-456-789" ).should.deepEqual([ "registry=https://registry.npmjs.org/", @@ -53,19 +35,19 @@ describe("cmd-login.ts", function () { it("should handle registry without trailing slash", async function () { generateNpmrcLines( "", - "http://registry.npmjs.org", + registryUrl("http://registry.npmjs.org"), "123-456-789" ).should.deepEqual(["//registry.npmjs.org/:_authToken=123-456-789"]); }); it("should quote token if necessary", async function () { generateNpmrcLines( "", - "http://registry.npmjs.org/", + registryUrl("http://registry.npmjs.org/"), "=123-456-789=" ).should.deepEqual(['//registry.npmjs.org/:_authToken="=123-456-789="']); generateNpmrcLines( "", - "http://registry.npmjs.org/", + registryUrl("http://registry.npmjs.org/"), "?123-456-789?" ).should.deepEqual(['//registry.npmjs.org/:_authToken="?123-456-789?"']); }); diff --git a/test/test-registry-url.ts b/test/test-registry-url.ts new file mode 100644 index 00000000..498d5e88 --- /dev/null +++ b/test/test-registry-url.ts @@ -0,0 +1,45 @@ +import { describe } from "mocha"; +import { + coerceRegistryUrl, + isRegistryUrl, + registryUrl, + removeTrailingSlash, +} from "../src/types/registry-url"; +import should from "should"; + +describe("registry-url", function () { + describe("validation", function () { + ["http://registry.npmjs.org/", "https://registry.npmjs.org/"].forEach( + (input) => + it(`"${input}" should be registry-url`, function () { + should(isRegistryUrl(input)).be.ok(); + }) + ); + [ + // Missing protocol + "registry.npmjs.org/", + ].forEach((input) => + it(`"${input}" should not be registry-url`, function () { + should(isRegistryUrl(input)).not.be.ok(); + }) + ); + }); + describe("remove trailing slash", function () { + it("should remove trailing slash if it is exists", () => + should(removeTrailingSlash(registryUrl("http://test.com/"))).be.equal( + "http://test.com" + )); + it("should do nothing if there is no trailing slash", () => + should(removeTrailingSlash(registryUrl("http://test.com"))).be.equal( + "http://test.com" + )); + }); + describe("coerce", function () { + it("should coerce urls without protocol", () => + should(coerceRegistryUrl("test.com")).be.equal("http://test.com")); + it("should remove trailing slash", () => + should(coerceRegistryUrl("http://test.com/")).be.equal( + "http://test.com" + )); + }); +}); From c50ab92fe1ac511d855e9b7f56de4ccbc2764739 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sat, 25 Nov 2023 14:05:18 +0100 Subject: [PATCH 27/27] refactor: use correct type --- src/types/global.ts | 2 +- test/mock-registry.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/types/global.ts b/src/types/global.ts index f9803a9a..85d2f82c 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -108,7 +108,7 @@ export type Dependency = { export type ScopedRegistry = { name: string; - url: string; + url: RegistryUrl; scopes: DomainName[]; }; diff --git a/test/mock-registry.ts b/test/mock-registry.ts index 1b35517c..bcddc0e9 100644 --- a/test/mock-registry.ts +++ b/test/mock-registry.ts @@ -3,9 +3,10 @@ import nock from "nock"; import { SearchEndpointResult } from "./types"; import { domainName, isDomainName } from "../src/types/domain-name"; import assert from "assert"; +import { registryUrl } from "../src/types/registry-url"; -export const unityRegistryUrl = "https://packages.unity.com"; -export const exampleRegistryUrl = "http://example.com"; +export const unityRegistryUrl = registryUrl("https://packages.unity.com"); +export const exampleRegistryUrl = registryUrl("http://example.com"); export const exampleRegistryReverseDomain = domainName("com.example"); export function startMockRegistry() {