Skip to content

Commit

Permalink
feat(cache): adds cache format version & checks against it (#891)
Browse files Browse the repository at this point in the history
## Description

- adds a cache format version attribute
- adds a check to skip using the cache when the version found in the
cache is too low
- makes the typescript typings in some of the cache related modules a
bit more readable (and valid).

## Motivation and Context

There are some changes in the dependency-cruiser output format that are
incompatible between version 15 and 16. For use without a cache (or
_known violations_, but see #890 for that) this presents no issues. The
cache can still contain the old format, though, which might result in
slightly unexpected output or disturbing looking errors. With this
feature dependency-cruiser will ignore the contents of the cache when
the format version of that cache is too low.

## How Has This Been Tested?

- [x] green ci
- [x] additional non-regression tests

## Types of changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Documentation only change
- [ ] Refactor (non-breaking change which fixes an issue without
changing functionality)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
  • Loading branch information
sverweij authored Dec 18, 2023
1 parent 875813e commit 7ddf2db
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 51 deletions.
43 changes: 35 additions & 8 deletions src/cache/cache.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ import { scannableExtensions } from "#extract/transpile/meta.mjs";
// @ts-expect-error ts(2307) - the ts compiler is not privy to the existence of #imports in package.json
import { bus } from "#utl/bus.mjs";

/**
* @typedef {import("../../types/dependency-cruiser.mjs").IRevisionData} IRevisionData
* @typedef {import("../../types/strict-options.mjs").IStrictCruiseOptions} IStrictCruiseOptions
* @typedef {import("../../types/dependency-cruiser.mjs").ICruiseResult} ICruiseResult
* @typedef {import("../../types/cache-options.mjs").cacheStrategyType} cacheStrategyType
*/

const CACHE_FILE_NAME = "cache.json";
const EMPTY_CACHE = {
modules: [],
Expand All @@ -27,10 +34,21 @@ const EMPTY_CACHE = {
optionsUsed: {},
},
};
// Bump this to the current major.minor version when the cache format changes in
// a way that's not backwards compatible.
// e.g. version 3.0.0 => 3
// version 3.1.0 => 3.1
// version 3.1.1 => 3.1
// version 3.11.0 => 3.11
// This means we assume breaking cache format versions won't occur
// in patch releases. If worst case scenario it _is_ necessary we could
// add the patch version divided by 1000_000 e.g.:
// version 3.14.16 => 3.14 + 16/1000_000 = 3.140016
const CACHE_FORMAT_VERSION = 16;

export default class Cache {
/**
* @param {import("../../types/cache-options.mjs").cacheStrategyType=} pCacheStrategy
* @param {cacheStrategyType=} pCacheStrategy
* @param {boolean=} pCompress
*/
constructor(pCacheStrategy, pCompress) {
Expand All @@ -42,10 +60,17 @@ export default class Cache {
this.compress = pCompress ?? false;
}

cacheFormatVersionCompatible(pCachedCruiseResult) {
return (
(pCachedCruiseResult?.revisionData?.cacheFormatVersion ?? 1) >=
CACHE_FORMAT_VERSION
);
}

/**
* @param {import("../../types/strict-options.mjs").IStrictCruiseOptions} pCruiseOptions
* @param {import("../../types/dependency-cruiser.mjs").ICruiseResult} pCachedCruiseResult
* @param {import("../../types/dependency-cruiser.mjs").IRevisionData=} pRevisionData
* @param {IStrictCruiseOptions} pCruiseOptions
* @param {ICruiseResult} pCachedCruiseResult
* @param {IRevisionData=} pRevisionData
* @returns {Promise<boolean>}
*/
async canServeFromCache(pCruiseOptions, pCachedCruiseResult, pRevisionData) {
Expand All @@ -61,8 +86,10 @@ export default class Cache {
),
},
));
this.revisionData.cacheFormatVersion = CACHE_FORMAT_VERSION;
bus.debug("cache: - comparing");
return (
this.cacheFormatVersionCompatible(pCachedCruiseResult) &&
this.cacheStrategy.revisionDataEqual(
pCachedCruiseResult.revisionData,
this.revisionData,
Expand All @@ -76,7 +103,7 @@ export default class Cache {

/**
* @param {string} pCacheFolder
* @returns {Promise<import("../../types/dependency-cruiser.mjs").ICruiseResult>}
* @returns {Promise<ICruiseResult>}
*/
async read(pCacheFolder) {
try {
Expand Down Expand Up @@ -131,11 +158,11 @@ export default class Cache {

/**
* @param {string} pCacheFolder
* @param {import("../../types/dependency-cruiser.mjs").ICruiseResult} pCruiseResult
* @param {import("../../types/dependency-cruiser.mjs").IRevisionData=} pRevisionData
* @param {ICruiseResult} pCruiseResult
* @param {IRevisionData=} pRevisionData
*/
async write(pCacheFolder, pCruiseResult, pRevisionData) {
const lRevisionData = pRevisionData ?? this.revisionData;
let lRevisionData = pRevisionData ?? this.revisionData;

await mkdir(pCacheFolder, { recursive: true });
const lUncompressedPayload = JSON.stringify(
Expand Down
51 changes: 29 additions & 22 deletions src/cache/content-strategy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@ import {
moduleIsInterestingForDiff,
} from "./helpers.mjs";

/**
* @typedef {import("../../types/dependency-cruiser.mjs").IModule} IModule
* @typedef {import("../../types/dependency-cruiser.mjs").IRevisionChange} IRevisionChange
* @typedef {import("../../types/dependency-cruiser.mjs").IRevisionData} IRevisionData
* @typedef {import("../../types/dependency-cruiser.mjs").ICruiseResult} ICruiseResult
* @typedef {import("../../types/strict-options.mjs").IStrictCruiseOptions} IStrictCruiseOptions
*/

/**
* @param {string} pBaseDirectory
* @returns {(pModule: import("../../types/dependency-cruiser.js").IModule) => import("../../types/dependency-cruiser.js").IModule}
* @returns {(IModule) => IModule}
*/
function addCheckSumToModule(pBaseDirectory) {
return (pModule) => {
Expand All @@ -26,9 +34,9 @@ function addCheckSumToModule(pBaseDirectory) {
}

/**
* @param {import("../../types/dependency-cruiser.js").IRevisionChange[]} pChanges
* @param {import("../../types/dependency-cruiser.js").IModule[]} pModules
* @returns {import("../../types/dependency-cruiser.js").IRevisionChange[]}
* @param {IRevisionChange[]} pChanges
* @param {IModule[]} pModules
* @returns {IRevisionChange[]}
*/
function refreshChanges(pChanges, pModules) {
return pChanges.filter(
Expand All @@ -44,15 +52,15 @@ function refreshChanges(pChanges, pModules) {
export default class ContentStrategy {
/**
* @param {string} pDirectory
* @param {import("../../types/dependency-cruiser.js").ICruiseResult} pCachedCruiseResult
* @param {import("../../types/strict-options.js").IStrictCruiseOptions} pCruiseOptions
* @param {ICruiseResult} pCachedCruiseResult
* @param {IStrictCruiseOptions} pCruiseOptions
* @param {Object} pOptions
* @param {Set<string>} pOptions.extensions
* @param {Set<import("watskeburt").changeTypeType>=} pOptions.interestingChangeTypes?
* @param {string=} pOptions.baseDir
* @param {typeof findContentChanges=} pOptions.diffListFn
* @param {typeof import('watskeburt').getSHA=} pOptions.checksumFn
* @returns {import("../../types/dependency-cruiser.js").IRevisionData}
* @returns {IRevisionData}
*/
getRevisionData(pDirectory, pCachedCruiseResult, pCruiseOptions, pOptions) {
const lOptions = {
Expand All @@ -62,21 +70,20 @@ export default class ContentStrategy {
};
return {
SHA1: "unknown-in-content-cache-strategy",
changes:
/** @type {import("../../types/dependency-cruiser.js").IRevisionChange[]} */ (
lOptions.diffListFn(pDirectory, pCachedCruiseResult, {
baseDir: lOptions.baseDir,
extensions: lOptions.extensions,
includeOnly: pCruiseOptions.includeOnly,
exclude: pCruiseOptions.exclude,
})
).filter(isInterestingChangeType(lOptions.interestingChangeTypes)),
changes: /** @type {IRevisionChange[]} */ (
lOptions.diffListFn(pDirectory, pCachedCruiseResult, {
baseDir: lOptions.baseDir,
extensions: lOptions.extensions,
includeOnly: pCruiseOptions.includeOnly,
exclude: pCruiseOptions.exclude,
})
).filter(isInterestingChangeType(lOptions.interestingChangeTypes)),
};
}

/**
* @param {import("../../types/dependency-cruiser.js").IRevisionData=} pExistingRevisionData
* @param {import("../../types/dependency-cruiser.js").IRevisionData=} pNewRevisionData
* @param {IRevisionData=} pExistingRevisionData
* @param {IRevisionData=} pNewRevisionData
* @returns {boolean}
*/
revisionDataEqual(pExistingRevisionData, pNewRevisionData) {
Expand All @@ -95,13 +102,13 @@ export default class ContentStrategy {
}

/**
* @param {import("../../types/dependency-cruiser.js").ICruiseResult} pCruiseResult
* @param {import("../../types/dependency-cruiser.js").IRevisionData=} pRevisionData
* @returns {import("../../types/dependency-cruiser.js").ICruiseResult}
* @param {ICruiseResult} pCruiseResult
* @param {IRevisionData=} pRevisionData
* @returns {ICruiseResult}
*/
prepareRevisionDataForSaving(pCruiseResult, pRevisionData) {
const lModulesWithCheckSum = pCruiseResult.modules.map(
addCheckSumToModule(pCruiseResult.summary.optionsUsed.baseDir),
addCheckSumToModule(pCruiseResult.summary.optionsUsed.baseDir || "."),
);
const lRevisionData = {
...pRevisionData,
Expand Down
4 changes: 2 additions & 2 deletions src/cache/find-content-changes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ function diffCachedModuleAgainstFileSet(
* @param {Object} pOptions
* @param {Set<string>} pOptions.extensions
* @param {string} pOptions.baseDir
* @param {import("../../types/strict-filter-types").IStrictExcludeType} pOptions.exclude
* @param {import("../../types/strict-filter-types").IStrictIncludeOnlyType=} pOptions.includeOnly
* @param {import("../../types/strict-filter-types.mjs").IStrictExcludeType} pOptions.exclude
* @param {import("../../types/strict-filter-types.mjs").IStrictIncludeOnlyType=} pOptions.includeOnly
* @returns {import("../..").IRevisionChange[]}
*/
export default function findContentChanges(
Expand Down
17 changes: 12 additions & 5 deletions src/cache/helpers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import memoize from "lodash/memoize.js";
// @ts-expect-error ts(2307) - the ts compiler is not privy to the existence of #imports in package.json
import { filenameMatchesPattern } from "#graph-utl/match-facade.mjs";

/**
* @typedef {import("../..").IModule} IModule
* @typedef {import("../../types/dependency-cruiser.mjs").IRevisionChange} IRevisionChange
* @typedef {import("../../types/filter-types.mjs").IExcludeType} IExcludeType
* @typedef {import("../../types/strict-filter-types.mjs").IStrictIncludeOnlyType} IStrictIncludeOnlyType
*/

/**
* @param {string} pString
* @returns {string}
Expand All @@ -30,7 +37,7 @@ export const getFileHashSync = memoize(_getFileHashSync);

/**
* @param {import("watskeburt").IChange} pChange
* @param {import("../../types/dependency-cruiser.js").IRevisionChange}
* @return {IRevisionChange}
*/
export function addCheckSumToChangeSync(pChange) {
return {
Expand All @@ -40,8 +47,7 @@ export function addCheckSumToChangeSync(pChange) {
}

/**
*
* @param {import("../../types/filter-types.js").IExcludeType} pExcludeOption
* @param {IExcludeType} pExcludeOption
* @returns {(pFileName: string) => boolean}
*/
export function excludeFilter(pExcludeOption) {
Expand All @@ -54,7 +60,7 @@ export function excludeFilter(pExcludeOption) {
}

/**
* @param {import("../../types/strict-filter-types.js").IStrictIncludeOnlyType=} pIncludeOnlyFilter
* @param {IStrictIncludeOnlyType=} pIncludeOnlyFilter
* @returns {(pFileName: string) => boolean}
*/
export function includeOnlyFilter(pIncludeOnlyFilter) {
Expand Down Expand Up @@ -112,7 +118,8 @@ export function isInterestingChangeType(pInterestingChangeTypes) {
}

/**
* @param {import("../..").IModule} pModule
* @param {IModule} pModule
* @returns {boolean}
*/
export function moduleIsInterestingForDiff(pModule) {
return (
Expand Down
20 changes: 14 additions & 6 deletions src/cache/metadata-strategy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ import {
// @ts-expect-error ts(2307) - the ts compiler is not privy to the existence of #imports in package.json
import { bus } from "#utl/bus.mjs";

/**
* @typedef {import("../../types/dependency-cruiser.mjs").IModule} IModule
* @typedef {import("../../types/dependency-cruiser.mjs").IRevisionChange} IRevisionChange
* @typedef {import("../../types/dependency-cruiser.mjs").IRevisionData} IRevisionData
* @typedef {import("../../types/dependency-cruiser.mjs").ICruiseResult} ICruiseResult
* @typedef {import("../../types/strict-options.mjs").IStrictCruiseOptions} IStrictCruiseOptions
*/

export default class MetaDataStrategy {
/**
* @param {string} _pDirectory
Expand All @@ -22,7 +30,7 @@ export default class MetaDataStrategy {
* @param {typeof getSHA=} pOptions.shaRetrievalFn
* @param {typeof list=} pOptions.diffListFn
* @param {typeof addCheckSumToChangeSync=} pOptions.checksumFn
* @returns {Promise<import("../../types/dependency-cruiser.js").IRevisionData>}
* @returns {Promise<IRevisionData>}
*/
async getRevisionData(
_pDirectory,
Expand Down Expand Up @@ -63,8 +71,8 @@ export default class MetaDataStrategy {
}

/**
* @param {import("../../types/dependency-cruiser.js").IRevisionData=} pExistingRevisionData
* @param {import("../../types/dependency-cruiser.js").IRevisionData=} pNewRevisionData
* @param {IRevisionData=} pExistingRevisionData
* @param {IRevisionData=} pNewRevisionData
* @returns {boolean}
*/
revisionDataEqual(pExistingRevisionData, pNewRevisionData) {
Expand All @@ -81,9 +89,9 @@ export default class MetaDataStrategy {
}

/**
* @param {import("../../types/dependency-cruiser.js").ICruiseResult} pCruiseResult
* @param {import("../../types/dependency-cruiser.js").IRevisionData=} pRevisionData
* @returns {import("../../types/dependency-cruiser.js").ICruiseResult}
* @param {ICruiseResult} pCruiseResult
* @param {IRevisionData=} pRevisionData
* @returns {ICruiseResult}
*/
prepareRevisionDataForSaving(pCruiseResult, pRevisionData) {
return pRevisionData
Expand Down
18 changes: 11 additions & 7 deletions src/cache/options-compatible.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// @ts-check
import { isDeepStrictEqual } from "node:util";

/**
* @typedef {import("../../types/strict-options.mjs").IStrictCruiseOptions} IStrictCruiseOptions
*/

/*
# command line options
## No influence on cache
Expand Down Expand Up @@ -88,8 +92,8 @@ export function cacheOptionIsCompatible(pExistingCacheOption, pNewCacheOption) {

/**
*
* @param {import("../../types/strict-options").IStrictCruiseOptions} pOldOptions
* @param {import("../../types/strict-options").IStrictCruiseOptions} pNewOptions
* @param {IStrictCruiseOptions} pOldOptions
* @param {IStrictCruiseOptions} pNewOptions
* @returns {boolean}
*/
// eslint-disable-next-line complexity
Expand All @@ -105,11 +109,11 @@ export function optionsAreCompatible(pOldOptions, pNewOptions) {
includeOnlyIsCompatible(pOldOptions.includeOnly, pNewOptions.includeOnly) &&
filterOptionIsCompatible(
pOldOptions.doNotFollow,
pNewOptions.doNotFollow
pNewOptions.doNotFollow,
) &&
filterOptionIsCompatible(
pOldOptions.moduleSystems,
pNewOptions.moduleSystems
pNewOptions.moduleSystems,
) &&
filterOptionIsCompatible(pOldOptions.exclude, pNewOptions.exclude) &&
filterOptionIsCompatible(pOldOptions.focus, pNewOptions.focus) &&
Expand All @@ -119,15 +123,15 @@ export function optionsAreCompatible(pOldOptions, pNewOptions) {
limitIsCompatible(pOldOptions.maxDepth, pNewOptions.maxDepth) &&
optionIsCompatible(
pOldOptions.knownViolations || [],
pNewOptions.knownViolations || []
pNewOptions.knownViolations || [],
) &&
optionIsCompatible(
pOldOptions.enhancedResolveOptions,
pNewOptions.enhancedResolveOptions
pNewOptions.enhancedResolveOptions,
) &&
optionIsCompatible(
pOldOptions.exoticRequireStrings,
pNewOptions.exoticRequireStrings
pNewOptions.exoticRequireStrings,
) &&
cacheOptionIsCompatible(pOldOptions.cache, pNewOptions.cache)
);
Expand Down
4 changes: 4 additions & 0 deletions src/schema/cruise-result.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/schema/cruise-result.schema.mjs

Large diffs are not rendered by default.

Loading

0 comments on commit 7ddf2db

Please sign in to comment.