Skip to content

feat: support predefined context and skip postbuild / skip jars#921

Closed
zapkub wants to merge 1 commit intokeycloakify:mainfrom
zapkub:rick/pr-predefined-context
Closed

feat: support predefined context and skip postbuild / skip jars#921
zapkub wants to merge 1 commit intokeycloakify:mainfrom
zapkub:rick/pr-predefined-context

Conversation

@zapkub
Copy link
Copy Markdown

@zapkub zapkub commented Sep 8, 2025

Support build in non npm, npx build system by introduce several options to run build command to avoid child_process execution.

Mostly the subprocesses are used inside build context factory function. This PR introduce a predefined result of the build context factory function.

  • Support predefined build context via env var KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT
  • Add --skip-post-build option to skip post build sub process
  • Add --skip-jar option to skip mvn packing

Test

I have tested with the script below to make sure external sub process won't be
invoked during the build

#!/bin/bash

set -e

export KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT=$(./node_modules/.bin/keycloakify print-build-context)

npx() {
    echo "npx is forbidden"
    exit 1
}
export -f npx

npm() {
    echo "npm is forbidden"
    exit 1
}
export -f npm

mvn() {
    echo "mvn is forbidden"
    exit 1
}
export -f mvn

./node_modules/.bin/vite build
./node_modules/.bin/keycloakify build --skip-jar --skip-post-build

Summary by CodeRabbit

  • New Features
    • Added CLI flags: --skip-jar to skip JAR generation and related checks, and --skip-post-build to skip post-build steps.
    • Introduced print-build-context command to output the project's build context as JSON for external build runners.
    • Added support for KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT env var to preload a build context without computing it.
    • Improved user-facing logs, clearly indicating when JAR building or post-build steps are skipped.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 8, 2025

Walkthrough

Adds CLI flags to skip JAR generation and post-build steps, updates build flow to honor these flags, introduces a print-build-context command, and enables an environment variable to supply a predefined BuildContext. Includes corresponding logs, conditional Maven checks, and a new exported constant for the env key.

Changes

Cohort / File(s) Summary
CLI flags and new command
src/bin/main.ts
Adds --skip-jar and --skip-post-build options to build command and forwards them. Introduces print-build-context command that outputs JSON BuildContext via getBuildContext.
Build flow conditionals
src/bin/keycloakify/keycloakify.ts
Extends command signature with skipJar? and skipPostBuild. Skips Maven check when skipJar. Adds branches to skip post-build and JAR build with user-facing logs; preserves previous default behavior otherwise.
BuildContext env override
src/bin/shared/buildContext.ts, src/bin/shared/constants.ts
Exports KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT constant. getBuildContext returns JSON-parsed context from this env var when set; otherwise runs existing computation.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant CLI as CLI (main.ts)
  participant BuildCmd as command() (keycloakify.ts)
  participant Maven as Maven
  participant Post as Post-build Script
  participant Jar as JAR Builder

  User->>CLI: keycloakify build [--skip-jar] [--skip-post-build]
  CLI->>BuildCmd: command({buildContext, skipJar, skipPostBuild})

  alt skipJar == true
    Note over BuildCmd: Maven check bypassed
  else
    BuildCmd->>Maven: verify/install
    Maven-->>BuildCmd: ok/fail
  end

  alt skipPostBuild == true
    Note over BuildCmd: Post-build skipped
  else
    BuildCmd->>Post: run_post_build_script()
    Post-->>BuildCmd: done
  end

  alt skipJar == true
    Note over BuildCmd: JAR build skipped (resources kept)
  else
    BuildCmd->>Jar: buildJars()
    Jar-->>BuildCmd: jar files
    BuildCmd->>BuildCmd: remove resources dir
  end

  BuildCmd-->>CLI: complete
  CLI-->>User: exit
Loading
sequenceDiagram
  autonumber
  actor User
  participant CLI as CLI (main.ts)
  participant Ctx as getBuildContext()
  User->>CLI: keycloakify print-build-context --project-dir
  CLI->>Ctx: getBuildContext({ projectDirPath })
  alt Env var set
    Ctx->>Ctx: parse process.env.KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT
  else
    Ctx->>Ctx: compute context from project
  end
  Ctx-->>CLI: BuildContext
  CLI-->>User: JSON(BuildContext) to stdout
