@jeswr/vc-cli
Version:
A CLI tool for creating and managing Verifiable Credentials
740 lines (649 loc) • 30.9 kB
JavaScript
import { program } from 'commander';
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { randomUUID } from 'node:crypto';
import {
generateCIDDocument,
signCredential,
verifyCredential,
deriveProof,
preprocessBBSVerification,
preprocessEd25519Verification,
collectDocuments,
getDocumentLoaderContent
} from './index.js';
// Get the directory path of the current file
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Read package.json to get version
const packageJson = JSON.parse(await fs.readFile(path.join(__dirname, 'package.json'), 'utf8'));
program
.name('vc-cli')
.description('CLI utility for generating CIDs and issuing verifiable credentials')
.version(packageJson.version);
program
.command('generate-cid')
.description('Generate a new CID document')
.requiredOption('-c, --controller <controller>', 'Controller DID')
.option('-o, --output <path>', 'Output path for CID document (file or directory)')
.option('-k, --keys <path>', 'Path to save private keys JSON file')
.option('--no-ed25519', 'Exclude Ed25519 signature type')
.option('--no-bbs', 'Exclude BBS+ signature type')
.option('--document-loader-content <path>', 'Path to JSON file containing predefined document loader responses')
.action(async (options) => {
try {
const { cid, privateKeys } = await generateCIDDocument(options.controller, {
includeEd25519: options.ed25519,
includeBBS: options.bbs
});
// Handle CID document output
if (options.output) {
let outputPath = options.output;
try {
const stats = await fs.stat(outputPath);
if (stats.isDirectory()) {
// If it's a directory, create a file named after the encoded controller
const encodedController = encodeURIComponent(options.controller);
outputPath = path.join(outputPath, `${encodedController}.jsonld`);
}
} catch (error) {
// If the path doesn't exist, assume it's a file path
const dir = path.dirname(outputPath);
await fs.mkdir(dir, { recursive: true });
}
await fs.writeFile(outputPath, JSON.stringify(cid, null, 2));
console.log(`CID document saved to: ${outputPath}`);
} else {
console.log('CID Document:');
console.log(JSON.stringify(cid, null, 2));
}
// Handle private keys output
if (options.keys) {
try {
// Check if file exists
const existingContent = await fs.readFile(options.keys, 'utf8');
const existingKeys = JSON.parse(existingContent);
// Merge existing keys with new keys
const mergedKeys = { ...existingKeys, ...privateKeys };
await fs.writeFile(options.keys, JSON.stringify(mergedKeys, null, 2));
console.log(`Private keys appended to: ${options.keys}`);
} catch (error) {
// If file doesn't exist or is invalid JSON, create new file
await fs.writeFile(options.keys, JSON.stringify(privateKeys, null, 2));
console.log(`Private keys saved to: ${options.keys}`);
}
} else {
console.log('\nPrivate Keys:');
console.log(JSON.stringify(privateKeys, null, 2));
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('sign-credential')
.description('Sign a verifiable credential using a CID document and private keys')
.requiredOption('-c, --cid <path>', 'Path to CID document')
.requiredOption('-k, --keys <path>', 'Path to private keys JSON file')
.requiredOption('-d, --document <path>', 'Path to JSON-LD document to sign')
.requiredOption('-i, --key-id <id>', 'ID of the key to use for signing')
.requiredOption('-o, --output <path>', 'Output path for signed credential')
.option('--credential-id <id>', 'ID for the credential (optional)')
.option('--subject-id <id>', 'ID for the credential subject (optional)')
.option('--document-loader-content <path>', 'Path to JSON file containing predefined document loader responses')
.action(async (options) => {
try {
// Read the CID document
const cidContent = await fs.readFile(options.cid, 'utf8');
const cid = JSON.parse(cidContent);
// Read the private keys
const keysContent = await fs.readFile(options.keys, 'utf8');
const privateKeys = JSON.parse(keysContent);
// Read the document to sign
const documentContent = await fs.readFile(options.document, 'utf8');
const document = JSON.parse(documentContent);
const signedVC = await signCredential({
cid,
privateKeys,
document,
keyId: options.keyId,
credentialId: options.credentialId,
subjectId: options.subjectId,
documentLoaderContent: await getDocumentLoaderContent(options)
});
// Write the signed credential to the output file
await fs.writeFile(options.output, JSON.stringify(signedVC, null, 2));
console.log(`Signed credential saved to: ${options.output}`);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('verify-credential')
.description('Verify a verifiable credential using a CID document')
.requiredOption('-c, --cid <path>', 'Path to CID document')
.requiredOption('-d, --document <path>', 'Path to verifiable credential to verify')
.option('--document-loader-content <path>', 'Path to JSON file containing predefined document loader responses')
.action(async (options) => {
try {
// Read the CID document
const cidContent = await fs.readFile(options.cid, 'utf8');
const cid = JSON.parse(cidContent);
// Read the verifiable credential
const documentContent = await fs.readFile(options.document, 'utf8');
const document = JSON.parse(documentContent);
const isValid = await verifyCredential({ cid, document, documentLoaderContent: await getDocumentLoaderContent(options) });
if (isValid) {
console.log('Credential verified successfully!');
} else {
console.error('Credential verification failed');
process.exit(1);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('derive-proof')
.description('Create a derived BBS proof from a signed input BBS document')
.requiredOption('-d, --document <path>', 'Path to signed BBS document')
.requiredOption('-r, --reveal <pointers>', 'Comma-separated list of JSON pointers to reveal (e.g. /credentialSubject/name,/credentialSubject/age)')
.requiredOption('-o, --output <path>', 'Output path for derived document')
.option('--document-loader-content <path>', 'Path to JSON file containing predefined document loader responses')
.action(async (options) => {
try {
// Read the signed document
const documentContent = await fs.readFile(options.document, 'utf8');
const document = JSON.parse(documentContent);
// Parse the reveal pointers
const revealPointers = options.reveal.split(',').map(pointer => pointer.trim());
const derivedDocument = await deriveProof({
document,
revealPointers,
documentLoaderContent: await getDocumentLoaderContent(options)
});
// Write the derived document to the output file
await fs.writeFile(options.output, JSON.stringify(derivedDocument, null, 2));
console.log(`Derived document saved to: ${options.output}`);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('bbs-verify-preprocess')
.description('Preprocess BBS verification data from derived credentials')
.requiredOption('-d, --document <path>', 'Path to derived BBS document or directory containing derived BBS documents')
.requiredOption('-c, --cid <path>', 'Path to CID document')
.requiredOption('-o, --output <path>', 'Output path for preprocessed data (file or directory)')
.option('--document-loader-content <path>', 'Path to JSON file containing predefined document loader responses')
.action(async (options) => {
try {
// Check if input is a directory
const stats = await fs.stat(options.document);
const isDirectory = stats.isDirectory();
// Get list of files to process
const filesToProcess = isDirectory ?
(await fs.readdir(options.document))
.filter(file => file.endsWith('.jsonld'))
.map(file => path.join(options.document, file)) :
[options.document];
if (filesToProcess.length === 0) {
throw new Error('No JSON-LD files found to process');
}
// Read the CID document
const cidContent = await fs.readFile(options.cid, 'utf8');
const cid = JSON.parse(cidContent);
// Process each file
for (const filePath of filesToProcess) {
try {
// Read the derived document
const documentContent = await fs.readFile(filePath, 'utf8');
const document = JSON.parse(documentContent);
const preprocessedData = await preprocessBBSVerification({
document,
cid,
documentLoaderContent: await getDocumentLoaderContent(options)
});
// Handle output path
let outputPath = options.output;
try {
const outputStats = await fs.stat(outputPath);
if (outputStats.isDirectory()) {
// If it's a directory, create a file named after the input document
const docName = path.basename(filePath, '.jsonld');
outputPath = path.join(outputPath, `${docName}-preprocessed.json`);
} else if (filesToProcess.length > 1) {
// If multiple files but output is a file, throw error
throw new Error('Output must be a directory when processing multiple files');
}
} catch (error) {
if (error.code === 'ENOENT') {
// If the path doesn't exist, assume it's a file path
const dir = path.dirname(outputPath);
await fs.mkdir(dir, { recursive: true });
} else {
throw error;
}
}
// Write the preprocessed data to the output file
await fs.writeFile(outputPath, JSON.stringify(preprocessedData, null, 2));
console.log(`Preprocessed data saved to: ${outputPath}`);
} catch (error) {
console.error(`Error processing ${filePath}:`, error.message);
if (filesToProcess.length === 1) {
// If only processing one file, exit with error
process.exit(1);
}
// Otherwise continue with next file
}
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('ed25519-verify-preprocess')
.description('Preprocess Ed25519 verification data from signed credentials')
.requiredOption('-d, --document <path>', 'Path to signed Ed25519 document or directory containing signed Ed25519 documents')
.requiredOption('-c, --cid <path>', 'Path to CID document')
.requiredOption('-o, --output <path>', 'Output path for preprocessed data (file or directory)')
.option('--document-loader-content <path>', 'Path to JSON file containing predefined document loader responses')
.action(async (options) => {
try {
// Check if input is a directory
const stats = await fs.stat(options.document);
const isDirectory = stats.isDirectory();
// Get list of files to process
const filesToProcess = isDirectory ?
(await fs.readdir(options.document))
.filter(file => file.endsWith('.jsonld'))
.map(file => path.join(options.document, file)) :
[options.document];
if (filesToProcess.length === 0) {
throw new Error('No JSON-LD files found to process');
}
// Read the CID document
const cidContent = await fs.readFile(options.cid, 'utf8');
const cid = JSON.parse(cidContent);
// Process each file
for (const filePath of filesToProcess) {
try {
// Read the signed document
const documentContent = await fs.readFile(filePath, 'utf8');
const document = JSON.parse(documentContent);
const preprocessedData = await preprocessEd25519Verification({
document,
cid,
documentLoaderContent: await getDocumentLoaderContent(options)
});
// Handle output path
let outputPath = options.output;
try {
const outputStats = await fs.stat(outputPath);
if (outputStats.isDirectory()) {
// If it's a directory, create a file named after the input document
const docName = path.basename(filePath, '.jsonld');
outputPath = path.join(outputPath, `${docName}-preprocessed.json`);
} else if (filesToProcess.length > 1) {
// If multiple files but output is a file, throw error
throw new Error('Output must be a directory when processing multiple files');
}
} catch (error) {
if (error.code === 'ENOENT') {
// If the path doesn't exist, assume it's a file path
const dir = path.dirname(outputPath);
await fs.mkdir(dir, { recursive: true });
} else {
throw error;
}
}
// Write the preprocessed data to the output file
await fs.writeFile(outputPath, JSON.stringify(preprocessedData, null, 2));
console.log(`Preprocessed data saved to: ${outputPath}`);
} catch (error) {
console.error(`Error processing ${filePath}:`, error.message);
if (filesToProcess.length === 1) {
// If only processing one file, exit with error
process.exit(1);
}
// Otherwise continue with next file
}
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('generate')
.description('Generate CIDs, sign credentials, and create derived proofs')
.option('-c, --cids <ids>', 'Comma-separated list of CID controller DIDs [default: "did:example:alice,did:example:bob,did:example:charlie,did:example:dave"]')
.option('-d, --documents <paths>', 'Comma-separated list of credential document paths to sign [default: all mock credentials]')
.option('-s, --signatures <types>', 'Comma-separated list of signature types to use (bbs,ed25519) [default: "bbs,ed25519"]')
.option('--no-derive', 'Skip creating derived proofs for BBS signatures')
.option('--no-preprocess', 'Skip preprocessing derived proofs')
.option('-o, --output-dir <path>', 'Output directory for generated files [default: "./generated"]')
.option('--distribute', 'Distribute documents across CIDs instead of having each CID sign all documents')
.option('--collect', 'Collect all generated files into a single Turtle file')
.option('--subject-id <id>', 'ID for the credential subject (optional)')
.option('--document-loader-content <path>', 'Path to JSON file containing predefined document loader responses')
.action(async (options) => {
try {
// Parse options with defaults
const cids = options.cids ? options.cids.split(',') : [
'did:example:alice',
'did:example:bob',
'did:example:charlie',
'did:example:dave'
];
const documents = options.documents ? options.documents.split(',') : [
path.join(__dirname, 'mocks/residence.jsonld'),
path.join(__dirname, 'mocks/barcode.jsonld'),
path.join(__dirname, 'mocks/employable.jsonld'),
path.join(__dirname, 'mocks/education.jsonld')
];
const signatures = options.signatures ? options.signatures.split(',') : ['bbs', 'ed25519'];
const shouldDerive = options.derive !== false;
const shouldPreprocess = options.preprocess !== false;
const baseOutputDir = options.outputDir || './generated';
const distribute = options.distribute || false;
const subjectId = options.subjectId || `did:example:${randomUUID()}`;
console.log('\n=== Starting Generation Process ===');
console.log(`Base output directory: ${baseOutputDir}`);
console.log(`CIDs to generate: ${cids.join(', ')}`);
console.log(`Documents to sign: ${documents.join(', ')}`);
console.log(`Signature types: ${signatures.join(', ')}`);
console.log(`Derive proofs: ${shouldDerive ? 'Yes' : 'No'}`);
console.log(`Preprocess derived proofs: ${shouldPreprocess ? 'Yes' : 'No'}`);
console.log(`Distribute documents: ${distribute ? 'Yes' : 'No'}`);
console.log(`Credential Subject ID: ${subjectId}\n`);
// Define directory paths and files
const cidsDir = path.join(baseOutputDir, 'cids');
const bbsDir = path.join(baseOutputDir, 'bbs');
const ed25519Dir = path.join(baseOutputDir, 'ed25519');
const derivedDir = path.join(baseOutputDir, 'derived');
const preprocessedDir = path.join(baseOutputDir, 'derived-preprocessed');
const ed25519PreprocessedDir = path.join(baseOutputDir, 'ed25519-preprocessed');
const keysFile = path.join(baseOutputDir, 'privateKeys.json');
// Create output directories
console.log('=== Creating Output Directories ===');
try {
await Promise.all([
fs.mkdir(cidsDir, { recursive: true }),
fs.mkdir(bbsDir, { recursive: true }),
fs.mkdir(ed25519Dir, { recursive: true }),
fs.mkdir(derivedDir, { recursive: true }),
shouldPreprocess && fs.mkdir(preprocessedDir, { recursive: true }),
shouldPreprocess && fs.mkdir(ed25519PreprocessedDir, { recursive: true })
]);
console.log('✓ Output directories created successfully\n');
} catch (error) {
throw new Error(`Failed to create output directories: ${error.message}`);
}
// Initialize combined private keys object
const allPrivateKeys = {};
// Generate CIDs
console.log('=== Generating CIDs ===');
const cidFiles = [];
for (const cid of cids) {
try {
console.log(`\nGenerating CID for: ${cid}`);
const shortName = cid.split(':').pop(); // Extract 'alice' from 'did:example:alice'
const cidFile = path.join(cidsDir, `${shortName}-cid.jsonld`);
// Generate CID and get private keys
const { cid: cidDoc, privateKeys } = await generateCIDDocument(cid, {
includeEd25519: signatures.includes('ed25519'),
includeBBS: signatures.includes('bbs')
});
// Save CID document
await fs.writeFile(cidFile, JSON.stringify(cidDoc, null, 2));
console.log(`✓ CID document saved to: ${cidFile}`);
// Merge private keys into combined object
Object.assign(allPrivateKeys, privateKeys);
cidFiles.push(cidFile);
} catch (error) {
throw new Error(`Failed to generate CID for ${cid}: ${error.message}`);
}
}
console.log('\n✓ All CIDs generated successfully\n');
// Save all private keys to a single file
console.log('=== Saving Private Keys ===');
try {
await fs.writeFile(keysFile, JSON.stringify(allPrivateKeys, null, 2));
console.log(`✓ All private keys saved to: ${keysFile}\n`);
} catch (error) {
throw new Error(`Failed to save private keys: ${error.message}`);
}
// Sign credentials
console.log('=== Signing Credentials ===');
const signedFiles = [];
// Helper function to sign a document with a CID
async function signDocumentWithCID(cidFile, docPath, signedFiles) {
try {
const cidContent = await fs.readFile(cidFile, 'utf8');
const cid = JSON.parse(cidContent);
const shortName = cid.id.split(':').pop();
const docName = path.basename(docPath, '.jsonld');
console.log(`\nProcessing document: ${docName} with CID: ${cid.id}`);
for (const sigType of signatures) {
try {
const keyId = cid.verificationMethod.find(vm =>
sigType === 'bbs' ? vm.publicKeyMultibase.startsWith('zUC7') : !vm.publicKeyMultibase.startsWith('zUC7')
)?.id;
if (!keyId) {
console.warn(`⚠️ No ${sigType} key found for CID ${cid.id}, skipping...`);
continue;
}
const outputDir = sigType === 'bbs' ? bbsDir : ed25519Dir;
const outputFile = path.join(outputDir, `${docName}-${shortName}.jsonld`);
const credentialId = `urn:uuid:${randomUUID()}`;
console.log(`Signing with ${sigType.toUpperCase()}...`);
const documentContent = await fs.readFile(docPath, 'utf8');
const document = JSON.parse(documentContent);
const signedVC = await signCredential({
cid,
privateKeys: allPrivateKeys,
document,
keyId,
credentialId,
subjectId
});
await fs.writeFile(outputFile, JSON.stringify(signedVC, null, 2));
console.log(`✓ Signed document saved to: ${outputFile}`);
signedFiles.push({
file: outputFile,
type: sigType,
cid: cid.id
});
} catch (error) {
throw new Error(`Failed to sign document ${docPath} with ${sigType} for CID ${cid.id}: ${error.message}`);
}
}
} catch (error) {
throw new Error(`Failed to process CID file ${cidFile}: ${error.message}`);
}
}
if (distribute) {
// Distribute documents across CIDs
for (let i = 0; i < documents.length; i++) {
const docPath = documents[i];
const cidIndex = i % cidFiles.length;
const cidFile = cidFiles[cidIndex];
await signDocumentWithCID(cidFile, docPath, signedFiles);
}
} else {
// Original behavior - each CID signs all documents
for (const cidFile of cidFiles) {
for (const docPath of documents) {
await signDocumentWithCID(cidFile, docPath, signedFiles);
}
}
}
console.log('\n✓ All credentials signed successfully\n');
// Create derived proofs for BBS signatures
if (shouldDerive) {
console.log('=== Creating Derived Proofs ===');
const bbsFiles = signedFiles.filter(f => f.type === 'bbs');
for (const { file } of bbsFiles) {
try {
const docName = path.basename(file, '.jsonld');
const outputFile = path.join(derivedDir, `${docName}-derived.jsonld`);
console.log(`\nDeriving proof for: ${docName}`);
// Use a reasonable set of reveal pointers based on the credential type
const revealPointers = [
'/credentialSubject',
];
const documentContent = await fs.readFile(file, 'utf8');
const document = JSON.parse(documentContent);
const derivedDocument = await deriveProof({
document,
revealPointers,
documentLoaderContent: await getDocumentLoaderContent(options)
});
await fs.writeFile(outputFile, JSON.stringify(derivedDocument, null, 2));
console.log(`✓ Derived proof saved to: ${outputFile}`);
// Preprocess the derived proof if enabled
if (shouldPreprocess) {
console.log(`Preprocessing derived proof: ${docName}`);
const cidFile = cidFiles.find(f => f.includes(docName.split('-')[1]));
const cidContent = await fs.readFile(cidFile, 'utf8');
const cid = JSON.parse(cidContent);
const preprocessedData = await preprocessBBSVerification({
document: derivedDocument,
cid,
documentLoaderContent: await getDocumentLoaderContent(options)
});
const preprocessedFile = path.join(preprocessedDir, `${docName}-preprocessed.json`);
await fs.writeFile(preprocessedFile, JSON.stringify(preprocessedData, null, 2));
console.log(`✓ Preprocessed data saved to: ${preprocessedFile}`);
}
} catch (error) {
throw new Error(`Failed to derive proof for ${file}: ${error.message}`);
}
}
console.log('\n✓ All proofs derived successfully\n');
}
// Verify all generated documents
console.log('=== Verifying Generated Documents ===');
// Verify Ed25519 signatures
console.log('\nVerifying Ed25519 Signatures:');
const ed25519Files = await fs.readdir(path.join(baseOutputDir, 'ed25519'));
for (const file of ed25519Files) {
try {
const cidName = file.split('-')[1].split('.')[0]; // Extract CID name from filename
const cidFile = path.join(cidsDir, `${cidName}-cid.jsonld`);
const signedFile = path.join(baseOutputDir, 'ed25519', file);
console.log(`\nVerifying: ${file}`);
const cidContent = await fs.readFile(cidFile, 'utf8');
const cid = JSON.parse(cidContent);
const documentContent = await fs.readFile(signedFile, 'utf8');
const document = JSON.parse(documentContent);
const isValid = await verifyCredential({ cid, document, documentLoaderContent: await getDocumentLoaderContent(options) });
if (!isValid) {
throw new Error('Verification failed');
}
console.log('✓ Verification successful');
// Preprocess Ed25519 documents if enabled
if (shouldPreprocess) {
console.log(`Preprocessing Ed25519 document: ${file}`);
const preprocessedData = await preprocessEd25519Verification({
document,
cid,
documentLoaderContent: await getDocumentLoaderContent(options)
});
const preprocessedFile = path.join(ed25519PreprocessedDir, `${file}-preprocessed.json`);
await fs.writeFile(preprocessedFile, JSON.stringify(preprocessedData, null, 2));
console.log(`✓ Preprocessed data saved to: ${preprocessedFile}`);
}
} catch (error) {
throw new Error(`Failed to verify Ed25519 document ${file}: ${error.message}`);
}
}
// Verify derived BBS proofs
console.log('\nVerifying Derived BBS Proofs:');
const derivedFiles = await fs.readdir(path.join(baseOutputDir, 'derived'));
for (const file of derivedFiles) {
try {
const cidName = file.split('-')[1].split('.')[0]; // Extract CID name from filename
const cidFile = path.join(cidsDir, `${cidName}-cid.jsonld`);
const derivedFile = path.join(baseOutputDir, 'derived', file);
console.log(`\nVerifying: ${file}`);
const cidContent = await fs.readFile(cidFile, 'utf8');
const cid = JSON.parse(cidContent);
const documentContent = await fs.readFile(derivedFile, 'utf8');
const document = JSON.parse(documentContent);
const isValid = await verifyCredential({ cid, document, documentLoaderContent: await getDocumentLoaderContent(options) });
if (!isValid) {
throw new Error('Verification failed');
}
console.log('✓ Verification successful');
} catch (error) {
throw new Error(`Failed to verify derived BBS document ${file}: ${error.message}`);
}
}
console.log('\n✓ All documents verified successfully\n');
console.log('=== Generation Complete ===');
console.log('All files are organized in:', baseOutputDir);
console.log('- CIDs:', cidsDir);
console.log('- BBS Signatures:', bbsDir);
console.log('- Ed25519 Signatures:', ed25519Dir);
console.log('- Derived Credentials:', derivedDir);
if (shouldPreprocess) {
console.log('- Preprocessed Derived Credentials:', preprocessedDir);
console.log('- Preprocessed Ed25519 Credentials:', ed25519PreprocessedDir);
}
// If --collect flag is set, collect all files into a single Turtle file
if (options.collect) {
console.log('\n=== Collecting Generated Files ===');
const outputFile = path.join(baseOutputDir, 'collected.ttl');
await collectDocuments({
documents: [
...(await fs.readdir(cidsDir)).map(f => path.join(cidsDir, f)),
...(await fs.readdir(bbsDir)).map(f => path.join(bbsDir, f)),
...(await fs.readdir(ed25519Dir)).map(f => path.join(ed25519Dir, f)),
...(await fs.readdir(derivedDir)).map(f => path.join(derivedDir, f))
],
outputPath: outputFile,
documentLoaderContent: await getDocumentLoaderContent(options)
});
console.log(`✓ All files collected into: ${outputFile}`);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('collect')
.description('Collect JSON-LD documents into a single Turtle file, excluding proofs')
.requiredOption('-d, --directory <path>', 'Directory containing JSON-LD documents')
.requiredOption('-o, --output <path>', 'Output path for Turtle file (must end with .ttl)')
.option('--document-loader-content <path>', 'Path to JSON file containing predefined document loader responses')
.action(async (options) => {
try {
// Read all files in the directory
const files = await fs.readdir(options.directory, { recursive: true });
const jsonldFiles = files.filter(file => file.endsWith('.jsonld'))
.map(file => path.join(options.directory, file))
.filter(file => !file.includes('-cid.jsonld'));
if (jsonldFiles.length === 0) {
throw new Error('No JSON-LD files found in the specified directory');
}
await collectDocuments({
documents: jsonldFiles,
outputPath: options.output,
documentLoaderContent: await getDocumentLoaderContent(options)
});
console.log(`Successfully collected ${jsonldFiles.length} documents into ${options.output}`);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program.parse();