-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/discoverability 2 #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/discoverability
Are you sure you want to change the base?
Changes from all commits
d6f4d2f
b4fcce2
b980657
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| /** | ||
| * bootstrap-all.js | ||
| * | ||
| * Finds every workspace package whose "main" points to src/index.ts | ||
| * (meaning the CLI will try to transpile it on-the-fly), and compiles | ||
| * them with tsc directly so Node 24 can load them as plain CommonJS. | ||
| * | ||
| * Usage: node bootstrap-all.js | ||
| */ | ||
|
|
||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const { execSync } = require('child_process'); | ||
|
|
||
| const ROOT = __dirname; | ||
|
|
||
| // ── 1. Collect all workspace package dirs ────────────────────────────────── | ||
| function getWorkspaceDirs() { | ||
| const rootPkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')); | ||
| const patterns = rootPkg.workspaces?.packages || ['packages/*', 'plugins/*']; | ||
| const dirs = []; | ||
| for (const pattern of patterns) { | ||
| // We only handle simple globs like "packages/*" | ||
| const base = pattern.replace(/\/\*$/, ''); | ||
| const baseDir = path.join(ROOT, base); | ||
| if (!fs.existsSync(baseDir)) continue; | ||
| for (const entry of fs.readdirSync(baseDir)) { | ||
| const full = path.join(baseDir, entry); | ||
| if (fs.statSync(full).isDirectory()) dirs.push(full); | ||
| } | ||
| } | ||
| return dirs; | ||
| } | ||
|
|
||
| // ── 2. Detect packages that load from src at runtime ────────────────────── | ||
| function needsBootstrap(pkgDir) { | ||
| const pkgJsonPath = path.join(pkgDir, 'package.json'); | ||
| if (!fs.existsSync(pkgJsonPath)) return false; | ||
| const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); | ||
| return ( | ||
| typeof pkg.main === 'string' && pkg.main.startsWith('src/') | ||
| ); | ||
| } | ||
|
|
||
| // ── 3. Check if a package already has a compiled dist ───────────────────── | ||
| function hasCompiledDist(pkgDir) { | ||
| const distDir = path.join(pkgDir, 'dist'); | ||
| if (!fs.existsSync(distDir)) return false; | ||
| const files = fs.readdirSync(distDir); | ||
| // Look for any .js file in dist | ||
| return files.some(f => f.endsWith('.js')); | ||
| } | ||
|
|
||
| // ── 4. Compile a single package with tsc ────────────────────────────────── | ||
| function compilePkg(pkgDir, pkgName) { | ||
| const srcIndex = path.join(pkgDir, 'src', 'index.ts'); | ||
| if (!fs.existsSync(srcIndex)) { | ||
| console.log(` [SKIP] ${pkgName} — no src/index.ts found`); | ||
| return false; | ||
| } | ||
|
|
||
| const distDir = path.join(pkgDir, 'dist'); | ||
| if (!fs.existsSync(distDir)) fs.mkdirSync(distDir, { recursive: true }); | ||
|
|
||
| try { | ||
| execSync( | ||
| [ | ||
| 'npx tsc', | ||
| `"${srcIndex}"`, | ||
| '--outDir', `"${distDir}"`, | ||
| '--module', 'commonjs', | ||
| '--target', 'es2019', | ||
| '--moduleResolution', 'node', | ||
| '--esModuleInterop', | ||
| '--allowSyntheticDefaultImports', | ||
| '--declaration', | ||
| '--skipLibCheck', | ||
| '--noEmit', 'false', | ||
| ].join(' '), | ||
| { cwd: pkgDir, stdio: 'pipe' } | ||
| ); | ||
|
|
||
| // tsc puts the file at dist/src/index.js — flatten it to dist/index.cjs.js | ||
| const tscOut = path.join(distDir, 'src', 'index.js'); | ||
| const tscOutFlat = path.join(distDir, 'index.js'); | ||
| const finalOut = path.join(distDir, 'index.cjs.js'); | ||
|
|
||
| if (fs.existsSync(tscOut)) { | ||
| fs.renameSync(tscOut, finalOut); | ||
| // Clean up the empty dist/src directory | ||
| try { fs.rmdirSync(path.join(distDir, 'src'), { recursive: true }); } catch {} | ||
| } else if (fs.existsSync(tscOutFlat)) { | ||
| fs.renameSync(tscOutFlat, finalOut); | ||
| } else { | ||
| // tsc compiled everything into dist — just find the first .js file | ||
| const js = fs.readdirSync(distDir).find(f => f.endsWith('.js')); | ||
| if (js && js !== 'index.cjs.js') { | ||
| fs.copyFileSync(path.join(distDir, js), finalOut); | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } catch (err) { | ||
| // tsc might print type errors but still emit — check if we got output | ||
| const tscOut = path.join(distDir, 'src', 'index.js'); | ||
| const finalOut = path.join(distDir, 'index.cjs.js'); | ||
|
|
||
| if (fs.existsSync(tscOut)) { | ||
| fs.renameSync(tscOut, finalOut); | ||
| try { fs.rmdirSync(path.join(distDir, 'src'), { recursive: true }); } catch {} | ||
| return true; | ||
| } | ||
| if (fs.existsSync(finalOut)) return true; | ||
|
|
||
| console.log(` [WARN] ${pkgName} — tsc failed, skipping`); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| // ── 5. Update package.json "main" to point at dist ──────────────────────── | ||
| function patchPackageJson(pkgDir, pkgName) { | ||
| const pkgJsonPath = path.join(pkgDir, 'package.json'); | ||
| const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); | ||
|
|
||
| const distCjs = path.join(pkgDir, 'dist', 'index.cjs.js'); | ||
| if (!fs.existsSync(distCjs)) { | ||
| console.log(` [SKIP patch] ${pkgName} — no dist/index.cjs.js`); | ||
| return; | ||
| } | ||
|
|
||
| if (pkg.main !== 'dist/index.cjs.js') { | ||
| pkg.main = 'dist/index.cjs.js'; | ||
| fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8'); | ||
| console.log(` [PATCHED] ${pkgName} — main → dist/index.cjs.js`); | ||
| } | ||
| } | ||
|
|
||
| // ── Main ─────────────────────────────────────────────────────────────────── | ||
| console.log('=== Backstage Node 24 Bootstrap ===\n'); | ||
| console.log('Scanning workspace packages...\n'); | ||
|
|
||
| const allDirs = getWorkspaceDirs(); | ||
| const toBootstrap = allDirs.filter(needsBootstrap); | ||
|
|
||
| console.log(`Found ${toBootstrap.length} packages with main="src/..." that need bootstrapping.\n`); | ||
|
|
||
| let compiled = 0; | ||
| let skipped = 0; | ||
| let already = 0; | ||
|
|
||
| for (const dir of toBootstrap) { | ||
| const pkgName = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).name; | ||
|
|
||
| if (hasCompiledDist(dir)) { | ||
| patchPackageJson(dir, pkgName); | ||
| already++; | ||
| continue; | ||
| } | ||
|
|
||
| process.stdout.write(`Compiling ${pkgName}...`); | ||
| const ok = compilePkg(dir, pkgName); | ||
| if (ok) { | ||
| process.stdout.write(' ✓\n'); | ||
| patchPackageJson(dir, pkgName); | ||
| compiled++; | ||
| } else { | ||
| process.stdout.write(' ✗ (skipped)\n'); | ||
| skipped++; | ||
| } | ||
| } | ||
|
|
||
| console.log(`\n=== Done ===`); | ||
| console.log(` Already compiled: ${already}`); | ||
| console.log(` Newly compiled: ${compiled}`); | ||
| console.log(` Skipped/failed: ${skipped}`); | ||
| console.log('\nYou can now run:'); | ||
| console.log(' yarn workspace @rk-apim/backstage-plugin-wso2-api-manager build'); | ||
| console.log(' yarn workspace @rk-apim/backstage-plugin-wso2-api-manager-backend build'); | ||
| console.log(' yarn workspace @rk-apim/backstage-plugin-catalog-backend-module-wso2-apim build'); | ||
|
Comment on lines
+177
to
+179
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bootstrap instructions still point to the old workspace scope. The commands at Lines [177]-[179] still use 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,27 @@ | ||||||||||||||||
| const { execSync } = require('child_process'); | ||||||||||||||||
| const fs = require('fs'); | ||||||||||||||||
| const path = require('path'); | ||||||||||||||||
|
|
||||||||||||||||
| const errorsDir = path.join(__dirname, 'packages', 'errors'); | ||||||||||||||||
|
|
||||||||||||||||
| console.log('Compiling @backstage/errors...'); | ||||||||||||||||
| try { | ||||||||||||||||
| // Use tsc to compile to CommonJS | ||||||||||||||||
| execSync('npx tsc src/index.ts --outDir dist --module commonjs --target es2019 --esModuleInterop --skipLibCheck', { | ||||||||||||||||
| cwd: errorsDir, | ||||||||||||||||
| stdio: 'inherit' | ||||||||||||||||
| }); | ||||||||||||||||
|
|
||||||||||||||||
| // Rename index.js to index.cjs.js to match package.json "main" | ||||||||||||||||
| const distIndex = path.join(errorsDir, 'dist', 'index.js'); | ||||||||||||||||
| const targetIndex = path.join(errorsDir, 'dist', 'index.cjs.js'); | ||||||||||||||||
|
|
||||||||||||||||
| if (fs.existsSync(distIndex)) { | ||||||||||||||||
| fs.renameSync(distIndex, targetIndex); | ||||||||||||||||
| console.log('Successfully compiled and mapped @backstage/errors to CommonJS!'); | ||||||||||||||||
| } else { | ||||||||||||||||
| console.error('tsc finished but dist/index.js was not found.'); | ||||||||||||||||
| } | ||||||||||||||||
| } catch (e) { | ||||||||||||||||
| console.error('Failed to compile @backstage/errors:', e.message); | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+25
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Script exits successfully even on compilation failure. When 🐛 Proposed fix } catch (e) {
console.error('Failed to compile `@backstage/errors`:', e.message);
+ process.exit(1);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| /** | ||
| * Compiles the core Backstage packages that the CLI needs to boot, | ||
| * using esbuild which handles all TS/directory imports automatically. | ||
| */ | ||
| const path = require('path'); | ||
| const fs = require('fs'); | ||
|
|
||
| // Try to load esbuild from various locations | ||
| let esbuild; | ||
| const candidates = [ | ||
| './node_modules/esbuild', | ||
| './packages/cli/node_modules/esbuild', | ||
| ]; | ||
| for (const c of candidates) { | ||
| try { esbuild = require(c); break; } catch {} | ||
| } | ||
| if (!esbuild) { | ||
| console.error('esbuild not found. Run: yarn add -W esbuild'); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const ROOT = __dirname; | ||
|
|
||
| // Packages that need to be compiled for the CLI to boot. | ||
| // Order matters: compile leaves first. | ||
| const PACKAGES = [ | ||
| { dir: 'packages/types', externals: [] }, | ||
| { dir: 'packages/errors', externals: ['@backstage/types', 'serialize-error'] }, | ||
| { dir: 'packages/config', externals: ['@backstage/types', '@backstage/errors', 'ms'] }, | ||
| { dir: 'packages/cli-common', externals: ['@backstage/*'] }, | ||
| { dir: 'packages/cli-node', externals: ['@backstage/*', 'fs-extra', 'semver', 'chalk', 'ora', 'minimatch', 'tar'] }, | ||
| { dir: 'packages/release-manifests', externals: ['@backstage/*'] }, | ||
| { dir: 'packages/integration', externals: ['@backstage/*', 'node-fetch', 'minimatch', 'parse-url', 'js-yaml', '@octokit/*'] }, | ||
| { dir: 'packages/config-loader', externals: ['@backstage/*', 'js-yaml', 'json-schema', 'minimatch', 'fs-extra'] }, | ||
| { dir: 'packages/catalog-model', externals: ['@backstage/*', 'ajv', 'js-yaml', 'lodash', 'json-schema-traverse'] }, | ||
| ]; | ||
|
|
||
| async function buildPkg({ dir, externals }) { | ||
| const pkgDir = path.join(ROOT, dir); | ||
| const pkgJsonPath = path.join(pkgDir, 'package.json'); | ||
| const srcIndex = path.join(pkgDir, 'src', 'index.ts'); | ||
|
|
||
| if (!fs.existsSync(srcIndex)) { | ||
| console.log(`[SKIP] ${dir} — no src/index.ts`); | ||
| return; | ||
| } | ||
|
|
||
| const distDir = path.join(pkgDir, 'dist'); | ||
| if (!fs.existsSync(distDir)) fs.mkdirSync(distDir, { recursive: true }); | ||
|
|
||
| const outfile = path.join(distDir, 'index.cjs.js'); | ||
|
|
||
| // Build all external patterns | ||
| const allExternals = [ | ||
| ...externals, | ||
| 'node:*', | ||
| ]; | ||
|
|
||
| try { | ||
| await esbuild.build({ | ||
| entryPoints: [srcIndex], | ||
| bundle: true, | ||
| platform: 'node', | ||
| format: 'cjs', | ||
| outfile, | ||
| external: allExternals, | ||
| logLevel: 'silent', | ||
| // Allow .ts files in paths | ||
| resolveExtensions: ['.ts', '.tsx', '.js', '.jsx', '.json'], | ||
| }); | ||
|
|
||
| // Patch package.json | ||
| const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); | ||
| const changed = pkg.main !== 'dist/index.cjs.js'; | ||
| pkg.main = 'dist/index.cjs.js'; | ||
| if (changed) { | ||
| fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2) + '\n'); | ||
| } | ||
|
|
||
| console.log(`[OK] ${dir}`); | ||
| } catch (err) { | ||
| console.error(`[FAIL] ${dir}: ${err.message.split('\n')[0]}`); | ||
| } | ||
|
Comment on lines
+81
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Propagate build failures to a non-zero process exit. If Suggested fix async function buildPkg({ dir, externals }) {
@@
try {
@@
console.log(`[OK] ${dir}`);
+ return true;
} catch (err) {
console.error(`[FAIL] ${dir}: ${err.message.split('\n')[0]}`);
+ return false;
}
}
(async () => {
console.log('Building core packages with esbuild...\n');
+ let failed = 0;
for (const pkg of PACKAGES) {
- await buildPkg(pkg);
+ const ok = await buildPkg(pkg);
+ if (!ok) failed += 1;
}
+ if (failed > 0) process.exitCode = 1;
console.log('\nDone! Now run:');Also applies to: 86-93 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| (async () => { | ||
| console.log('Building core packages with esbuild...\n'); | ||
| for (const pkg of PACKAGES) { | ||
| await buildPkg(pkg); | ||
| } | ||
| console.log('\nDone! Now run:'); | ||
| console.log(' yarn workspace @rk-apim/backstage-plugin-wso2-api-manager build'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Post-run command still uses old package scope. The workspace name at Line [92] still uses 🤖 Prompt for AI Agents |
||
| })(); | ||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compiled-dist detection can incorrectly skip needed builds.
hasCompiledDistchecks for any.jsfile, but bootstrapping needsdist/index.cjs.js. A package with unrelated dist JS gets counted as “already compiled” and bypasses compile.Suggested fix
function hasCompiledDist(pkgDir) { const distDir = path.join(pkgDir, 'dist'); if (!fs.existsSync(distDir)) return false; - const files = fs.readdirSync(distDir); - // Look for any .js file in dist - return files.some(f => f.endsWith('.js')); + return ( + fs.existsSync(path.join(distDir, 'index.cjs.js')) || + fs.existsSync(path.join(distDir, 'index.js')) || + fs.existsSync(path.join(distDir, 'src', 'index.js')) + ); }Also applies to: 154-157
🤖 Prompt for AI Agents