Skip to content

Commit

Permalink
feat: Add updatedAt field in metadata (#628)
Browse files Browse the repository at this point in the history
  • Loading branch information
Feverqwe authored Jan 9, 2024
1 parent 0cd0a5d commit 63f6f5e
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type NestedContributorsForPathFunction = (
) => void;
export type UserByLoginFunction = (login: string) => Promise<Contributor | null>;
export type CollectionOfPluginsFunction = (output: string, options: PluginOptions) => string;
export type GetModifiedTimeByPathFunction = (filepath: string) => number | undefined;

interface YfmConfig {
varsPreset: VarsPreset;
Expand Down
48 changes: 46 additions & 2 deletions src/services/contributors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ async function getContributorsForNestedFiles(
return Object.assign({}, ...includesContributors);
}

function getRelativeIncludeFilePaths(fileData: FileData, includeContents: string[]): Set<string> {
function getRelativeIncludeFilePaths(
fileData: Pick<FileData, 'tmpInputFilePath'>,
includeContents: string[],
): Set<string> {
const {tmpInputFilePath} = fileData;
const relativeIncludeFilePaths: Set<string> = new Set();

Expand All @@ -115,4 +118,45 @@ function getRelativeIncludeFilePaths(fileData: FileData, includeContents: string
return relativeIncludeFilePaths;
}

export {getFileContributorsMetadata, getFileContributorsString};
async function getFileIncludes(
fileData: Pick<FileData, 'fileContent' | 'tmpInputFilePath' | 'inputFolderPathLength'>,
) {
const {fileContent, tmpInputFilePath, inputFolderPathLength} = fileData;

const results = new Set<string>();

const includeContents = fileContent.match(REGEXP_INCLUDE_CONTENTS);
if (!includeContents || includeContents.length === 0) {
return [];
}
const relativeIncludeFilePaths: Set<string> = getRelativeIncludeFilePaths(
{tmpInputFilePath},
includeContents,
);
for (const relativeIncludeFilePath of relativeIncludeFilePaths.values()) {
const relativeFilePath = relativeIncludeFilePath.substring(inputFolderPathLength + 1);
if (results.has(relativeFilePath)) continue;
results.add(relativeFilePath);

let contentIncludeFile: string;
try {
contentIncludeFile = await readFile(relativeIncludeFilePath, 'utf8');
} catch (err) {
if (err.code === 'ENOENT') {
continue;
}
throw err;
}

const includedPaths = await getFileIncludes({
inputFolderPathLength,
fileContent: contentIncludeFile,
tmpInputFilePath: relativeIncludeFilePath,
});
includedPaths.forEach((path) => results.add(path));
}

return Array.from(results.values());
}

export {getFileContributorsMetadata, getFileContributorsString, getFileIncludes};
44 changes: 42 additions & 2 deletions src/services/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ import {
updateAuthorMetadataStringByAuthorLogin,
updateAuthorMetadataStringByFilePath,
} from './authors';
import {getFileContributorsMetadata, getFileContributorsString} from './contributors';
import {
getFileContributorsMetadata,
getFileContributorsString,
getFileIncludes,
} from './contributors';
import {isObject} from './utils';
import {сarriage} from '../utils';
import {REGEXP_AUTHOR, metadataBorder} from '../constants';
import {dirname, relative, resolve} from 'path';
import {ArgvService} from './index';
import {ArgvService, TocService} from './index';

async function getContentWithUpdatedMetadata(
fileContent: string,
Expand Down Expand Up @@ -105,6 +109,11 @@ async function getContentWithUpdatedDynamicMetadata(
newMetadatas.push(contributorsMetaData);
}

const mtimeMetadata = await getModifiedTimeMetadataString(options, fileContent);
if (mtimeMetadata) {
newMetadatas.push(mtimeMetadata);
}

let authorMetadata = '';
if (fileMetadata) {
const matchAuthor = fileMetadata.match(REGEXP_AUTHOR);
Expand Down Expand Up @@ -188,6 +197,37 @@ async function getContributorsMetadataString(
return undefined;
}

async function getModifiedTimeMetadataString(options: MetaDataOptions, fileContent: string) {
const {isContributorsEnabled, vcsConnector, fileData} = options;

const {tmpInputFilePath, inputFolderPathLength} = fileData;

const relativeFilePath = tmpInputFilePath.substring(inputFolderPathLength + 1);

if (!isContributorsEnabled || !vcsConnector) {
return undefined;
}

const includedFiles = await getFileIncludes({...fileData, fileContent});
includedFiles.push(relativeFilePath);

const tocCopyFileMap = TocService.getCopyFileMap();

const mtimeList = includedFiles
.map((path) => {
const mappedPath = tocCopyFileMap.get(path) || path;
return vcsConnector.getModifiedTimeByPath(mappedPath);
})
.filter((v) => typeof v === 'number') as number[];

if (mtimeList.length) {
const mtime = Math.max(...mtimeList);
return `updatedAt: ${new Date(mtime * 1000).toISOString()}`;
}

return undefined;
}

function getUpdatedMetadataString(newMetadatas: string[], defaultMetadata = ''): string {
const newMetadata = newMetadatas.join(сarriage) + (newMetadatas.length ? сarriage : '');
const preparedDefaultMetadata = defaultMetadata.trimRight();
Expand Down
10 changes: 10 additions & 0 deletions src/services/tocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface TocServiceData {
const storage: TocServiceData['storage'] = new Map();
let navigationPaths: TocServiceData['navigationPaths'] = [];
const includedTocPaths: TocServiceData['includedTocPaths'] = new Set();
const tocFileCopyMap = new Map<string, string>();

async function add(path: string) {
const {
Expand Down Expand Up @@ -207,6 +208,10 @@ function _copyTocDir(tocPath: string, destDir: string) {
} else {
shell.cp(from, to);
}

const relFrom = relative(inputFolderPath, from);
const relTo = relative(inputFolderPath, to);
tocFileCopyMap.set(relTo, relFrom);
});
}

Expand Down Expand Up @@ -396,11 +401,16 @@ function setNavigationPaths(paths: TocServiceData['navigationPaths']) {
navigationPaths = paths;
}

function getCopyFileMap() {
return tocFileCopyMap;
}

export default {
add,
getForPath,
getNavigationPaths,
getTocDir,
getIncludedTocPaths,
setNavigationPaths,
getCopyFileMap,
};
2 changes: 2 additions & 0 deletions src/vcs-connector/connector-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Contributors,
ContributorsByPathFunction,
ExternalAuthorByPathFunction,
GetModifiedTimeByPathFunction,
NestedContributorsForPathFunction,
UserByLoginFunction,
} from '../models';
Expand Down Expand Up @@ -31,6 +32,7 @@ export interface VCSConnector {
addNestedContributorsForPath: NestedContributorsForPathFunction;
getContributorsByPath: ContributorsByPathFunction;
getUserByLogin: UserByLoginFunction;
getModifiedTimeByPath: GetModifiedTimeByPathFunction;
}

export interface VCSConnectorConfig {
Expand Down
43 changes: 42 additions & 1 deletion src/vcs-connector/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ const authorByPath: Map<string, Contributor | null> = new Map();
const contributorsByPath: Map<string, FileContributors> = new Map();
const contributorsData: Map<string, Contributor | null> = new Map();
const loginUserMap: Map<string, Contributor | null> = new Map();
const pathMTime = new Map<string, number>();

async function getGitHubVCSConnector(): Promise<VCSConnector | undefined> {
const {contributors} = ArgvService.getConfig();
const {contributors, rootInput} = ArgvService.getConfig();

const httpClientByToken = getHttpClientByToken();
if (!httpClientByToken) {
Expand All @@ -49,6 +50,7 @@ async function getGitHubVCSConnector(): Promise<VCSConnector | undefined> {
authorByPath.get(path) ?? null;

if (contributors) {
await getFilesMTime(rootInput, pathMTime);
await getAllContributorsTocFiles(httpClientByToken);
addNestedContributorsForPath = (path: string, nestedContributors: Contributors) =>
addNestedContributorsForPathFunction(path, nestedContributors);
Expand All @@ -60,6 +62,7 @@ async function getGitHubVCSConnector(): Promise<VCSConnector | undefined> {
addNestedContributorsForPath,
getContributorsByPath,
getUserByLogin: (login: string) => getUserByLogin(httpClientByToken, login),
getModifiedTimeByPath: (filename: string) => pathMTime.get(filename),
};
}

Expand Down Expand Up @@ -364,4 +367,42 @@ function shouldAuthorBeIgnored({email, name}: ShouldAuthorBeIgnoredArgs) {
return false;
}

async function getFilesMTime(repoDir: string, pathMTime: Map<string, number>) {
const timeFiles = await simpleGit({
baseDir: repoDir,
}).raw(
'log',
'--reverse',
'--before=now',
'--diff-filter=ADMR',
'--pretty=format:%ct',
'--name-status',
);

const parts = timeFiles.split(/\n\n/);
parts.forEach((part) => {
const lines = part.trim().split(/\n/);
const committerDate = lines.shift();
const unixtime = Number(committerDate);
lines.forEach((line) => {
const [status, from, to] = line.split(/\t/);
switch (status[0]) {
case 'R': {
pathMTime.delete(from);
pathMTime.set(to, unixtime);
break;
}
case 'D': {
pathMTime.delete(from);
break;
}
default: {
pathMTime.set(from, unixtime);
}
}
});
});
return pathMTime;
}

export default getGitHubVCSConnector;
2 changes: 2 additions & 0 deletions tests/integrations/services/metadataAuthors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const simpleMetadataFilePath = 'mocks/fileContent/metadata/simpleMetadata.md';

jest.mock('services/contributors', () => ({
getFileContributorsMetadata: () => Promise.resolve(''),
getFileIncludes: () => Promise.resolve([]),
}));

describe('getContentWithUpdatedMetadata (Authors)', () => {
Expand All @@ -27,6 +28,7 @@ describe('getContentWithUpdatedMetadata (Authors)', () => {
getContributorsByPath: () => Promise.resolve(null),
getUserByLogin: () => Promise.resolve(expectedAuthorData),
getExternalAuthorByPath: () => expectedAuthorData,
getModifiedTimeByPath: () => undefined,
};

describe('should return file content with updated author in metadata', () => {
Expand Down
1 change: 1 addition & 0 deletions tests/integrations/services/metadataContributors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('getContentWithUpdatedMetadata (Contributors)', () => {
getContributorsByPath: () => Promise.resolve(null),
getUserByLogin: () => Promise.resolve(null),
getExternalAuthorByPath: () => null,
getModifiedTimeByPath: () => undefined,
};

describe('should return file content with updated contributors in metadata ' +
Expand Down
3 changes: 2 additions & 1 deletion tests/units/services/authors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ const author = {
const authorByPath: Map<string, Contributor | null> = new Map();

const defaultVCSConnector: VCSConnector = {
addNestedContributorsForPath: () => {},
addNestedContributorsForPath: () => { },
getContributorsByPath: () => Promise.resolve(null),
getUserByLogin: () => Promise.resolve(author),
getExternalAuthorByPath: (path) => authorByPath.get(path),
getModifiedTimeByPath: () => undefined,
};

describe('getAuthorDetails returns author details', () => {
Expand Down
2 changes: 2 additions & 0 deletions tests/units/services/metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const contributorsString: string = replaceDoubleToSingleQuotes(JSON.stringify(co

jest.mock('services/contributors', () => ({
getFileContributorsString: () => Promise.resolve(contributorsString),
getFileIncludes: () => Promise.resolve([]),
}));

const authorString: string = replaceDoubleToSingleQuotes(JSON.stringify(contributorSecond));
Expand All @@ -38,6 +39,7 @@ const defaultVCSConnector: VCSConnector = {
getContributorsByPath: () => Promise.resolve(null),
getUserByLogin: () => Promise.resolve(null),
getExternalAuthorByPath: () => null,
getModifiedTimeByPath: () => undefined,
};

describe('getUpdatedMetadata', () => {
Expand Down

0 comments on commit 63f6f5e

Please sign in to comment.