import { TestFileProvider } from './classes.testfileprovider.js'; import * as plugins from './plugins.js'; class TapNodeTools { private smartshellInstance: plugins.smartshell.Smartshell; private smartnetworkInstance: plugins.smartnetwork.SmartNetwork; public testFileProvider = new TestFileProvider(); constructor() {} private qenv: plugins.qenv.Qenv; public async getQenv(): Promise { this.qenv = this.qenv || new plugins.qenv.Qenv('./', '.nogit/'); return this.qenv; } public async getEnvVarOnDemand(envVarNameArg: string): Promise { const qenv = await this.getQenv(); return qenv.getEnvVarOnDemand(envVarNameArg); } public async runCommand(commandArg: string): Promise { if (!this.smartshellInstance) { this.smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash', }); } const result = await this.smartshellInstance.exec(commandArg); return result; } public async createHttpsCert( commonName: string = 'localhost', allowSelfSigned: boolean = true ): Promise<{ key: string; cert: string }> { if (allowSelfSigned) { // set node to allow self-signed certificates process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; } // Generate a key pair const keys = plugins.smartcrypto.nodeForge.pki.rsa.generateKeyPair(2048); // Create a self-signed certificate const cert = plugins.smartcrypto.nodeForge.pki.createCertificate(); cert.publicKey = keys.publicKey; cert.serialNumber = '01'; cert.validity.notBefore = new Date(); cert.validity.notAfter = new Date(); cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); const attrs = [ { name: 'commonName', value: commonName }, { name: 'countryName', value: 'US' }, { shortName: 'ST', value: 'California' }, { name: 'localityName', value: 'San Francisco' }, { name: 'organizationName', value: 'My Company' }, { shortName: 'OU', value: 'Dev' }, ]; cert.setSubject(attrs); cert.setIssuer(attrs); // Sign the certificate with its own private key (self-signed) cert.sign(keys.privateKey, plugins.smartcrypto.nodeForge.md.sha256.create()); // PEM encode the private key and certificate const pemKey = plugins.smartcrypto.nodeForge.pki.privateKeyToPem(keys.privateKey); const pemCert = plugins.smartcrypto.nodeForge.pki.certificateToPem(cert); return { key: pemKey, cert: pemCert, }; } /** * create and return a smartmongo instance */ public async createSmartmongo() { const smartmongoMod = await import('@push.rocks/smartmongo'); const smartmongoInstance = new smartmongoMod.SmartMongo(); await smartmongoInstance.start(); return smartmongoInstance; } /** * create and return a smartstorage instance */ public async createSmartStorage() { const smartstorageMod = await import('@push.rocks/smartstorage'); const smartstorageInstance = await smartstorageMod.SmartStorage.createAndStart({ server: { port: 3003 }, storage: { cleanSlate: true }, }); return smartstorageInstance; } // ============ // Network Tools // ============ private getSmartNetwork(): plugins.smartnetwork.SmartNetwork { if (!this.smartnetworkInstance) { this.smartnetworkInstance = new plugins.smartnetwork.SmartNetwork(); } return this.smartnetworkInstance; } /** * Find a single free port on the local machine. */ public async findFreePort(optionsArg?: { startPort?: number; endPort?: number; randomize?: boolean; exclude?: number[]; }): Promise { const options = { startPort: 3000, endPort: 60000, randomize: true, exclude: [] as number[], ...optionsArg, }; const smartnetwork = this.getSmartNetwork(); const port = await smartnetwork.findFreePort(options.startPort, options.endPort, { randomize: options.randomize, exclude: options.exclude, }); if (!port) { throw new Error( `Could not find a free port in range ${options.startPort}-${options.endPort}` ); } return port; } /** * Find multiple distinct free ports on the local machine. * Each found port is automatically excluded from subsequent searches. */ public async findFreePorts(countArg: number, optionsArg?: { startPort?: number; endPort?: number; randomize?: boolean; exclude?: number[]; }): Promise { const options = { startPort: 3000, endPort: 60000, randomize: true, exclude: [] as number[], ...optionsArg, }; const smartnetwork = this.getSmartNetwork(); const ports: number[] = []; const excluded = new Set(options.exclude); for (let i = 0; i < countArg; i++) { const port = await smartnetwork.findFreePort(options.startPort, options.endPort, { randomize: options.randomize, exclude: [...excluded], }); if (!port) { throw new Error( `Could only find ${ports.length} of ${countArg} free ports in range ${options.startPort}-${options.endPort}` ); } ports.push(port); excluded.add(port); } return ports; } /** * Find a range of consecutive free ports on the local machine. * All returned ports are sequential (e.g., [4000, 4001, 4002]). */ public async findFreePortRange(countArg: number, optionsArg?: { startPort?: number; endPort?: number; exclude?: number[]; }): Promise { const options = { startPort: 3000, endPort: 60000, exclude: [] as number[], ...optionsArg, }; const smartnetwork = this.getSmartNetwork(); const excludeSet = new Set(options.exclude); for (let start = options.startPort; start <= options.endPort - countArg + 1; start++) { let allFree = true; for (let offset = 0; offset < countArg; offset++) { const port = start + offset; if (excludeSet.has(port)) { allFree = false; start = port; // skip ahead past excluded port break; } const isFree = await smartnetwork.isLocalPortUnused(port); if (!isFree) { allFree = false; start = port; // skip ahead past occupied port break; } } if (allFree) { const ports: number[] = []; for (let offset = 0; offset < countArg; offset++) { ports.push(start + offset); } return ports; } } throw new Error( `Could not find ${countArg} consecutive free ports in range ${options.startPort}-${options.endPort}` ); } } export const tapNodeTools = new TapNodeTools();