Skip to content

Commit 74f178a

Browse files
committed
feat: ✨ update metadata workflow + clean file structure
1 parent 3e04849 commit 74f178a

9 files changed

Lines changed: 1101 additions & 1380 deletions

File tree

bot/commands/validations/index.js

Lines changed: 159 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,21 @@ import { renderIssues, createIssue } from "../../utils/renderer/index.js";
33
import dbInstance from "../../db.js";
44
import { logwatch } from "../../utils/logwatch.js";
55
import { applyLastModifiedTemplate } from "../../utils/tools/index.js";
6-
import { validateLicense } from "../../compliance-checks/license/index.js";
6+
import {
7+
checkForLicense,
8+
validateLicense,
9+
} from "../../compliance-checks/license/index.js";
710
import { getCWLFiles } from "../../compliance-checks/cwl/index.js";
811
import {
9-
validateMetadata,
10-
getCitationContent,
11-
getCodemetaContent,
12-
gatherMetadata,
13-
convertDateToUnix,
14-
applyDbMetadata,
15-
applyCodemetaMetadata,
16-
applyCitationMetadata,
12+
checkMetadataFilesExists,
13+
updateMetadataDatabase,
14+
applyMetadataTemplate,
1715
} from "../../compliance-checks/metadata/index.js";
1816
import { checkForReadme } from "../../compliance-checks/readme/index.js";
19-
import { createId } from "../../utils/tools/index.js";
20-
import { checkForCodeofConduct } from "../../compliance-checks/code-of-conduct/index.js";
21-
import { checkForContributingFile } from "../../compliance-checks/contributing/index.js";
17+
import {
18+
checkForContributingFile,
19+
checkForCodeofConduct,
20+
} from "../../compliance-checks/additional-checks/index.js";
2221

