add test case and update usage
Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com>
This commit is contained in:
@@ -16,22 +16,163 @@
|
||||
|
||||
import { Lockfile } from '@backstage/cli-node';
|
||||
import { PackageDocsCache } from './Cache';
|
||||
import { join as joinPath } from 'path';
|
||||
import {
|
||||
createMockDirectory,
|
||||
MockDirectory,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { join as joinPath } from 'path';
|
||||
|
||||
jest.mock('crypto', () => {
|
||||
const hash = {
|
||||
update: jest.fn(),
|
||||
digest: jest.fn().mockReturnValue('test'),
|
||||
};
|
||||
return {
|
||||
createHash: jest.fn().mockReturnValue(hash),
|
||||
};
|
||||
});
|
||||
|
||||
describe('PackageDocsCache', () => {
|
||||
let testDir: MockDirectory;
|
||||
beforeEach(async () => {
|
||||
beforeAll(async () => {
|
||||
testDir = createMockDirectory();
|
||||
});
|
||||
afterEach(async () => {
|
||||
testDir.clear();
|
||||
});
|
||||
it('should be able to parse cache files', async () => {
|
||||
const cache = await PackageDocsCache.loadAsync(
|
||||
joinPath(__dirname, 'test-cache'),
|
||||
new Lockfile(),
|
||||
testDir.addContent({
|
||||
'.cache': {
|
||||
'package-docs': {
|
||||
test: {
|
||||
'cache.json': JSON.stringify({
|
||||
hash: 'test',
|
||||
packageName: 'test',
|
||||
restoreTo: 'test',
|
||||
version: '1',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
test: {
|
||||
'package.json': JSON.stringify({
|
||||
name: '@test/test',
|
||||
}),
|
||||
},
|
||||
});
|
||||
const lockfile = {
|
||||
getDependencyTreeHash: () => 'test',
|
||||
} as any as Lockfile;
|
||||
const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile);
|
||||
expect(await cache.has('test')).toBe(true);
|
||||
});
|
||||
|
||||
it('should be able to restore cache', async () => {
|
||||
testDir.addContent({
|
||||
'.cache': {
|
||||
'package-docs': {
|
||||
test: {
|
||||
'cache.json': JSON.stringify({
|
||||
hash: 'test',
|
||||
packageName: 'test',
|
||||
restoreTo: 'test',
|
||||
version: '1',
|
||||
}),
|
||||
contents: {
|
||||
'src/index.ts': 'export const test = "test";',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
test: {
|
||||
'package.json': JSON.stringify({
|
||||
name: '@test/test',
|
||||
}),
|
||||
},
|
||||
});
|
||||
const lockfile = {
|
||||
getDependencyTreeHash: () => 'test',
|
||||
} as any as Lockfile;
|
||||
const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile);
|
||||
await cache.restore('test');
|
||||
expect(
|
||||
await readFile(joinPath(testDir.path, 'test', 'src/index.ts'), 'utf-8'),
|
||||
).toBe('export const test = "test";');
|
||||
});
|
||||
|
||||
it('should be able to write cache', async () => {
|
||||
testDir.addContent({
|
||||
'.cache': {},
|
||||
test: {
|
||||
'package.json': JSON.stringify({
|
||||
name: '@test/test',
|
||||
}),
|
||||
'src/index.ts': 'export const test = "test";',
|
||||
},
|
||||
});
|
||||
const lockfile = {
|
||||
getDependencyTreeHash: () => 'test',
|
||||
} as any as Lockfile;
|
||||
const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile);
|
||||
await cache.write('test', joinPath(testDir.path, 'test'));
|
||||
expect(
|
||||
await readFile(
|
||||
joinPath(testDir.path, '.cache', 'package-docs', 'test', 'cache.json'),
|
||||
'utf-8',
|
||||
),
|
||||
).toBe(
|
||||
JSON.stringify({
|
||||
hash: 'test',
|
||||
packageName: '@test/test',
|
||||
restoreTo: 'test',
|
||||
version: '1',
|
||||
}),
|
||||
);
|
||||
cache.add('test', 'test');
|
||||
expect(
|
||||
await readFile(
|
||||
joinPath(
|
||||
testDir.path,
|
||||
'.cache',
|
||||
'package-docs',
|
||||
'test',
|
||||
'contents',
|
||||
'src/index.ts',
|
||||
),
|
||||
'utf-8',
|
||||
),
|
||||
).toBe('export const test = "test";');
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
content: JSON.stringify({
|
||||
hash: 'test',
|
||||
packageName: 'test',
|
||||
restoreTo: 'test',
|
||||
version: '2',
|
||||
}),
|
||||
},
|
||||
{
|
||||
content: JSON.stringify({
|
||||
hash: 'test',
|
||||
packageName: 1,
|
||||
}),
|
||||
},
|
||||
])('should skip invalid cache files', async content => {
|
||||
testDir.addContent({
|
||||
'.cache': {},
|
||||
test: {
|
||||
'package.json': JSON.stringify({
|
||||
name: '@test/test',
|
||||
}),
|
||||
},
|
||||
'cache.json': content,
|
||||
});
|
||||
const lockfile = {
|
||||
getDependencyTreeHash: () => 'test',
|
||||
} as any as Lockfile;
|
||||
const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile);
|
||||
expect(await cache.has('test')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,12 +18,11 @@ import globby from 'globby';
|
||||
import { dirname, join as joinPath, relative } from 'path';
|
||||
import crypto from 'crypto';
|
||||
import { Lockfile } from '@backstage/cli-node';
|
||||
import { paths as cliPaths } from '../../lib/paths';
|
||||
import { mkdirp } from 'fs-extra';
|
||||
import { z } from 'zod';
|
||||
import { CACHE_DIR, CACHE_FILE } from './constants';
|
||||
|
||||
const version = '1';
|
||||
const CACHE_FILE = 'cache.json';
|
||||
|
||||
interface CacheEntry {
|
||||
hash: string;
|
||||
@@ -46,11 +45,13 @@ export class PackageDocsCache {
|
||||
private readonly lockfile: Lockfile,
|
||||
// A map of package directory to cache entry.
|
||||
private readonly cache: Map<string, CacheEntry>,
|
||||
private readonly cacheDir: string,
|
||||
private readonly baseDirectory: string,
|
||||
) {
|
||||
this.keyCache = new Map();
|
||||
}
|
||||
static async loadAsync(cacheDir: string, lockfile: Lockfile) {
|
||||
static async loadAsync(baseDirectory: string, lockfile: Lockfile) {
|
||||
const cacheDir = joinPath(baseDirectory, CACHE_DIR);
|
||||
await mkdirp(cacheDir);
|
||||
const cacheFiles = await globby(`**/${CACHE_FILE}`, {
|
||||
cwd: cacheDir,
|
||||
});
|
||||
@@ -61,23 +62,29 @@ export class PackageDocsCache {
|
||||
try {
|
||||
const cacheJson = JSON.parse(cache);
|
||||
const parsed = cacheEntrySchema.parse(cacheJson);
|
||||
if (parsed.version !== version) {
|
||||
console.warn(
|
||||
`Skipping cache file ${file} due to version mismatch: ${parsed.version} !== ${version}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
map.set(pkg, parsed);
|
||||
} catch (e) {
|
||||
console.error(`Skipping unparseable cache file ${file}: ${e}`);
|
||||
}
|
||||
}
|
||||
return new PackageDocsCache(lockfile, map, cacheDir);
|
||||
return new PackageDocsCache(lockfile, map, baseDirectory);
|
||||
}
|
||||
|
||||
async directoryToName(directory: string) {
|
||||
const packageJson = await readFile(
|
||||
joinPath(directory, 'package.json'),
|
||||
joinPath(this.baseDirectory, directory, 'package.json'),
|
||||
'utf-8',
|
||||
);
|
||||
return JSON.parse(packageJson).name;
|
||||
}
|
||||
|
||||
async toKey(pkg: string) {
|
||||
private async toKey(pkg: string) {
|
||||
if (this.keyCache.has(pkg)) {
|
||||
return this.keyCache.get(pkg)!;
|
||||
}
|
||||
@@ -93,7 +100,7 @@ export class PackageDocsCache {
|
||||
hash.update('\0');
|
||||
|
||||
for (const path of result.sort()) {
|
||||
const absPath = cliPaths.resolveTargetRoot(pkg, path);
|
||||
const absPath = joinPath(this.baseDirectory, pkg, path);
|
||||
const pathInPackage = joinPath(absPath, path);
|
||||
hash.update(pathInPackage);
|
||||
hash.update('\0');
|
||||
@@ -122,16 +129,16 @@ export class PackageDocsCache {
|
||||
}
|
||||
const cacheEntry = this.cache.get(pkg);
|
||||
const restoreTo = cacheEntry!.restoreTo;
|
||||
const cacheDir = joinPath(this.cacheDir, pkg);
|
||||
const cacheDir = joinPath(this.baseDirectory, CACHE_DIR, pkg);
|
||||
const contentsDir = joinPath(cacheDir, 'contents');
|
||||
|
||||
const targetDir = cliPaths.resolveTargetRoot(restoreTo);
|
||||
const targetDir = joinPath(this.baseDirectory, restoreTo);
|
||||
await mkdirp(targetDir);
|
||||
await cp(contentsDir, targetDir, { recursive: true });
|
||||
}
|
||||
|
||||
async write(pkg: string, contentDirectory: string) {
|
||||
const cacheDir = joinPath(this.cacheDir, pkg);
|
||||
const cacheDir = joinPath(this.baseDirectory, CACHE_DIR, pkg);
|
||||
const contentsDir = joinPath(cacheDir, 'contents');
|
||||
await mkdirp(contentsDir);
|
||||
const hashString = await this.toKey(pkg);
|
||||
@@ -139,7 +146,7 @@ export class PackageDocsCache {
|
||||
const cacheEntry: CacheEntry = {
|
||||
hash: hashString,
|
||||
packageName: await this.directoryToName(pkg),
|
||||
restoreTo: relative(cliPaths.resolveTargetRoot(), contentDirectory),
|
||||
restoreTo: relative(this.baseDirectory, contentDirectory),
|
||||
version,
|
||||
};
|
||||
await writeFile(joinPath(cacheDir, CACHE_FILE), JSON.stringify(cacheEntry));
|
||||
|
||||
@@ -58,8 +58,6 @@ const HIGHLIGHT_LANGUAGES = [
|
||||
'json',
|
||||
];
|
||||
|
||||
const CACHE_DIR = '.cache/package-docs';
|
||||
|
||||
function getExports(packageJson: any) {
|
||||
if (packageJson.exports) {
|
||||
return Object.values(packageJson.exports).filter(
|
||||
@@ -120,9 +118,8 @@ export default async function packageDocs(paths: string[] = [], opts: any) {
|
||||
exclude: opts.exclude,
|
||||
});
|
||||
|
||||
await mkdirp(CACHE_DIR);
|
||||
const cache = await PackageDocsCache.loadAsync(
|
||||
CACHE_DIR,
|
||||
cliPaths.resolveTargetRoot(),
|
||||
await Lockfile.load(cliPaths.resolveTargetRoot('yarn.lock')),
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const CACHE_FILE = 'cache.json';
|
||||
export const CACHE_DIR = '.cache/package-docs';
|
||||
Reference in New Issue
Block a user