diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 122502e7..00000000 --- a/.eslintrc +++ /dev/null @@ -1,33 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint"], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "plugin:import/errors", - "plugin:import/warnings", - "plugin:import/typescript", - "prettier" // disable rules that are redundant with prettier - ], - "parserOptions": { - "ecmaVersion": 2020, - "sourceType": "module" - }, - "ignorePatterns": ["**/node_modules/**", "**/lib/**", "**/*.js"], - "rules": { - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/camelcase": "off", - "prefer-const": "off", - "no-prototype-builtins": "off", - "@typescript-eslint/consistent-type-assertions": "off", - "import/namespace": "off", - "import/order": "warn", - "import/no-unresolved": "off", - "import/default": "off" - } -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..064b193d --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,60 @@ +module.exports = { + root: true, + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:import/errors", + "plugin:import/warnings", + "plugin:import/typescript", + "prettier", // disable rules that are redundant with prettier + ], + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, + ignorePatterns: ["**/node_modules/**", "**/lib/**", "**/*.js"], + rules: { + "@typescript-eslint/no-shadow": ["error", { ignoreTypeValueShadow: true }], + "@typescript-eslint/explicit-module-boundary-types": "error", + "prefer-const": ["error", { destructuring: "all" }], + + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/camelcase": "off", + "no-prototype-builtins": "off", + "@typescript-eslint/consistent-type-assertions": "off", + "import/namespace": "off", + "import/order": "warn", + "import/no-unresolved": "off", + "import/default": "off", + }, + overrides: [ + { + files: ["**/*.test.ts"], + rules: { + "no-restricted-properties": [ + "error", + ...["describe", "it", "test"] + .map((func) => [ + { + object: func, + property: "only", + message: "Do not commit .only() tests", + }, + { + object: func, + property: "skip", + message: + "Do not commit .skip() tests (disable this rule if needed)", + }, + ]) + .flat(), + ], + }, + }, + ], +}; diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 72a08391..cdabff53 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -42,12 +42,12 @@ jobs: if: ${{ matrix.os == 'ubuntu-latest' }} run: yarn syncpack:check + - run: yarn format:check + if: ${{ matrix.os == 'ubuntu-latest' }} + - run: yarn build - run: yarn lint if: ${{ matrix.os == 'ubuntu-latest' }} - - run: yarn format:check - if: ${{ matrix.os == 'ubuntu-latest' }} - - run: yarn test diff --git a/.vscode/launch.json b/.vscode/launch.json index 318c8c5a..e95d7e26 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,7 +13,7 @@ "request": "launch", "program": "${workspaceRoot}/tools/debugTests.js", "cwd": "${fileDirname}", - "args": ["--runInBand", "--watch", "${fileBasename}"], + "args": ["${fileBasename}"], "sourceMaps": true, "outputCapture": "std", "console": "integratedTerminal" diff --git a/change/change-229f3af9-c39b-4355-be59-58895bb8bd4e.json b/change/change-229f3af9-c39b-4355-be59-58895bb8bd4e.json new file mode 100644 index 00000000..cf5df7ba --- /dev/null +++ b/change/change-229f3af9-c39b-4355-be59-58895bb8bd4e.json @@ -0,0 +1,46 @@ +{ + "changes": [ + { + "type": "none", + "comment": "Simplify build script", + "packageName": "backfill", + "email": "elcraig@microsoft.com", + "dependentChangeType": "none" + }, + { + "type": "patch", + "comment": "Move logic out of index file (no behavior change)", + "packageName": "backfill-cache", + "email": "elcraig@microsoft.com", + "dependentChangeType": "patch" + }, + { + "type": "none", + "comment": "Simplify build script", + "packageName": "backfill-config", + "email": "elcraig@microsoft.com", + "dependentChangeType": "none" + }, + { + "type": "minor", + "comment": "Improve internal structure and remove excess exports. If you were using exports besides Hasher, please file an issue.", + "packageName": "backfill-hasher", + "email": "elcraig@microsoft.com", + "dependentChangeType": "patch" + }, + { + "type": "none", + "comment": "Simplify build script", + "packageName": "backfill-logger", + "email": "elcraig@microsoft.com", + "dependentChangeType": "none" + }, + { + "type": "none", + "comment": "Simplify build script", + "packageName": "backfill-utils-dotenv", + "email": "elcraig@microsoft.com", + "dependentChangeType": "none" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index a1f15511..518b4d02 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "**/@azure/core-util": "~1.12.0", "**/@azure/core-xml": "~1.4.5", "**/@azure/logger": "~1.2.0", + "**/syncpack/minimatch": "^6.0.0", "**/cache-base/union-value/set-value": "^2.0.0" }, "devDependencies": { @@ -74,11 +75,14 @@ }, "rationale": { "resolutions": { - "@azure/*": "Node 18 compatibility" + "@azure/*": "Node 18 compatibility", + "syncpack/minimatch": "Unpin minimatch due to security issue. Should match original major version." } }, "syncpack": { - "peer": false, + "prod": true, + "dev": true, + "peer": true, "workspace": false, "resolutions": false } diff --git a/packages/backfill/package.json b/packages/backfill/package.json index 2cffbc2e..67b540fe 100644 --- a/packages/backfill/package.json +++ b/packages/backfill/package.json @@ -13,8 +13,7 @@ "backfill": "./bin/backfill.js" }, "scripts": { - "build": "yarn compile", - "compile": "tsc -b", + "build": "tsc -b --pretty", "test": "jest", "watch": "tsc -b -w" }, @@ -33,7 +32,7 @@ "yargs": "^16.1.1" }, "devDependencies": { - "@types/fs-extra": "^9.0.13", + "@types/fs-extra": "^8.0.0", "@types/jest": "^30.0.0", "@types/node": "^14.18.36", "@types/yargs": "^15.0.15", diff --git a/packages/backfill/src/__tests__/cli.test.ts b/packages/backfill/src/__tests__/cli.test.ts index fea6cf2e..28f06640 100644 --- a/packages/backfill/src/__tests__/cli.test.ts +++ b/packages/backfill/src/__tests__/cli.test.ts @@ -6,11 +6,16 @@ import { makeLogger } from "backfill-logger"; import { createBuildCommand } from "../commandRunner"; -const logger = makeLogger("mute"); +const muteLogger = makeLogger("mute"); describe("createBuildCommand", () => { it("runs a command successfully", async () => { - const buildCommand = createBuildCommand(["echo foo"], false, [""], logger); + const buildCommand = createBuildCommand( + ["echo foo"], + false, + [""], + muteLogger + ); const buildResult = await buildCommand(); @@ -20,7 +25,7 @@ describe("createBuildCommand", () => { }); it("resolves if no command can be found", async () => { - const buildCommand = createBuildCommand([""], false, [""], logger); + const buildCommand = createBuildCommand([""], false, [""], muteLogger); await expect(buildCommand()).rejects.toThrow("Command not provided"); }); @@ -65,7 +70,7 @@ describe("createBuildCommand", () => { ["echo foo"], true, [path.join(fixtureLocation, "lib/**").replace(/\\/g, "/")], - logger + muteLogger ); const index_js_ExistsBeforeBuild = await fs.pathExists( diff --git a/packages/backfill/src/__tests__/helper.ts b/packages/backfill/src/__tests__/helper.ts index ce56fd62..08542cf6 100644 --- a/packages/backfill/src/__tests__/helper.ts +++ b/packages/backfill/src/__tests__/helper.ts @@ -1,7 +1,7 @@ import path from "path"; import findUp from "find-up"; -export async function findPathToBackfill() { +export async function findPathToBackfill(): Promise { const commandPath = await findUp(path.join("bin", "backfill.js"), { cwd: __dirname, }); diff --git a/packages/backfill/src/api.ts b/packages/backfill/src/api.ts index 62819e4c..b5566e0d 100644 --- a/packages/backfill/src/api.ts +++ b/packages/backfill/src/api.ts @@ -86,8 +86,7 @@ export async function fetch( cwd, incrementalCaching ); - const fetch = await cacheStorage.fetch(hash); - return fetch; + return await cacheStorage.fetch(hash); } export async function put( diff --git a/packages/backfill/src/audit.ts b/packages/backfill/src/audit.ts index 9654679d..f3745ce1 100644 --- a/packages/backfill/src/audit.ts +++ b/packages/backfill/src/audit.ts @@ -37,7 +37,7 @@ export function initializeWatcher( logFolder: string, outputGlob: string[], logger: Logger -) { +): void { // Trying to find the git root and using it as an approximation of code boundary const repositoryRoot = getGitRepositoryRoot(packageRoot); @@ -95,7 +95,7 @@ async function delay(time: number) { }); } -export async function closeWatcher(logger: Logger) { +export async function closeWatcher(logger: Logger): Promise { // Wait for one second before closing, giving time for file changes to propagate await delay(1000); diff --git a/packages/cache/package.json b/packages/cache/package.json index b305db29..0637c575 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -10,8 +10,7 @@ }, "main": "lib/index.js", "scripts": { - "build": "yarn compile", - "compile": "tsc -b", + "build": "tsc -b --pretty", "test": "jest", "watch": "tsc -b -w" }, @@ -26,7 +25,7 @@ "tar-fs": "^2.1.0" }, "devDependencies": { - "@types/fs-extra": "^9.0.13", + "@types/fs-extra": "^8.0.0", "@types/jest": "^30.0.0", "@types/node": "^14.18.36", "@types/tar-fs": "^2.0.1", diff --git a/packages/cache/src/NpmCacheStorage.ts b/packages/cache/src/NpmCacheStorage.ts index 34f8c65e..0c3b2cd7 100644 --- a/packages/cache/src/NpmCacheStorage.ts +++ b/packages/cache/src/NpmCacheStorage.ts @@ -86,7 +86,7 @@ export class NpmCacheStorage extends CacheStorage { return true; } - protected async _put(hash: string, filesToCache: string[]) { + protected async _put(hash: string, filesToCache: string[]): Promise { const { npmPackageName, registryUrl, npmrcUserconfig } = this.options; const temporaryNpmOutputFolder = path.resolve( diff --git a/packages/cache/src/__tests__/LocalCacheStorage.test.ts b/packages/cache/src/__tests__/LocalCacheStorage.test.ts index 1e27042c..4c602fd0 100644 --- a/packages/cache/src/__tests__/LocalCacheStorage.test.ts +++ b/packages/cache/src/__tests__/LocalCacheStorage.test.ts @@ -5,7 +5,7 @@ import { makeLogger } from "backfill-logger"; import { setupFixture } from "backfill-utils-test"; import { CacheStorageConfig } from "backfill-config"; -import { getCacheStorageProvider } from "../index"; +import { getCacheStorageProvider } from "../getCacheStorageProvider"; const setupCacheStorage = async (fixtureName: string) => { const fixtureLocation = await setupFixture(fixtureName); diff --git a/packages/cache/src/__tests__/getCacheStorageProvider.test.ts b/packages/cache/src/__tests__/getCacheStorageProvider.test.ts index 3d475794..56ca14dd 100644 --- a/packages/cache/src/__tests__/getCacheStorageProvider.test.ts +++ b/packages/cache/src/__tests__/getCacheStorageProvider.test.ts @@ -1,5 +1,6 @@ import { Logger, makeLogger } from "backfill-logger"; -import { getCacheStorageProvider, ICacheStorage } from ".."; +import { getCacheStorageProvider } from "../getCacheStorageProvider"; +import type { ICacheStorage } from "../CacheStorage"; import { AzureBlobCacheStorage } from "../AzureBlobCacheStorage"; import { LocalCacheStorage } from "../LocalCacheStorage"; diff --git a/packages/cache/src/getCacheStorageProvider.ts b/packages/cache/src/getCacheStorageProvider.ts new file mode 100644 index 00000000..c9d6fd87 --- /dev/null +++ b/packages/cache/src/getCacheStorageProvider.ts @@ -0,0 +1,74 @@ +import { CacheStorageConfig, CustomStorageConfig } from "backfill-config"; +import { Logger } from "backfill-logger"; + +import { ICacheStorage } from "./CacheStorage"; +import { AzureBlobCacheStorage } from "./AzureBlobCacheStorage"; +import { LocalCacheStorage } from "./LocalCacheStorage"; +import { NpmCacheStorage } from "./NpmCacheStorage"; +import { LocalSkipCacheStorage } from "./LocalSkipCacheStorage"; + +export function isCustomProvider( + config: CacheStorageConfig +): config is CustomStorageConfig { + return typeof config.provider === "function"; +} + +const memo = new Map(); + +export function getCacheStorageProvider( + cacheStorageConfig: CacheStorageConfig, + internalCacheFolder: string, + logger: Logger, + cwd: string, + incrementalCaching = false +): ICacheStorage { + let cacheStorage: ICacheStorage | undefined; + + if (isCustomProvider(cacheStorageConfig)) { + try { + return cacheStorageConfig.provider(logger, cwd); + } catch { + throw new Error("cacheStorageConfig.provider cannot be creaated"); + } + } + + const key = `${cacheStorageConfig.provider}${internalCacheFolder}${cwd}`; + cacheStorage = memo.get(key); + if (cacheStorage) { + return cacheStorage; + } + + if (cacheStorageConfig.provider === "npm") { + cacheStorage = new NpmCacheStorage( + cacheStorageConfig.options, + internalCacheFolder, + logger, + cwd, + incrementalCaching + ); + } else if (cacheStorageConfig.provider === "azure-blob") { + cacheStorage = new AzureBlobCacheStorage( + cacheStorageConfig.options, + logger, + cwd, + incrementalCaching + ); + } else if (cacheStorageConfig.provider === "local-skip") { + cacheStorage = new LocalSkipCacheStorage( + internalCacheFolder, + logger, + cwd, + incrementalCaching + ); + } else { + cacheStorage = new LocalCacheStorage( + internalCacheFolder, + logger, + cwd, + incrementalCaching + ); + } + memo.set(key, cacheStorage); + + return cacheStorage; +} diff --git a/packages/cache/src/index.ts b/packages/cache/src/index.ts index e199a6db..ea3bc118 100644 --- a/packages/cache/src/index.ts +++ b/packages/cache/src/index.ts @@ -1,75 +1,5 @@ -import { CacheStorageConfig, CustomStorageConfig } from "backfill-config"; -import { Logger } from "backfill-logger"; - -import { ICacheStorage } from "./CacheStorage"; -import { AzureBlobCacheStorage } from "./AzureBlobCacheStorage"; -import { LocalCacheStorage } from "./LocalCacheStorage"; -import { NpmCacheStorage } from "./NpmCacheStorage"; -import { LocalSkipCacheStorage } from "./LocalSkipCacheStorage"; -export { ICacheStorage, CacheStorage } from "./CacheStorage"; - -export function isCustomProvider( - config: CacheStorageConfig -): config is CustomStorageConfig { - return typeof config.provider === "function"; -} - -const memo = new Map(); - -export function getCacheStorageProvider( - cacheStorageConfig: CacheStorageConfig, - internalCacheFolder: string, - logger: Logger, - cwd: string, - incrementalCaching = false -): ICacheStorage { - let cacheStorage: ICacheStorage | undefined; - - if (isCustomProvider(cacheStorageConfig)) { - try { - return cacheStorageConfig.provider(logger, cwd); - } catch { - throw new Error("cacheStorageConfig.provider cannot be creaated"); - } - } - - const key = `${cacheStorageConfig.provider}${internalCacheFolder}${cwd}`; - cacheStorage = memo.get(key); - if (cacheStorage) { - return cacheStorage; - } - - if (cacheStorageConfig.provider === "npm") { - cacheStorage = new NpmCacheStorage( - cacheStorageConfig.options, - internalCacheFolder, - logger, - cwd, - incrementalCaching - ); - } else if (cacheStorageConfig.provider === "azure-blob") { - cacheStorage = new AzureBlobCacheStorage( - cacheStorageConfig.options, - logger, - cwd, - incrementalCaching - ); - } else if (cacheStorageConfig.provider === "local-skip") { - cacheStorage = new LocalSkipCacheStorage( - internalCacheFolder, - logger, - cwd, - incrementalCaching - ); - } else { - cacheStorage = new LocalCacheStorage( - internalCacheFolder, - logger, - cwd, - incrementalCaching - ); - } - memo.set(key, cacheStorage); - - return cacheStorage; -} +export { + getCacheStorageProvider, + isCustomProvider, +} from "./getCacheStorageProvider"; +export { type ICacheStorage, CacheStorage } from "./CacheStorage"; diff --git a/packages/config/package.json b/packages/config/package.json index e3689dc6..a675c309 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -10,8 +10,7 @@ }, "main": "lib/index.js", "scripts": { - "build": "yarn compile", - "compile": "tsc -b", + "build": "tsc -b --pretty", "test": "jest", "watch": "tsc -b -w" }, @@ -21,7 +20,6 @@ "pkg-dir": "^4.2.0" }, "devDependencies": { - "@types/fs-extra": "^9.0.13", "@types/jest": "^30.0.0", "backfill-utils-test": "*", "backfill-utils-tsconfig": "*", diff --git a/packages/config/src/createConfig.ts b/packages/config/src/createConfig.ts index eac3dd50..3b02fe5d 100644 --- a/packages/config/src/createConfig.ts +++ b/packages/config/src/createConfig.ts @@ -71,7 +71,7 @@ export function createDefaultConfig(fromPath: string): Config { /** * Get the package name from `/package.json`. */ -export function getName(packageRoot: string) { +export function getName(packageRoot: string): string { return ( require(path.join(packageRoot, "package.json")).name || path.basename(path.dirname(packageRoot)) @@ -82,7 +82,7 @@ export function getName(packageRoot: string) { * Get a list of `backfill.config.js` file paths, starting at `fromPath` and * searching upward. */ -export function getSearchPaths(fromPath: string) { +export function getSearchPaths(fromPath: string): string[] { const searchPaths: string[] = []; let nextPath: string | undefined = fromPath; diff --git a/packages/hasher/package.json b/packages/hasher/package.json index 8e85eb06..8365292b 100644 --- a/packages/hasher/package.json +++ b/packages/hasher/package.json @@ -10,20 +10,18 @@ }, "main": "lib/index.js", "scripts": { - "build": "yarn compile", - "compile": "tsc -b", + "build": "tsc -b --pretty", "test": "jest", "watch": "tsc -b -w" }, "dependencies": { "@rushstack/package-deps-hash": "^3.2.4", - "backfill-config": "^6.7.1", "backfill-logger": "^5.4.0", "fs-extra": "^8.1.0", "workspace-tools": "^0.40.0" }, "devDependencies": { - "@types/fs-extra": "^9.0.13", + "@types/fs-extra": "^8.0.0", "@types/jest": "^30.0.0", "backfill-utils-test": "*", "backfill-utils-tsconfig": "*", diff --git a/packages/hasher/src/Hasher.ts b/packages/hasher/src/Hasher.ts new file mode 100644 index 00000000..196ffe28 --- /dev/null +++ b/packages/hasher/src/Hasher.ts @@ -0,0 +1,109 @@ +import { Logger } from "backfill-logger"; +import { findWorkspacePath, WorkspaceInfo } from "workspace-tools"; +import { generateHashOfFiles } from "./hashOfFiles"; +import { + PackageHashInfo, + getPackageHash, + generateHashOfInternalPackages, +} from "./hashOfPackage"; +import { hashStrings, getPackageRoot } from "./helpers"; +import { RepoInfo, getRepoInfo, getRepoInfoNoCache } from "./repoInfo"; + +export interface IHasher { + createPackageHash: (salt: string) => Promise; + hashOfOutput: () => Promise; +} + +function isDone(done: PackageHashInfo[], packageName: string): boolean { + return Boolean(done.find(({ name }) => name === packageName)); +} + +function isInQueue(queue: string[], packagePath: string): boolean { + return queue.indexOf(packagePath) >= 0; +} + +export function addToQueue( + dependencyNames: string[], + queue: string[], + done: PackageHashInfo[], + workspaces: WorkspaceInfo +): void { + dependencyNames.forEach((name) => { + const dependencyPath = findWorkspacePath(workspaces, name); + + if (dependencyPath) { + if (!isDone(done, name) && !isInQueue(queue, dependencyPath)) { + queue.push(dependencyPath); + } + } + }); +} + +export class Hasher implements IHasher { + private packageRoot: string; + private repoInfo?: RepoInfo; + + constructor( + private options: { + packageRoot: string; + }, + private logger: Logger + ) { + this.packageRoot = this.options.packageRoot; + } + + public async createPackageHash(salt: string): Promise { + const tracer = this.logger.setTime("hashTime"); + + const packageRoot = await getPackageRoot(this.packageRoot); + + this.repoInfo = await getRepoInfo(packageRoot); + + const { workspaceInfo } = this.repoInfo; + + const queue = [packageRoot]; + const done: PackageHashInfo[] = []; + + while (queue.length > 0) { + const nextPackageRoot = queue.shift(); + + if (!nextPackageRoot) { + continue; + } + + const packageHash = await getPackageHash( + nextPackageRoot, + this.repoInfo, + this.logger + ); + + addToQueue(packageHash.internalDependencies, queue, done, workspaceInfo); + + done.push(packageHash); + } + + const internalPackagesHash = generateHashOfInternalPackages(done); + const buildCommandHash = hashStrings(salt); + const combinedHash = hashStrings([internalPackagesHash, buildCommandHash]); + + this.logger.verbose(`Hash of internal packages: ${internalPackagesHash}`); + this.logger.verbose(`Hash of build command: ${buildCommandHash}`); + this.logger.verbose(`Combined hash: ${combinedHash}`); + + tracer.stop(); + this.logger.setHash(combinedHash); + + return combinedHash; + } + + /** + * Hash of output will hash the output files. This is meant to be used by validation and will not cache the repo hashes. + * The validateOutput option should be used sparingly for performance reasons. It is meant to help be a debugging tool + * to help investigate integrity of the cache. + */ + public async hashOfOutput(): Promise { + const repoInfo = await getRepoInfoNoCache(this.packageRoot); + + return generateHashOfFiles(this.packageRoot, repoInfo); + } +} diff --git a/packages/hasher/src/__tests__/index.test.ts b/packages/hasher/src/__tests__/Hasher.test.ts similarity index 98% rename from packages/hasher/src/__tests__/index.test.ts rename to packages/hasher/src/__tests__/Hasher.test.ts index a42f5c11..7335cdbc 100644 --- a/packages/hasher/src/__tests__/index.test.ts +++ b/packages/hasher/src/__tests__/Hasher.test.ts @@ -5,7 +5,7 @@ import { makeLogger } from "backfill-logger"; import { WorkspaceInfo } from "workspace-tools"; import { PackageHashInfo } from "../hashOfPackage"; -import { Hasher, addToQueue } from "../index"; +import { Hasher, addToQueue } from "../Hasher"; const logger = makeLogger("mute"); diff --git a/packages/hasher/src/__tests__/hashOfFiles.test.ts b/packages/hasher/src/__tests__/hashOfFiles.test.ts index 5f85bc33..750db6f8 100644 --- a/packages/hasher/src/__tests__/hashOfFiles.test.ts +++ b/packages/hasher/src/__tests__/hashOfFiles.test.ts @@ -97,7 +97,7 @@ describe("generateHashOfFiles()", () => { fs.mkdirpSync(folder); fs.writeFileSync(path.join(folder, "foo.txt"), "bar"); - let repoInfo = await getRepoInfoNoCache(packageRoot); + const repoInfo = await getRepoInfoNoCache(packageRoot); const hashOfPackage = await generateHashOfFiles(packageRoot, repoInfo); @@ -114,7 +114,7 @@ describe("generateHashOfFiles()", () => { fs.mkdirpSync(folder); fs.writeFileSync(path.join(folder, "foo.txt"), "bar"); - let repoInfo = await getRepoInfoNoCache(workspaceRoot); + const repoInfo = await getRepoInfoNoCache(workspaceRoot); const hashOfPackage = await generateHashOfFiles(folder, repoInfo); @@ -127,7 +127,7 @@ describe("generateHashOfFiles()", () => { const folder = path.join(workspaceRoot, "packages", "package-a"); fs.writeFileSync(path.join(folder, "foo.txt"), "bar"); - let repoInfo = await getRepoInfoNoCache(workspaceRoot); + const repoInfo = await getRepoInfoNoCache(workspaceRoot); const hashOfPackage = await generateHashOfFiles(folder, repoInfo); diff --git a/packages/hasher/src/__tests__/resolveDependenciesHelper.ts b/packages/hasher/src/__tests__/resolveDependenciesHelper.ts index 361e8844..a947e38a 100644 --- a/packages/hasher/src/__tests__/resolveDependenciesHelper.ts +++ b/packages/hasher/src/__tests__/resolveDependenciesHelper.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { setupFixture } from "backfill-utils-test"; import { diff --git a/packages/hasher/src/createPackageHashes.ts b/packages/hasher/src/createPackageHashes.ts index 73c2df81..3fcec33a 100644 --- a/packages/hasher/src/createPackageHashes.ts +++ b/packages/hasher/src/createPackageHashes.ts @@ -5,7 +5,7 @@ export function createPackageHashes( root: string, workspaceInfo: WorkspaceInfo, repoHashes: { [key: string]: string } -) { +): Record { /** * This is a trie that looks like this: * { @@ -41,7 +41,7 @@ export function createPackageHashes( const pathParts = entry.split(/[\\/]/); let node = pathTree; - let packagePathParts = []; + const packagePathParts = []; for (const part of pathParts) { if (node[part]) { diff --git a/packages/hasher/src/hashOfFiles.ts b/packages/hasher/src/hashOfFiles.ts index 2828dee6..62adebfd 100644 --- a/packages/hasher/src/hashOfFiles.ts +++ b/packages/hasher/src/hashOfFiles.ts @@ -30,7 +30,6 @@ export async function generateHashOfFiles( for (const hash of packageHashes[packageRelativeRoot]) { hashes.push(hash[0], hash[1]); } - return hashStrings(hashes); } else { // Slow old path: if files are not clearly inside a package (mostly the case for malformed monorepos, like tests) const normalized = path.normalize(packageRoot) + sep; @@ -41,12 +40,10 @@ export async function generateHashOfFiles( files.sort((a, b) => a.localeCompare(b)); - const hashes: string[] = []; - for (const file of files) { hashes.push(file, repoHashes[file]); } - - return hashStrings(hashes); } + + return hashStrings(hashes); } diff --git a/packages/hasher/src/index.ts b/packages/hasher/src/index.ts index f35dbeb3..82f4b644 100644 --- a/packages/hasher/src/index.ts +++ b/packages/hasher/src/index.ts @@ -1,111 +1 @@ -import { Logger } from "backfill-logger"; -import { findWorkspacePath, WorkspaceInfo } from "workspace-tools"; -import { generateHashOfFiles } from "./hashOfFiles"; -import { - PackageHashInfo, - getPackageHash, - generateHashOfInternalPackages, -} from "./hashOfPackage"; -import { hashStrings, getPackageRoot } from "./helpers"; -import { RepoInfo, getRepoInfo, getRepoInfoNoCache } from "./repoInfo"; - -export interface IHasher { - createPackageHash: (salt: string) => Promise; - hashOfOutput: () => Promise; -} - -function isDone(done: PackageHashInfo[], packageName: string): boolean { - return Boolean(done.find(({ name }) => name === packageName)); -} - -function isInQueue(queue: string[], packagePath: string): boolean { - return queue.indexOf(packagePath) >= 0; -} - -export function addToQueue( - dependencyNames: string[], - queue: string[], - done: PackageHashInfo[], - workspaces: WorkspaceInfo -): void { - dependencyNames.forEach((name) => { - const dependencyPath = findWorkspacePath(workspaces, name); - - if (dependencyPath) { - if (!isDone(done, name) && !isInQueue(queue, dependencyPath)) { - queue.push(dependencyPath); - } - } - }); -} - -export class Hasher implements IHasher { - private packageRoot: string; - private repoInfo?: RepoInfo; - - constructor( - private options: { - packageRoot: string; - }, - private logger: Logger - ) { - this.packageRoot = this.options.packageRoot; - } - - public async createPackageHash(salt: string): Promise { - const tracer = this.logger.setTime("hashTime"); - - const packageRoot = await getPackageRoot(this.packageRoot); - - this.repoInfo = await getRepoInfo(packageRoot); - - const { workspaceInfo } = this.repoInfo; - - const queue = [packageRoot]; - const done: PackageHashInfo[] = []; - - while (queue.length > 0) { - const packageRoot = queue.shift(); - - if (!packageRoot) { - continue; - } - - const packageHash = await getPackageHash( - packageRoot, - this.repoInfo, - this.logger - ); - - addToQueue(packageHash.internalDependencies, queue, done, workspaceInfo); - - done.push(packageHash); - } - - const internalPackagesHash = generateHashOfInternalPackages(done); - const buildCommandHash = hashStrings(salt); - const combinedHash = hashStrings([internalPackagesHash, buildCommandHash]); - - this.logger.verbose(`Hash of internal packages: ${internalPackagesHash}`); - this.logger.verbose(`Hash of build command: ${buildCommandHash}`); - this.logger.verbose(`Combined hash: ${combinedHash}`); - - tracer.stop(); - this.logger.setHash(combinedHash); - - return combinedHash; - } - - /** - * Hash of output will hash the output files. This is meant to be used by validation and will not cache the repo hashes. - * The validateOutput option should be used sparingly for performance reasons. It is meant to help be a debugging tool - * to help investigate integrity of the cache. - */ - public async hashOfOutput(): Promise { - const repoInfo = await getRepoInfoNoCache(this.packageRoot); - - return generateHashOfFiles(this.packageRoot, repoInfo); - } -} - -export * from "./repoInfo"; +export { Hasher, type IHasher } from "./Hasher"; diff --git a/packages/hasher/src/repoInfo.ts b/packages/hasher/src/repoInfo.ts index 8b960a11..c163c53a 100644 --- a/packages/hasher/src/repoInfo.ts +++ b/packages/hasher/src/repoInfo.ts @@ -31,7 +31,7 @@ function searchRepoInfoCache(packageRoot: string) { } } -export async function getRepoInfoNoCache(cwd: string) { +export async function getRepoInfoNoCache(cwd: string): Promise { const root = getWorkspaceRoot(cwd); if (!root) { diff --git a/packages/hasher/tsconfig.json b/packages/hasher/tsconfig.json index 7d03daf9..3cd8cafb 100644 --- a/packages/hasher/tsconfig.json +++ b/packages/hasher/tsconfig.json @@ -5,9 +5,5 @@ "outDir": "lib" }, "include": ["src"], - "references": [ - { "path": "../config" }, - { "path": "../logger" }, - { "path": "../utils-test" } - ] + "references": [{ "path": "../logger" }, { "path": "../utils-test" }] } diff --git a/packages/logger/package.json b/packages/logger/package.json index a9418c71..c6edf522 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -10,8 +10,7 @@ }, "main": "lib/index.js", "scripts": { - "build": "yarn compile", - "compile": "tsc -b", + "build": "tsc -b --pretty", "watch": "tsc -b -w" }, "dependencies": { @@ -20,7 +19,7 @@ "fs-extra": "^8.1.0" }, "devDependencies": { - "@types/fs-extra": "^9.0.13", + "@types/fs-extra": "^8.0.0", "@types/node": "^14.18.36", "backfill-utils-tsconfig": "*", "typescript": "~4.7.0" diff --git a/packages/logger/src/consoleLogger.ts b/packages/logger/src/consoleLogger.ts index 496515bc..d3311cf7 100644 --- a/packages/logger/src/consoleLogger.ts +++ b/packages/logger/src/consoleLogger.ts @@ -22,9 +22,9 @@ export function makeConsoleLogger( trace(...args: string[]): void; consoleOverride: Console; } { - let consoleOverride = (overrides && overrides.console) || defaultConsole; - let formatter = defaultFormatter; - let logFilter = defaultLogFilter(logLevel); + const consoleOverride = (overrides && overrides.console) || defaultConsole; + const formatter = defaultFormatter; + const logFilter = defaultLogFilter(logLevel); return { consoleOverride, diff --git a/packages/logger/src/logger.ts b/packages/logger/src/logger.ts index 6ec0bbfd..1a7308f5 100644 --- a/packages/logger/src/logger.ts +++ b/packages/logger/src/logger.ts @@ -109,8 +109,8 @@ export function makeLogger( }; }, - setMode(mode: string, logLevel: "verbose" | "info") { - consoleLogger[logLevel](`Running in ${mode} mode.`); + setMode(mode: string, level: "verbose" | "info") { + consoleLogger[level](`Running in ${mode} mode.`); performanceReportData["mode"] = mode; }, diff --git a/packages/logger/src/timer.ts b/packages/logger/src/timer.ts index dd6c5ad6..1781079e 100644 --- a/packages/logger/src/timer.ts +++ b/packages/logger/src/timer.ts @@ -1,7 +1,7 @@ export type Timer = { start(): { stop(): number } }; -export const defaultTimer = { - start() { +export const defaultTimer: Timer = { + start(): { stop(): number } { const start = process.hrtime(); return { stop() { diff --git a/packages/utils-dotenv/package.json b/packages/utils-dotenv/package.json index 0759097c..822f2742 100644 --- a/packages/utils-dotenv/package.json +++ b/packages/utils-dotenv/package.json @@ -10,8 +10,7 @@ }, "main": "lib/index.js", "scripts": { - "build": "yarn compile", - "compile": "tsc -b", + "build": "tsc -b --pretty", "watch": "tsc -b -w" }, "dependencies": { diff --git a/packages/utils-dotenv/src/index.ts b/packages/utils-dotenv/src/index.ts index fbbcda76..16e4ac22 100644 --- a/packages/utils-dotenv/src/index.ts +++ b/packages/utils-dotenv/src/index.ts @@ -1,7 +1,7 @@ import { config } from "dotenv"; import findUp from "find-up"; -export function loadDotenv() { +export function loadDotenv(): void { if (process.env.NODE_ENV === "test") { return; } diff --git a/packages/utils-test/package.json b/packages/utils-test/package.json index 226d99be..e7123bf7 100644 --- a/packages/utils-test/package.json +++ b/packages/utils-test/package.json @@ -11,8 +11,7 @@ }, "main": "lib/index.js", "scripts": { - "build": "yarn compile", - "compile": "tsc -b", + "build": "tsc -b --pretty", "watch": "tsc -b -w" }, "dependencies": { @@ -21,7 +20,7 @@ "tempy": "^0.7.1" }, "devDependencies": { - "@types/fs-extra": "^9.0.13", + "@types/fs-extra": "^8.0.0", "@types/jest": "^30.0.0", "@types/node": "^14.18.36", "backfill-utils-tsconfig": "*", diff --git a/packages/utils-test/src/jestConfig.ts b/packages/utils-test/src/jestConfig.ts index f537244e..96e22540 100644 --- a/packages/utils-test/src/jestConfig.ts +++ b/packages/utils-test/src/jestConfig.ts @@ -6,8 +6,10 @@ export const jestConfig: Config = { "^.+\\.tsx?$": [ "ts-jest", { - // Badly-named option actually means disable type checking - isolatedModules: true, + tsconfig: { + // Badly-named option actually means disable type checking + isolatedModules: true, + }, }, ], }, diff --git a/packages/utils-test/src/setupFixture.ts b/packages/utils-test/src/setupFixture.ts index e3c34349..03fc58c0 100644 --- a/packages/utils-test/src/setupFixture.ts +++ b/packages/utils-test/src/setupFixture.ts @@ -5,7 +5,7 @@ import execa from "execa"; const fixturesDir = path.resolve(__dirname, "../__fixtures__"); -export function setupFixture(fixtureName: string) { +export function setupFixture(fixtureName: string): string { const fixturePath = path.join(fixturesDir, fixtureName); if (!fs.existsSync(fixturePath)) { @@ -30,7 +30,7 @@ export function setupFixture(fixtureName: string) { /** * Remove a temp directory, ignoring any errors. */ -export function removeTempDir(tempDir: string) { +export function removeTempDir(tempDir: string): void { try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch { diff --git a/renovate.json5 b/renovate.json5 index 5cc1cb01..698e6752 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -4,7 +4,6 @@ "github>microsoft/m365-renovate-config:beachball", "github>microsoft/m365-renovate-config:disableEsmVersions", "github>microsoft/m365-renovate-config:groupMore", - "github>microsoft/m365-renovate-config:groupTypes", "github>microsoft/m365-renovate-config:keepFresh", "github>microsoft/m365-renovate-config:restrictNode(14)" ], diff --git a/tools/debugTests.js b/tools/debugTests.js index 1c1a5508..9180740d 100644 --- a/tools/debugTests.js +++ b/tools/debugTests.js @@ -20,6 +20,7 @@ function start() { packagePath, "--runInBand", "--testTimeout=999999999", + "--watch", ...args, ]); } diff --git a/yarn.lock b/yarn.lock index 3dd3c12f..7702422d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -869,10 +869,10 @@ dependencies: "@babel/types" "^7.28.2" -"@types/fs-extra@^9.0.13": - version "9.0.13" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" - integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== +"@types/fs-extra@^8.0.0": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.5.tgz#33aae2962d3b3ec9219b5aca2555ee00274f5927" + integrity sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ== dependencies: "@types/node" "*" @@ -1436,6 +1436,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -1492,6 +1497,13 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" +brace-expansion@^5.0.2: + version "5.0.3" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.3.tgz#6a9c6c268f85b53959ec527aeafe0f7300258eef" + integrity sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA== + dependencies: + balanced-match "^4.0.2" + braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -2306,12 +2318,18 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-xml-builder@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz#a485d7e8381f1db983cf006f849d1066e2935241" + integrity sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ== + fast-xml-parser@^5.0.7: - version "5.3.2" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.3.2.tgz#78a51945fbf7312e1ff6726cb173f515b4ea11d8" - integrity sha512-n8v8b6p4Z1sMgqRmqLJm3awW4NX7NkaKPfb3uJIBTSH7Pdvufi3PQ3/lJLQrvxcMYl7JI2jnDO90siPEpD8JBA== + version "5.4.1" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz#0c81b8ecfb3021e5ad83aa3df904af19a05bc601" + integrity sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A== dependencies: - strnum "^2.1.0" + fast-xml-builder "^1.0.0" + strnum "^2.1.2" fastq@^1.6.0: version "1.19.1" @@ -3780,33 +3798,33 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@6.1.6: - version "6.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-6.1.6.tgz#5384bb324be5b5dae12a567c03d22908febd0ddd" - integrity sha512-6bR3UIeh/DF8+p6A9Spyuy67ShOq42rOkHWi7eUe3Ua99Zo5lZfGC6lJJWkeoK4k9jQFT3Pl7czhTXimG2XheA== +minimatch@6.1.6, minimatch@^6.0.0: + version "6.2.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-6.2.3.tgz#f5c78246aa2c546afa4c7e19294e41e5e9b8f023" + integrity sha512-5rvZbDy5y2k40rre/0OBbYnl03en25XPU3gOVO7532beGMjAipq88VdS9OeLOZNrD+Tb0lDhBJHZ7Gcd8qKlPg== dependencies: brace-expansion "^2.0.1" minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + version "3.1.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== dependencies: brace-expansion "^1.1.7" minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + version "5.1.9" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.9.tgz#1293ef15db0098b394540e8f9f744f9fda8dee4b" + integrity sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw== dependencies: brace-expansion "^2.0.1" minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + version "9.0.8" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.8.tgz#bb3aa36d7b42ea77a93c44d5c1082b188112497c" + integrity sha512-reYkDYtj/b19TeqbNZCV4q9t+Yxylf/rYBsLb42SXJatTv4/ylq5lEiAmhA/IToxO7NI2UzNMghHoHuaqDkAjw== dependencies: - brace-expansion "^2.0.1" + brace-expansion "^5.0.2" minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" @@ -4683,10 +4701,10 @@ strip-outer@^1.0.1: dependencies: escape-string-regexp "^1.0.2" -strnum@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/strnum/-/strnum-2.1.1.tgz#cf2a6e0cf903728b8b2c4b971b7e36b4e82d46ab" - integrity sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw== +strnum@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-2.1.2.tgz#a5e00ba66ab25f9cafa3726b567ce7a49170937a" + integrity sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ== supports-color@^7.1.0: version "7.2.0" @@ -5035,9 +5053,9 @@ v8-to-istanbul@^9.0.1: convert-source-map "^2.0.0" validator@^13.7.0: - version "13.15.23" - resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.23.tgz#59a874f84e4594588e3409ab1edbe64e96d0c62d" - integrity sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw== + version "13.15.26" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.26.tgz#36c3deeab30e97806a658728a155c66fcaa5b944" + integrity sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA== walker@^1.0.8: version "1.0.8"