preamble
Version:
Automated License & Metadata applicators for Codebases.
197 lines (161 loc) • 5.89 kB
JavaScript
/* *******************************************************
* preamble
*
* @license
*
* Apache-2.0
*
* Copyright 2024 Alex Stevovich
*
* 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.
*
* @meta
*
* package_name: preamble
* file_name: src/index.js
* purpose: {{PURPOSE}}
*
* @system
*
* generated_on: 2025-03-09T02:08:39.988Z
* certified_version: 1.0.0
* file_uuid: bb69f848-fd48-4ff3-9b5c-a992a6686166
* file_size: 5891 bytes
* file_hash: 647c8710ebd7adb86e7abda1888282c54a12d2aaa3835fb6509654e9682c2492
* mast_hash: 6f79d08ffd90cdb749f3348bb8e10e369cb0ff03613b932a196fd874bf3d0945
* generated_by: preamble on npm!
*
* [Preamble Metadata]
********************************************************/
import path from 'path';
import { createHash, randomUUID } from 'crypto';
function isValidUUID(uuid) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
uuid,
);
}
export function extractLicenseFields(matter) {
const fields = {};
const lines = matter.split('\n');
for (const line of lines) {
const trimmedLine = line.trim();
if (!trimmedLine) continue;
const match = trimmedLine.match(/(?:.*\s)?([a-zA-Z0-9_]+)\s*:\s*(.*)$/);
if (match) {
const key = match[1].trim();
const value = match[2].trim();
fields[key] = value;
}
}
return fields;
}
function hash(input) {
return createHash('sha256').update(input, 'utf8').digest('hex');
}
function templateLicense(license, data) {
for (const [key, value] of Object.entries(data)) {
const pattern = new RegExp(`{{\\s*${key.toUpperCase()}\\s*}}`, 'g');
license = license.replace(pattern, value);
}
return license;
}
/**
* Extracts a block comment containing a specific identifier.
* Ensures the block is properly removed and can be reinserted later.
*/
function extractDecoratorByContains(content, identifier) {
const blockCommentRegex = /\/\*[\s\S]*?\*\//g;
const shebangRegex = /^#![^\n]+\n/;
// Extract shebang first before processing anything else
let shebangMatch = content.match(shebangRegex);
let shebang = shebangMatch ? shebangMatch[0] : null;
// Remove the shebang from content before extracting the decorator
let newContent = shebang
? content.replace(shebangRegex, '').trimStart()
: content;
// Extract block comment containing the identifier
let matches = newContent.match(blockCommentRegex) || [];
let decorator = null;
for (let match of matches) {
if (match.includes(identifier)) {
decorator = match;
break;
}
}
if (!decorator) {
console.error(
`⚠️ Warning: No decorator containing '${identifier}' was found.`,
);
return { decorator: null, content: newContent, shebang };
}
// Remove the decorator block
newContent = newContent.replace(decorator, '').trimStart();
return { decorator, content: newContent, shebang };
}
export async function inscribeJs(content, template, data) {
const preambleMarker = '[Preamble Metadata]';
const {
decorator: previousLicense,
content: strippedContent,
shebang,
} = extractDecoratorByContains(content, preambleMarker);
let DATA = {};
if (previousLicense) {
Object.assign(DATA, extractLicenseFields(previousLicense));
}
const date = new Date();
DATA.gen_package_name = data.pkg.name || 'Unknown';
DATA.gen_package_description = data.pkg.description || 'Unknown';
// Normalize file paths
const baseDir = process.cwd();
DATA.gen_file_name = path.posix.normalize(
path.relative(baseDir, data.file.path).replace(/\\/g, '/'),
);
DATA.gen_full_year = date.getFullYear();
if (!isValidUUID(DATA.file_uuid)) {
DATA.gen_file_uuid = randomUUID();
} else {
DATA.gen_file_uuid = DATA.file_uuid;
}
DATA.preamble_marker = preambleMarker;
const oldDocHash = DATA.file_hash;
const oldBlockHash = DATA.mast_hash;
const newDocHash = hash(strippedContent);
let docUpdated = newDocHash !== oldDocHash;
template = templateLicense(template, DATA);
const newBlockHash = hash(template);
DATA = {};
DATA.gen_generated_on = new Date().toISOString();
DATA.gen_certified_version = data.pkg.version || '0.0.0';
DATA.gen_file_hash = newDocHash;
DATA.gen_file_size = data.file.size + ' bytes';
//how can i get the version of preamble here?
DATA.gen_generated_by = 'preamble on npm!';
template = templateLicense(template, DATA);
let blockUpdated = newBlockHash !== oldBlockHash;
DATA.gen_mast_hash = newBlockHash;
const finalBlock = templateLicense(template, DATA);
if (!finalBlock.includes(preambleMarker)) {
throw new Error(
`Masthead insertion failed: The generated masthead block does not contain the required identifier. Place {{DECORATOR}} somewhere in your template'.`,
);
}
let updated = docUpdated || blockUpdated;
if (updated) {
console.log('Result: Changes.');
} else {
console.log('Result: No Changes.');
}
let finalContent = `${shebang ? shebang : ''}/* ${finalBlock}*/ \n${strippedContent}`;
return { result: finalContent, updated };
}