diff --git a/.gitignore b/.gitignore index 4173650..5a67c61 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ build/ packages/frontend-core/control/ *.tsbuildinfo +# Auto-generated sources (created during packages/frontend-core prebuild) +packages/frontend-core/src/webusb/webUsbDevices.autogen.ts + # Environment variables .env .env.local diff --git a/packages/frontend-core/scripts/generate-webusb-registrations.js b/packages/frontend-core/scripts/generate-webusb-registrations.js new file mode 100644 index 0000000..a37d43a --- /dev/null +++ b/packages/frontend-core/scripts/generate-webusb-registrations.js @@ -0,0 +1,105 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const pkgRoot = path.resolve(__dirname, '..'); +const webusbSrcDir = path.join(pkgRoot, 'src', 'webusb'); + +const GENERATED_BASENAME = 'webUsbDevices.autogen.ts'; +const GENERATED_PATH = path.join(webusbSrcDir, GENERATED_BASENAME); + +function walk(dir) { + /** @type {string[]} */ + const results = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + // skip templates and other non-code folders by default + if (entry.name === 'templates') continue; + results.push(...walk(full)); + } else { + results.push(full); + } + } + return results; +} + +function toPosix(p) { + return p.split(path.sep).join('/'); +} + +function writeIfChanged(filePath, content) { + if (fs.existsSync(filePath)) { + const current = fs.readFileSync(filePath, 'utf8'); + if (current === content) return false; + } + fs.writeFileSync(filePath, content, 'utf8'); + return true; +} + +export function generateWebUsbRegistrations() { + if (!fs.existsSync(webusbSrcDir)) { + console.warn(`[webusb-autogen] Directory not found: ${webusbSrcDir}`); + return; + } + + const candidates = walk(webusbSrcDir) + .filter(f => f.endsWith('.ts')) + .filter(f => !f.endsWith('.d.ts')) + .filter(f => path.basename(f) !== 'index.ts') + // webUsb.ts defines registerWebUsbInstance; it should not be included + // in the auto-registration imports list. + .filter(f => path.basename(f) !== 'webUsb.ts') + .filter(f => path.basename(f) !== GENERATED_BASENAME); + + const importSpecifiers = []; + const matcher = /\bregisterWebUsbInstance\s*\(/; + + for (const filePath of candidates) { + const text = fs.readFileSync(filePath, 'utf8'); + if (!matcher.test(text)) continue; + // Avoid false positives from declarations like `function registerWebUsbInstance(...)`. + if (/\bexport\s+function\s+registerWebUsbInstance\b|\bfunction\s+registerWebUsbInstance\b/.test(text)) continue; + + const relFromWebusb = path.relative(webusbSrcDir, filePath); + const noExt = relFromWebusb.replace(/\.ts$/, ''); + const spec = './' + toPosix(noExt); + importSpecifiers.push(spec); + } + + importSpecifiers.sort(); + + const header = [ + '/*', + ' * AUTO-GENERATED FILE - DO NOT EDIT.', + ' *', + ' * Generated by: packages/frontend-core/scripts/generate-webusb-registrations.js', + ' * Purpose: import all WebUSB device modules that self-register via registerWebUsbInstance(...)', + ' */', + '', + ].join('\n'); + + const body = importSpecifiers.length + ? importSpecifiers.map(s => `import '${s}';`).join('\n') + '\n' + : '// No WebUSB device modules with registerWebUsbInstance(...) were found.\n'; + + const content = header + body; + + const changed = writeIfChanged(GENERATED_PATH, content); + const msg = changed ? 'updated' : 'up-to-date'; + console.log(`[webusb-autogen] ${msg}: src/webusb/${GENERATED_BASENAME} (${importSpecifiers.length} imports)`); +} + +// Allow running directly: `node scripts/generate-webusb-registrations.js` +if (import.meta.url === `file://${__filename}`) { + try { + generateWebUsbRegistrations(); + process.exit(0); + } catch (e) { + console.error(e); + process.exit(1); + } +} diff --git a/packages/frontend-core/scripts/prebuild.js b/packages/frontend-core/scripts/prebuild.js index c1c4ca9..9a0c346 100644 --- a/packages/frontend-core/scripts/prebuild.js +++ b/packages/frontend-core/scripts/prebuild.js @@ -1,6 +1,7 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; +import { generateWebUsbRegistrations } from './generate-webusb-registrations.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -21,6 +22,7 @@ function copyDir(src, dest, skipFiles = []) { (function main() { try { + generateWebUsbRegistrations(); console.log('Copying control files...'); copyDir(path.join(pkgRoot, 'src', 'control'), path.join(pkgRoot, 'dist', 'control'), ['pre.js']); process.exit(0); diff --git a/packages/frontend-core/src/webusb/index.ts b/packages/frontend-core/src/webusb/index.ts index 0db9fe0..a276265 100644 --- a/packages/frontend-core/src/webusb/index.ts +++ b/packages/frontend-core/src/webusb/index.ts @@ -1,3 +1,7 @@ +// Auto-register built-in WebUSB device implementations. +// This file is generated at build time by scripts/prebuild.js. +import './webUsbDevices.autogen'; + // Re-exporting all types from WebUsb module export { WebUsbChannels, ControlWebUsb, ControlWebUsbInitialParams diff --git a/test-apps/package.json b/test-apps/package.json index 5126cd2..f8a3bbd 100644 --- a/test-apps/package.json +++ b/test-apps/package.json @@ -10,7 +10,7 @@ "@websdr/frontend-core": "file:../packages/frontend-core" }, "devDependencies": { - "tsx": "^4.7.0", + "tsx": "^4.21.0", "typescript": "^5.9.2" } -} \ No newline at end of file +}