2322
const ISSUE_TITLE = `FAIR Compliance Dashboard`;
2423
const db = dbInstance;
@@ -234,125 +233,131 @@ export async function rerunMetadataValidation(
234233
repository,
235234
issueBody
236235
) {
237-
logwatch.start("Validating metadata files...");
238-
try {
239-
let metadata = await gatherMetadata(context, owner, repository);
240-
let containsCitation = false,
241-
containsCodemeta = false,
242-
validCitation = false,
243-
validCodemeta = false;
236+
const repoInfo = `${owner}/${repository.name}`;
237+
logwatch.start(
238+
`Rerunning metadata validation for repo: ${repository.name} (ID: ${repository.id})`
239+
);
244240

245-
let existingMetadataEntry = await db.codeMetadata.findUnique({
246-
where: {
247-
repository_id: repository.id,
241+
try {
242+
// Check which metadata files exist
243+
const subjects = await checkMetadataFilesExists(context, owner, repository);
244+
245+
// Get license status (needed for metadata checks)
246+
const licenseCheck = await checkForLicense(context, owner, repository.name);
247+
subjects.license = licenseCheck?.status;
248+
249+
// Force revalidation by creating a synthetic context that looks like bot push
250+
const syntheticContext = {
251+
...context,
252+
payload: {
253+
...context.payload,
254+
pusher: { name: `${process.env.GH_APP_NAME}[bot]` }, // Triggers full revalidation
248255
},
249-
});
256+
};
250257

251-
if (existingMetadataEntry?.metadata) {
252-
// Update the metadata variable
253-
containsCitation = existingMetadataEntry.contains_citation;
254-
containsCodemeta = existingMetadataEntry.contains_codemeta;
255-
metadata = applyDbMetadata(existingMetadataEntry, metadata);
256-
} else {
257-
// create blank entry to prevent issues down the line
258-
existingMetadataEntry = await db.codeMetadata.create({
259-
data: {
260-
identifier: createId(),
261-
repository: {
262-
connect: {
263-
id: repository.id,
264-
},
265-
},
266-
},
267-
});
268-
}
258+
await updateMetadataDatabase(
259+
repository.id,
260+
subjects,
261+
repository,
262+
owner,
263+
syntheticContext
264+
);
269265

270-
const citation = await getCitationContent(context, owner, repository);
271-
const codemeta = await getCodemetaContent(context, owner, repository);
266+
// Generate new metadata section
267+
let newMetadataSection = "";
268+
newMetadataSection = await applyMetadataTemplate(
269+
subjects,
270+
newMetadataSection,
271+
repository,
272+
owner,
273+
syntheticContext
274+
);
272275

273-
if (codemeta) {
274-
containsCodemeta = true;
275-
validCodemeta = await validateMetadata(codemeta, "codemeta", repository);
276-
metadata = await applyCodemetaMetadata(codemeta, metadata, repository);
277-
}
276+
// Parse the existing issue body to replace just the metadata section
277+
const issueBodyWithoutTimestamp = issueBody.substring(
278+
0,
279+
issueBody.indexOf(`<sub><span style="color: grey;">Last updated`)
280+
);
278281

279-
if (citation) {
280-
containsCitation = true;
281-
validCitation = await validateMetadata(citation, "citation", repository);
282-
metadata = await applyCitationMetadata(citation, metadata, repository);
283-
// consola.info("Metadata so far after citation update", JSON.stringify(metadata, null, 2));
284-
}
282+
// Find the metadata section boundaries
283+
const metadataStartMarker = "## Metadata";
284+
const metadataStartIndex =
285+
issueBodyWithoutTimestamp.indexOf(metadataStartMarker);
285286

286-
// Ensure all dates have been converted to ISO strings split by the T
287-
if (metadata.creationDate) {
288-
metadata.creationDate = convertDateToUnix(metadata.creationDate);
289-
}
290-
if (metadata.firstReleaseDate) {
291-
metadata.firstReleaseDate = convertDateToUnix(metadata.firstReleaseDate);
292-
}
293-
if (metadata.currentVersionReleaseDate) {
294-
metadata.currentVersionReleaseDate = convertDateToUnix(
295-
metadata.currentVersionReleaseDate
296-
);
287+
if (metadataStartIndex === -1) {
288+
throw new Error("Could not find Metadata section in issue body");
297289
}
298290

299-
// update the database with the metadata information
300-
if (existingMetadataEntry) {
301-
await db.codeMetadata.update({
302-
data: {
303-
codemeta_status: validCodemeta ? "valid" : "invalid",
304-
citation_status: validCitation ? "valid" : "invalid",
305-
contains_citation: containsCitation,
306-
contains_codemeta: containsCodemeta,
307-
metadata: metadata,
308-
},
309-
where: {
310-
repository_id: repository.id,
311-
},
312-
});
291+
// Find the next section (starts with ## or end of string)
292+
const afterMetadataStart = issueBodyWithoutTimestamp.substring(
293+
metadataStartIndex + metadataStartMarker.length
294+
);
295+
const nextSectionMatch = afterMetadataStart.match(/\n## /);
296+
297+
let updatedBody;
298+
if (nextSectionMatch) {
299+
// There's another section after Metadata
300+
const nextSectionIndex =
301+
metadataStartIndex +
302+
metadataStartMarker.length +
303+
nextSectionMatch.index;
304+
305+
updatedBody =
306+
issueBodyWithoutTimestamp.substring(0, metadataStartIndex) + // Before Metadata
307+
newMetadataSection + // New Metadata section
308+
issueBodyWithoutTimestamp.substring(nextSectionIndex); // After Metadata
313309
} else {
314-
await db.codeMetadata.create({
315-
data: {
316-
codemeta_status: validCodemeta ? "valid" : "invalid",
317-
citation_status: validCitation ? "valid" : "invalid",
318-
contains_citation: containsCitation,
319-
contains_codemeta: containsCodemeta,
320-
metadata: metadata,
321-
},
322-
where: {
323-
repository_id: repository.id,
324-
},
325-
});
310+
// Metadata is the last section
311+
updatedBody =
312+
issueBodyWithoutTimestamp.substring(0, metadataStartIndex) + // Before Metadata
313+
newMetadataSection; // New Metadata section
326314
}
327315

328-
const issueBodyRemovedCommand = issueBody.substring(
329-
0,
330-
issueBody.indexOf(`<sub><span style="color: grey;">Last updated`)
331-
);
332-
const lastModified = await applyLastModifiedTemplate(
333-
issueBodyRemovedCommand
316+
// Add timestamp
317+
updatedBody = applyLastModifiedTemplate(updatedBody);
318+
319+
// Update the issue
320+
await createIssue(context, owner, repository, ISSUE_TITLE, updatedBody);
321+
logwatch.info(
322+
`Metadata validation rerun completed for repo: ${repository.name} (ID: ${repository.id})`
334323
);
335-
await createIssue(context, owner, repository, ISSUE_TITLE, lastModified);
336324
} catch (error) {
337-
// Remove the command from the issue body
338-
const issueBodyRemovedCommand = issueBody.substring(
339-
0,
340-
issueBody.indexOf(`<sub><span style="color: grey;">Last updated`)
341-
);
342-
const lastModified = await applyLastModifiedTemplate(
343-
issueBodyRemovedCommand
325+
logwatch.error(
326+
{
327+
message: "Failed to rerun metadata validation",
328+
repo: repoInfo,
329+
error: error.message,
330+
stack: error.stack,
331+
},
332+
true
344333
);
345-
await createIssue(context, owner, repository, ISSUE_TITLE, lastModified);
346-
if (error.cause) {
347-
logwatch.error(
334+
335+
// rrestore issue body without command
336+
try {
337+
const issueBodyRemovedCommand = issueBody.substring(
338+
0,
339+
issueBody.indexOf(`<sub><span style="color: grey;">Last updated`)
340+
);
341+
const lastModified = applyLastModifiedTemplate(issueBodyRemovedCommand);
342+
343+
await context.octokit.issues.update({
344+
owner,
345+
repo: repository.name,
346+
issue_number: context.payload.issue.number,
347+
body: lastModified,
348+
});
349+
} catch (restoreError) {
350+
logwatch.warn(
348351
{
349-
message: "Error.cause message for Metadata Validation",
350-
error: error.cause,
352+
message: "Failed to restore issue body after error",
353+
repo: repoInfo,
354+
error: restoreError.message,
351355
},
352356
true
353357
);
354358
}
355-
throw new Error("Error rerunning metadata validation", error);
359+
360+
throw error;
356361
}
357362
}
358363

@@ -365,68 +370,61 @@ export async function rerunLicenseValidation(
365370
// Run the license validation again
366371
logwatch.start("Rerunning License Validation...");
367372
try {
368-
const licenseRequest = await context.octokit.rest.licenses.getForRepo({
369-
owner,
370-
repo: repository.name,
371-
});
372-
373-
const existingLicense = await db.licenseRequest.findUnique({
374-
where: {
375-
repository_id: repository.id,
376-
},
377-
});
378-
379-
const license = !!licenseRequest.data.license;
373+
const license = await checkForLicense(context, owner, repository.name);
380374

381375
if (!license) {
382376
throw new Error("License not found in the repository");
383377
}
384378

385-
const { licenseId, licenseContent, licenseContentEmpty } = validateLicense(
386-
licenseRequest,
387-
existingLicense
388-
);
389-
390-
logwatch.info({
391-
message: `License validation complete`,
392-
licenseId,
393-
licenseContent,
394-
licenseContentEmpty,
395-
});
396-
397379
// Update the database with the license information
398-
if (existingLicense) {
399-
await db.licenseRequest.update({
400-
data: {
401-
license_id: licenseId,
402-
license_content: licenseContent,
403-
license_status: licenseContentEmpty ? "invalid" : "valid",
404-
},
405-
where: {
406-
repository_id: repository.id,
407-
},
408-
});
409-
} else {
410-
await db.licenseRequest.create({
411-
data: {
412-
license_id: licenseId,
413-
license_content: licenseContent,
414-
license_status: licenseContentEmpty ? "invalid" : "valid",
415-
},
416-
where: {
417-
repository_id: repository.id,
418-
},
419-
});
420-
}
380+
await updateLicenseDatabase(repository, license);
421381

422382
// Update the issue body
423383
const issueBodyRemovedCommand = issueBody.substring(
424384
0,
425385
issueBody.indexOf(`<sub><span style="color: grey;">Last updated`)
426386
);
427-
const lastModified = await applyLastModifiedTemplate(
428-
issueBodyRemovedCommand
387+
388+
// Generate new license section
389+
let newLicenseSection = "";
390+
newLicenseSection = await applyLicenseTemplate(
391+
license,
392+
newLicenseSection,
393+
repository,
394+
owner,
395+
context
396+
);
397+
// Parse the existing issue body to replace just the license section
398+
const issueBodyWithoutTimestamp = issueBodyRemovedCommand;
399+
const licenseStartMarker = "## LICENSE";
400+
const licenseStartIndex =
401+
issueBodyWithoutTimestamp.indexOf(licenseStartMarker);
402+
if (licenseStartIndex === -1) {
403+
throw new Error("Could not find LICENSE section in issue body");
404+
}
405+
406+
// Find the next section (starts with ## or end of string)
407+
const afterLicenseStart = issueBodyWithoutTimestamp.substring(
408+
licenseStartIndex + licenseStartMarker.length
429409
);
410+
const nextSectionMatch = afterLicenseStart.match(/\n## /);
411+
let updatedBody;
412+
if (nextSectionMatch) {
413+
// There's another section after LICENSE
414+
const nextSectionIndex =
415+
licenseStartIndex + licenseStartMarker.length + nextSectionMatch.index;
416+
updatedBody =
417+
issueBodyWithoutTimestamp.substring(0, licenseStartIndex) + // Before LICENSE
418+
newLicenseSection + // New LICENSE section
419+
issueBodyWithoutTimestamp.substring(nextSectionIndex); // After LICENSE
420+
} else {
421+
// LICENSE is the last section
422+
updatedBody =
423+
issueBodyWithoutTimestamp.substring(0, licenseStartIndex) + // Before LICENSE
424+
newLicenseSection; // New LICENSE section
425+
}
426+
427+
const lastModified = await applyLastModifiedTemplate(updatedBody);
430428
await createIssue(context, owner, repository, ISSUE_TITLE, lastModified);
431429
} catch (error) {
432430
// Remove the command from the issue body
@@ -443,6 +441,7 @@ export async function rerunLicenseValidation(
443441
{
444442
message: "Error.cause message for License Validation",
445443
error: error.cause,
444+
stack: error?.stack,
446445
},
447446
true
448447
);

0 commit comments

Comments
 (0)