Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions bootstrap-all.js
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'));
}
Comment on lines +46 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Compiled-dist detection can incorrectly skip needed builds.

hasCompiledDist checks for any .js file, but bootstrapping needs dist/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
Verify each finding against the current code and only fix it if needed.

In `@bootstrap-all.js` around lines 46 - 52, The current hasCompiledDist function
incorrectly treats any .js in dist as a compiled package; change it to check
specifically for the expected entry file (dist/index.cjs.js) by testing
existence of path.join(pkgDir, 'dist', 'index.cjs.js') (or equivalent direct
file check) instead of scanning for any .js; update the other duplicated
detection logic elsewhere in the file to the same explicit check so packages
with unrelated JS files don’t bypass compilation.


// ── 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Bootstrap instructions still point to the old workspace scope.

The commands at Lines [177]-[179] still use @rk-apim/...; after this rename they should use @rk-apim-1/....

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bootstrap-all.js` around lines 177 - 179, Bootstrap print statements still
reference the old workspace scope '@rk-apim'; update the three console.log
strings that mention '@rk-apim/backstage-plugin-wso2-api-manager',
'@rk-apim/backstage-plugin-wso2-api-manager-backend', and
'@rk-apim/backstage-plugin-catalog-backend-module-wso2-apim' to use the new
scope '@rk-apim-1' so the printed yarn workspace commands reflect the renamed
packages.

27 changes: 27 additions & 0 deletions bootstrap-errors.js
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Script exits successfully even on compilation failure.

When tsc throws an error, the catch block logs the message but doesn't set a non-zero exit code. This could cause CI pipelines to proceed despite the build failing.

🐛 Proposed fix
 } catch (e) {
   console.error('Failed to compile `@backstage/errors`:', e.message);
+  process.exit(1);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (e) {
console.error('Failed to compile @backstage/errors:', e.message);
}
} catch (e) {
console.error('Failed to compile `@backstage/errors`:', e.message);
process.exit(1);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bootstrap-errors.js` around lines 25 - 27, The catch block that handles tsc
compilation errors currently logs the error via console.error in the catch (e)
handler but does not signal failure to the process; update the error handler
(the catch (e) block around the tsc compile call in bootstrap-errors.js) to set
a non-zero exit status (e.g., set process.exitCode = 1 or call process.exit(1))
after logging the error so CI and callers observe the failure.

93 changes: 93 additions & 0 deletions fix-core-packages.js
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate build failures to a non-zero process exit.

If esbuild.build fails, the script only logs and continues, so CI/local automation can report success despite failed bootstrapping.

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
Verify each finding against the current code and only fix it if needed.

In `@fix-core-packages.js` around lines 81 - 83, The catch blocks that currently
only log errors (e.g., the catch using "err" that calls console.error(`[FAIL]
${dir}: ${err.message.split('\n')[0]}`) after esbuild.build) must cause a
non-zero process exit so failures propagate to CI; update those catch handlers
to either rethrow the error or call process.exit(1) after logging (and ensure
any async top-level callers await/handle rethrown errors), and apply the same
change to the other catch blocks in this file (the one covering lines 86-93) so
any esbuild.build or related build failures terminate the process with a
non-zero exit code.

}

(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');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Post-run command still uses old package scope.

The workspace name at Line [92] still uses @rk-apim/...; this PR switched to @rk-apim-1/..., so this instruction is now misleading.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fix-core-packages.js` at line 92, The post-run instruction string printed by
console.log still references the old package scope
"@rk-apim/backstage-plugin-wso2-api-manager"; update the logged message in the
console.log call to use the new scope
"@rk-apim-1/backstage-plugin-wso2-api-manager" so the user sees the correct yarn
workspace command to run after the script completes.

})();
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
"@octokit/rest": "^19.0.3",
"@rk-apim/backstage-plugin-wso2-api-manager": "workspace:^",
"@rk-apim-1/backstage-plugin-wso2-api-manager": "workspace:^",
"history": "^5.0.0",
"react": "^18.0.2",
"react-dom": "^18.0.2",
Expand Down
4 changes: 1 addition & 3 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,8 @@ import {
import { CustomizableHomePage } from './components/home/CustomizableHomePage';
import { HomePage } from './components/home/HomePage';
import { BuiThemerPage } from '@backstage/plugin-mui-to-bui';
import { Wso2ApiManagerPage } from '@rk-apim/backstage-plugin-wso2-api-manager';
import { Wso2ApiManagerPage } from '@rk-apim-1/backstage-plugin-wso2-api-manager';
import { asgardeoAuthApiRef } from './apis';
import { PermissionTestPage } from './components/PermissionTestPage';

const app = createApp({
apis,
Expand Down Expand Up @@ -216,7 +215,6 @@ const routes = (
</Route>
<Route path="/notifications" element={<NotificationsPage />} />
<Route path="/mui-to-bui" element={<BuiThemerPage />} />
<Route path="/test-permissions" element={<PermissionTestPage />} />
</FlatRoutes>
);

Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
Wso2ApiManagerClient,
wso2ApiManagerApiRef,
wso2AuthApiRef,
} from '@rk-apim/backstage-plugin-wso2-api-manager';
} from '@rk-apim-1/backstage-plugin-wso2-api-manager';

export const asgardeoAuthApiRef: ApiRef<
OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
Expand Down
36 changes: 0 additions & 36 deletions packages/app/src/components/PermissionTestPage.tsx

This file was deleted.

5 changes: 0 additions & 5 deletions packages/app/src/components/Root/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,6 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
text="APIs"
/>
<SidebarItem icon={CloudQueueIcon} to="wso2" text="WSO2 APIM" />
<SidebarItem
icon={useApp().getSystemIcon('kind:user')!}
to="test-permissions"
text="Test Permissions"
/>

<SidebarItem
icon={useApp().getSystemIcon('docs')!}
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/components/catalog/EntityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import {
isWso2Api,
isMcpEntity,
hasMultipleComponentRelations,
} from '@rk-apim/backstage-plugin-wso2-api-manager';
} from '@rk-apim-1/backstage-plugin-wso2-api-manager';

import {
Direction,
Expand Down
Loading