Loading
sequenceDiagram
  autonumber
  participant Caller
  participant Ctx as getBuildContext()
  Caller->>Ctx: getBuildContext(...)
  alt KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT present
    Note over Ctx: Short-circuit with env JSON
  else
    Note over Ctx: Use existing derivation logic
  end
  Ctx-->>Caller: BuildContext
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Tokyo #866 — Touches the same CLI and build-context areas (main.ts, buildContext.ts), likely overlapping in command options or context handling.

Poem

I thump my paws on terminal ground,
Flags skip the jars; no Maven found.
A context printed, neat and bright—
Env vars whisper in the night.
Post-build naps? I’ll gladly take—
Hippity-hop, ship without bake! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bin/keycloakify/keycloakify.ts (1)

53-63: Fix incorrect Maven install commands in help text.
The suggested package names should be “maven”, not “mvn”.

Apply this diff:

-            const installationCommand = (() => {
+            const installationCommand = (() => {
                 switch (os.platform()) {
                     case "darwin":
-                        return "brew install mvn";
+                        return "brew install maven";
                     case "win32":
-                        return "choco install mvn";
+                        return "choco install maven";
                     case "linux":
                     default:
-                        return "sudo apt-get install mvn";
+                        return "sudo apt-get install maven";
                 }
             })();
🧹 Nitpick comments (5)
src/bin/shared/constants.ts (1)

91-91: Name and placement LGTM; add a brief doc for discoverability.
A short JSDoc helps users know how to populate this env var.

Apply this diff:

-export const KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT = "KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT";
+/** JSON string from `keycloakify print-build-context` used to bypass subprocesses in getBuildContext */
+export const KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT = "KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT";
src/bin/keycloakify/keycloakify.ts (1)

110-117: Make the skip-post-build guidance name the exact env var.
Users see which variable to set without guessing.

Apply this diff:

-            console.log(
-                chalk.yellow(
-                    `⚠️  --skip-post-build passed. Skipping the post build script step. (run postbuild manually with RUN_POST_BUILD_SCRIPT env var)`
-                )
-            );
+            console.log(
+                chalk.yellow(
+                    `⚠️  --skip-post-build passed. Skipping the post-build script. ` +
+                    `You can run it manually by setting ${VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.RUN_POST_BUILD_SCRIPT}.`
+                )
+            );
src/bin/main.ts (3)

71-90: Optional: add short aliases and keep option naming consistent

Consider short flags for ergonomics and to align with other options that expose short names (e.g., -p for project). Example: -J for --skip-jar and -B for --skip-post-build. Push both long and short into optionsKeys to keep the unknown-option guard working.

Apply this diff to both options:

 .option({
   key: "skipJar",
-  name: (() => {
-      const name = "skip-jar";
-      optionsKeys.push(name);
-      return name;
-  })(),
+  name: (() => {
+      const long = "skip-jar";
+      const short = "J";
+      optionsKeys.push(long, short);
+      return { long, short };
+  })(),
   description: "Skip output JAR file generation.",
   defaultValue: false
 })
 .option({
   key: "skipPostBuild",
-  name: (() => {
-      const name = "skip-post-build";
-      optionsKeys.push(name);
-      return name;
-  })(),
+  name: (() => {
+      const long = "skip-post-build";
+      const short = "B";
+      optionsKeys.push(long, short);
+      return { long, short };
+  })(),
   description: "Skip post-build steps.",
   defaultValue: false
 })

403-403: Nit: polish the help text

Minor grammar/style improvement.

-    description: "print a build context for non npx build runners. pass the result to KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT env variable",
+    description: "Print the build context for non-NPX build runners. Pass the result to the KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT environment variable.",

408-410: Ensure JSON-only output and robust serialization

If KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT is set, getBuildContext logs to stdout (“Using predefined build context…”), which will corrupt the JSON stream. Also, buildContext may contain non-serializable/circular values (e.g., getters), risking JSON.stringify failures.

Suggestion: temporarily unset the env var to avoid the extra log and use a safe replacer to drop functions and handle cycles.

-        const buildContext = getBuildContext({ projectDirPath });
-        console.log(JSON.stringify(buildContext))
+        // Avoid stdout noise from env-based short-circuit during printing
+        const ENV_KEY = "KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT";
+        const prev = process.env[ENV_KEY];
+        if (prev !== undefined) {
+            delete process.env[ENV_KEY];
+        }
+        const buildContext = getBuildContext({ projectDirPath });
+        if (prev !== undefined) {
+            process.env[ENV_KEY] = prev;
+        }
+        // Safe stringify: drop functions and handle circular refs
+        const seen = new WeakSet();
+        const json = JSON.stringify(
+            buildContext,
+            (k, v) => {
+                if (typeof v === "function") return undefined;
+                if (typeof v === "object" && v !== null) {
+                    if (seen.has(v)) return undefined;
+                    seen.add(v);
+                }
+                return v;
+            }
+        );
+        console.log(json);

If you prefer not to change behavior here, alternatively switch the “Using predefined build context…” log in src/bin/shared/buildContext.ts to stderr (console.error) so it won’t break JSON output.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between feeba96 and 5727334.

📒 Files selected for processing (4)
  • src/bin/keycloakify/keycloakify.ts (3 hunks)
  • src/bin/main.ts (2 hunks)
  • src/bin/shared/buildContext.ts (2 hunks)
  • src/bin/shared/constants.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/bin/shared/buildContext.ts (1)
src/bin/shared/constants.ts (1)
  • KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT (91-91)
src/bin/keycloakify/keycloakify.ts (3)
src/bin/shared/buildContext.ts (1)
  • BuildContext (27-78)
src/bin/keycloakify/buildJars/buildJars.ts (1)
  • buildJars (19-68)
src/bin/tools/fs.rmSync.ts (1)
  • rmSync (9-34)
src/bin/main.ts (2)
src/bin/keycloakify/keycloakify.ts (1)
  • command (13-158)
src/bin/shared/buildContext.ts (1)
  • getBuildContext (133-1073)
🔇 Additional comments (5)
src/bin/shared/buildContext.ts (1)

15-17: Import looks correct and scoped.
No issues with the new constant import.

src/bin/keycloakify/keycloakify.ts (2)

21-24: Correct: Maven check bypassed when --skip-jar.
Prevents blocking environments where Maven isn't available.


137-151: Jar skip path is clear and side-effect free.
Leaving resourcesDirPath intact when skipping jar is a sensible default.

src/bin/main.ts (2)

67-90: Build flags wired into CLI (skip-jar / skip-post-build): LGTM

The options are correctly added with sane defaults and hooked into the build command.


93-100: Propagation of flags to keycloakify.command: LGTM

Parameters match the updated signature and will properly bypass Maven and post-build when requested.

Comment on lines +137 to +141
if (process.env[KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT]){
console.log("Using predefined build context from env variable");
return JSON.parse(process.env[KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT]);
}

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Sep 8, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden parsing of env-supplied BuildContext (fail fast with actionable errors).
Direct JSON.parse may crash with a vague stack trace and no shape validation. Add try/catch and minimal schema checks so users get clear guidance.

Apply this diff:

-    if (process.env[KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT]){
-        console.log("Using predefined build context from env variable");
-        return JSON.parse(process.env[KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT]);
-    }
+    if (process.env[KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT]) {
+        try {
+            const parsed = JSON.parse(process.env[KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT]!);
+            // Minimal runtime validation to avoid subtle breakages downstream
+            const zMinimal = z.object({
+                packageJsonFilePath: z.string(),
+                keycloakifyBuildDirPath: z.string(),
+                projectDirPath: z.string(),
+                bundler: z.union([z.literal("vite"), z.literal("webpack")]),
+                themeNames: z.array(z.string()).min(1),
+                jarTargets: z
+                    .array(
+                        z.object({
+                            keycloakVersionRange: z.string(),
+                            jarFileBasename: z.string()
+                        })
+                    )
+                    .min(1),
+                implementedThemeTypes: z.unknown(),
+                startKeycloakOptions: z.unknown()
+            });
+            zMinimal.parse(parsed);
+            console.log(
+                `Using predefined build context from ${KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT}`
+            );
+            return parsed as BuildContext;
+        } catch (e) {
+            console.error(
+                chalk.red(
+                    `Invalid ${KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT}: ${(e as Error).message}. ` +
+                        `Tip: run "keycloakify print-build-context" and paste its JSON.`
+                )
+            );
+            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
if (process.env[KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT]){
console.log("Using predefined build context from env variable");
return JSON.parse(process.env[KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT]);
}
if (process.env[KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT]) {
try {
const parsed = JSON.parse(process.env[KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT]!);
// Minimal runtime validation to avoid subtle breakages downstream
const zMinimal = z.object({
packageJsonFilePath: z.string(),
keycloakifyBuildDirPath: z.string(),
projectDirPath: z.string(),
bundler: z.union([z.literal("vite"), z.literal("webpack")]),
themeNames: z.array(z.string()).min(1),
jarTargets: z
.array(
z.object({
keycloakVersionRange: z.string(),
jarFileBasename: z.string()
})
)
.min(1),
implementedThemeTypes: z.unknown(),
startKeycloakOptions: z.unknown()
});
zMinimal.parse(parsed);
console.log(
`Using predefined build context from ${KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT}`
);
return parsed as BuildContext;
} catch (e) {
console.error(
chalk.red(
`Invalid ${KEYCLOAKIFY_PREDEFINED_BUILD_CONTEXT}: ${(e as Error).message}. ` +
`Tip: run "keycloakify print-build-context" and paste its JSON.`
)
);
process.exit(1);
}
}
🤖 Prompt for AI Agents
In src/bin/shared/buildContext.ts around lines 137 to 141, the code directly
JSON.parses an env var which can throw a vague error and produce an invalid
BuildContext shape; wrap the parse in a try/catch to fail fast with a clear
message if parsing fails, then perform minimal shape validation (e.g., ensure
the result is an object and contains required keys/types used later) and throw a
descriptive Error if validation fails; log or include the env var name and
suggestion to check JSON formatting so callers receive actionable guidance.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I feel like this runtime type check is unnecessary, but I am happy to fix if you guys need.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@zapkub Thanks for considering the suggestion! I understand your perspective that the runtime validation might feel like overkill.

The main benefit would be providing clearer error messages to users if they accidentally pass malformed JSON or if the structure doesn't match expectations - instead of getting a cryptic error later in the build process, they'd get an immediate, actionable message pointing them to use keycloakify print-build-context.

Since this feature is designed for users working with non-standard build systems (who are likely more technical), and the JSON is typically generated programmatically via your print-build-context command, I can see the argument that basic JSON.parse() might be sufficient.

Feel free to keep it as-is if you think it's the right balance for your use case. The core functionality works well either way!

@garronej
Copy link
Copy Markdown
Collaborator

Hello @zapkub,

First, thank you for taking the time to put together this PR, I really appreciate the effort and the thought behind it. 🙏

That said, I don’t think we should move forward with merging this change.

The problem this PR addresses (preventing subprocess execution in CI environments) comes from very specific build policies. I completely understand where that comes from, many organizations have strict compliance or reproducibility requirements.
However, from Keycloakify’s perspective, introducing additional flags like --skip-jar, --skip-post-build, or environment-driven build contexts adds a lot of complexity to the build pipeline for a very niche use case.

The packaging process is already one of the most intricate parts of the project, and every new code path or flag increases maintenance costs and potential edge cases. Given that, I’d prefer to keep the CLI focused and stable, rather than expanding it to accommodate custom CI rules.

If you need to adapt Keycloakify to your environment, I’d recommend using patch-package.
It’s a tool I personally use when I need to slightly adjust a dependency to fit a very specific constraint. It lets you maintain a small local diff that’s automatically applied after each install, no forking or waiting for upstream changes, and you stay in full control of your modification.

So again, thank you for the contribution and for taking the initiative to make Keycloakify work better in your setup. I really appreciate the effort, I just think this particular customization is better handled downstream. 🙏

@garronej garronej closed this Oct 21, 2025
@zapkub
Copy link
Copy Markdown
Author

zapkub commented Nov 9, 2025

@garronej Thank you for your response. We will handle the change in our side and looking forward to contribute the build feature in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants