Skip to content

v3.0.0 #74

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

Merged
merged 33 commits into from
May 31, 2025
Merged

v3.0.0 #74

merged 33 commits into from
May 31, 2025

Conversation

soranoo
Copy link
Owner

@soranoo soranoo commented May 31, 2025

Summary by Sourcery

Upgrade to version 3.0.0 with a complete overhaul of the CSS transformation engine, adding nested CSS and TailwindCSS 4 support and refining the configuration API.

New Features:

  • Support nested CSS obfuscation
  • Add compatibility with TailwindCSS v4 class syntax
  • Enable CSS idents obfuscation alongside selectors

Enhancements:

  • Rebuild CSS pipeline using css-seasoning and lightningcss-wasm
  • Unify path filtering via whiteListedFolderPaths and blackListedFolderPaths
  • Switch JS obfuscation to AST-based parsing by default with regex fallback
  • Simplify and consolidate utility functions (e.g. shouldIncludePath, loadConversionTables, stringToNumber)
  • Rename and merge configuration options (prefix/suffix, ignorePatterns) and deprecate obsolete flags

Build:

  • Bump version to 3.0.0-beta.5 and update dependencies
  • Integrate semantic-release and add biome lint script

CI:

  • Update test workflow to actions/checkout@v4
  • Add GitHub Actions release workflow

Documentation:

  • Revise README for v3 release, add migration guide (docs/upgrade-to-v3.md) and update config reference

Tests:

  • Update JS-AST and utility tests to match new code structure

Summary by CodeRabbit

  • New Features

    • Introduced support for TailwindCSS 4, nested CSS, and CSS idents obfuscation.
    • Added a new release workflow for automated publishing and versioning.
    • Provided a migration guide for upgrading to version 3.
  • Improvements

    • Simplified and refactored CSS obfuscation using external libraries for better reliability.
    • Unified and modernized configuration options, including new prefix/suffix and ignore pattern structures.
    • Improved path filtering and conversion table management for more robust processing.
  • Bug Fixes

    • Enhanced handling of class name obfuscation in HTML and JavaScript files.
  • Documentation

    • Updated the README with detailed breaking changes, new configuration examples, and migration instructions.
    • Added a dedicated upgrade guide for version 3.
  • Chores

    • Replaced Prettier with Biome for code formatting and linting.
    • Updated dependencies and scripts for release and linting workflows.
  • Refactor

    • Deprecated and removed legacy configuration options and internal utilities.
    • Streamlined codebase by removing redundant or obsolete test files and functions.
  • Tests

    • Added new unit tests for utility and obfuscation functions to ensure reliability.

soranoo and others added 30 commits April 22, 2025 07:16
Moves test files from the `src/handlers` directory to the `src/__tests__` directory for better organization and separation of concerns.
This change improves the project structure and makes it easier to locate and manage test files.
Also, updates import paths in test files.
Moves the test file from the `src` directory to the `__tests__` directory for better organization and separation of concerns.
This change improves the project structure and makes it easier to locate test files.
Migrates the project's code formatting and linting from Prettier to Biome.

This change removes the existing Prettier configuration and
implements Biome. The Biome configuration enforces consistent code
style and catches potential issues, improving code quality and
maintainability.
Upgrades the core CSS transformation engine to `css-seasoning` and `lightningcss-wasm`.

- Replaces PostCSS with `css-seasoning` for CSS parsing and transformation.
- Integrates `lightningcss-wasm` for CSS minification and optimization.
- Introduces a new `obfuscateCssFiles` function for processing CSS files.
- Removes the deprecated `classLength` option.
- Improves the way of css obfuscation with new logic and features.

This change significantly enhances the performance and flexibility of the CSS obfuscation process.
Refactors the CSS obfuscation process to leverage the `css-seasoning` library, replacing custom CSS parsing and manipulation logic. This change improves maintainability and leverages a dedicated library for CSS transformations.

Removes the old CSS test file as the functionality is now covered by `css-seasoning`.
Refactors utility functions for better organization and reusability.

- Removes unused random string generation and string simplification functions.
- Exports previously internal functions to enhance module accessibility and
  facilitate testing.
- Streamlines the codebase by removing dead code and improving overall
  structure.
Introduces breaking changes to support TailwindCSS 4, nested CSS, and CSS ident obfuscation.

- Updates configuration options, including renaming, merging, and removing deprecated options.
- Migrates to css-seasoning v1.4.1.
- Enables JS AST parsing by default.
- Updates documentation with v3 migration guide.
Sets up semantic release to automate the release process.

This includes:
- Adding a release workflow file.
- Installing semantic-release and related plugins.
- Creating a release configuration file.

This automates versioning, changelog generation, and package publishing.
# 1.0.0-beta.1 (2025-04-25)

### Bug Fixes

* fix html tests ([f517839](f517839))
* **html:** fix [#57](#57) ([a638538](a638538))
* **html:** fix incorrect html class name truncation ([7cb985d](7cb985d))
* **js-ast:** added support to `MemberExpression` [#45](#45) ([8c95ba0](8c95ba0))
* **js-ast:** added support to `TemplateLiteral` & `TemplateElement` [#45](#45) ([0dd46b3](0dd46b3))

### Features

* Enables semantic release for automated publishing ([b43a194](b43a194))
* Upgrades to v3 with new CSS transformation ([0afd780](0afd780))
* v3 migration with TailwindCSS 4 support ([668a5f6](668a5f6))
# 3.0.0-beta.3 (2025-04-25)

### Bug Fixes

* fix html tests ([f517839](f517839))
* **html:** fix [#57](#57) ([a638538](a638538))
* **html:** fix incorrect html class name truncation ([7cb985d](7cb985d))
* **js-ast:** added support to `MemberExpression` [#45](#45) ([8c95ba0](8c95ba0))
* **js-ast:** added support to `TemplateLiteral` & `TemplateElement` [#45](#45) ([0dd46b3](0dd46b3))

### Features

* Enables semantic release for automated publishing ([b43a194](b43a194))
* Upgrades to v3 with new CSS transformation ([0afd780](0afd780))
* v3 migration with TailwindCSS 4 support ([668a5f6](668a5f6))
# [3.0.0-beta.4](v3.0.0-beta.3...v3.0.0-beta.4) (2025-05-30)

### Features

* support ident prefix/suffix options ([32952bf](32952bf))
# [3.0.0-beta.5](v3.0.0-beta.4...v3.0.0-beta.5) (2025-05-30)

### Bug Fixes

* update key names in loadConversionTables for consistency ([3d6035d](3d6035d))
Copy link

sourcery-ai bot commented May 31, 2025

Reviewer's Guide

This PR overhauls the v3 release by rebuilding the CSS obfuscation core on css-seasoning and lightningcss to support nested rules and idents, introduces a unified path filtering mechanism, refactors utilities and handlers for CSS/HTML/JS with AST fallback, updates configuration and CLI integration, and refreshes documentation and CI workflows (including semantic-release).

Sequence Diagram: CSS Obfuscation and File Update Process in v3.0.0

sequenceDiagram
    actor Developer
    participant CLI
    participant MainProcess as "index.ts (obfuscateCli -> obfuscate)"
    participant CssHandler as "css.ts (obfuscateCssFiles)"
    participant CssSeasoningLib as "css-seasoning"
    participant LightningCssLib as "lightningcss"
    participant FileSystem
    participant UtilsProcess as "utils.ts (replaceJsonKeysInFiles)"

    Developer->>CLI: Runs obfuscate command (e.g., obfuscate-build)
    CLI->>MainProcess: obfuscateCli()
    MainProcess->>MainProcess: Loads ConfigOptions_v3
    alt Config.refreshClassConversionJson is true
        MainProcess->>FileSystem: Deletes existing conversion.json
    end
    MainProcess->>CssHandler: obfuscateCssFiles(config)
    CssHandler->>CssSeasoningLib: initTransform()
    CssHandler->>LightningCssLib: init()
    CssHandler->>UtilsProcess: findAllFilesWithExt(".css", buildFolderPath, pathFilters)
    UtilsProcess-->>CssHandler: cssPaths[]
    loop For each cssPath
        CssHandler->>FileSystem: Read CSS file content
        CssHandler->>CssSeasoningLib: transform(cssContent, mode, prefix, suffix, seed, ignorePatterns, existingTables)
        CssSeasoningLib-->>CssHandler: obfuscatedCss, newPartialConversionTables
        alt Config.removeOriginalCss is false
            CssHandler->>CssHandler: Merge original CSS + obfuscatedCss
        end
        CssHandler->>LightningCssLib: transform(mergedOrObfuscatedCss, minify:true)
        LightningCssLib-->>CssHandler: finalCssCode
        CssHandler->>FileSystem: Write finalCssCode to outCssPath
        CssHandler->>CssHandler: Aggregate newPartialConversionTables
    end
    CssHandler-->>MainProcess: finalAggregatedConversionTables
    MainProcess->>FileSystem: Write finalAggregatedConversionTables to "conversion.json"
    MainProcess->>UtilsProcess: replaceJsonKeysInFiles(finalAggregatedConversionTables, config)
    UtilsProcess->>FileSystem: Read/Write HTML, JS, etc. files (applying obfuscated class names)
    MainProcess-->>CLI: Log "Obfuscation Completed"
    CLI-->>Developer: Shows logs
Loading

ER Diagram: Configuration Options for v3.0.0

erDiagram
    ConfigOptions {
        boolean enable
        string mode "random | simplify"
        string buildFolderPath
        string classConversionJsonFolderPath
        boolean refreshClassConversionJson
        PrefixSuffixOptions prefix
        PrefixSuffixOptions suffix
        IgnorePatterns ignorePatterns
        string_array allowExtensions
        RegExp_array contentIgnoreRegexes
        string_RegExp_array whiteListedFolderPaths
        string_RegExp_array blackListedFolderPaths
        boolean enableMarkers
        string_array markers
        boolean removeMarkersAfterObfuscated
        boolean removeOriginalCss
        string generatorSeed "Optional, undefined for random"
        boolean enableJsAst "Default: true"
        string logLevel
    }

    PrefixSuffixOptions {
        string selectors "Optional"
        string idents "Optional"
    }

    IgnorePatterns {
        string_RegExp_array selectors "Optional"
        string_RegExp_array idents "Optional"
    }

    ConfigOptions ||--|{ PrefixSuffixOptions : has_prefix
    ConfigOptions ||--|{ PrefixSuffixOptions : has_suffix
    ConfigOptions ||--|{ IgnorePatterns : has_ignorePatterns

    DeprecatedInV3 {
        string classLength "Removed"
        string classPrefix "Replaced by prefix.selectors"
        string classSuffix "Replaced by suffix.selectors"
        string_RegExp_array classIgnore "Replaced by ignorePatterns.selectors"
        string simplify_seedable_mode "Removed from 'mode' options"
        string generatorSeed_minus_one "'-1' for random seed replaced by 'undefined'"
        boolean enableJsAst_false_default "Default changed from false to true"
    }
Loading

File-Level Changes

Change Details Files
Rebuilt CSS transformation engine using css-seasoning and lightningcss with nested CSS and ident obfuscation support
  • Replace css.parse and custom logic with initTransform() & transform()
  • Support nested rules and CSS idents in obfuscation
  • Expose prefix, suffix, mode, ignorePatterns, and seed options
  • Add obfuscateCssFiles orchestrator to handle batch CSS processing
src/handlers/css.ts
Introduced unified path filtering function for whitelisting/blacklisting
  • Add shouldIncludePath util with RegExp and string support
  • Replace manual path checks in replaceJsonKeysInFiles
  • Update findAllFilesWithExt to use shouldIncludePath
src/utils.ts
Refactored utility module and conversion table loading
  • Remove recoverable-random, use cssEscape/cssUnescape
  • Introduce loadConversionTables for merging selectors and idents
  • Modularize and export normalizePath, getFilenameFromPath, obfuscateKeys, etc.
src/utils.ts
src/types.ts
Simplified HTML handler with htmlparser2 and CSS unescape
  • Streamline tag parsing and class name obfuscation using obfuscateKeys
  • Handle script tags by invoking obfuscateJs on inline scripts
  • Clean up deprecated recursive functions
src/handlers/html.ts
Enhanced JS obfuscation with Babel AST fallback
  • Implement obfuscateJsWithAst for AST-based string literal replacement
  • Refactor obfuscateJs to use AST when enabled and fallback to regex
  • Modularize component search and forward-ref obfuscation
src/handlers/js.ts
src/handlers/js-ast.ts
Updated configuration schema and CLI integration
  • Revise Options type to include prefix, suffix, ignorePatterns and remove deprecated fields
  • Update defaultOptions, merge classIgnore into ignorePatterns for backward compatibility
  • Convert obfuscateCli to async, write conversion.json file
src/config.ts
src/index.ts
src/types.ts
Overhauled documentation and CI workflows for semantic-release
  • Bump package to 3.0.0-beta.5, add biome lint and semantic-release scripts
  • Update README with v3 migration guide and revised config examples
  • Add GitHub Actions release workflow and migration docs
package.json
.github/workflows/auto_test.yml
.github/workflows/release.yml
README.md
docs/upgrade-to-v3.md
release.config.cjs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

vercel bot commented May 31, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
next-css-obfuscator ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 31, 2025 10:04am

Copy link

coderabbitai bot commented May 31, 2025

Walkthrough

This update introduces a major version (v3) with breaking changes, including new configuration options, migration guides, and extensive refactoring of CSS obfuscation logic to use external libraries. The release process is automated with semantic-release, and Prettier is replaced by Biome for code formatting and linting. Deprecated options are marked, and documentation is updated accordingly.

Changes

File(s) / Path(s) Change Summary
.github/workflows/auto_test.yml, .github/workflows/release.yml Reformatted branch arrays, updated checkout action, added new release workflow for semantic-release with custom initial version and dry-run for PRs.
.prettierignore, .prettierrc Deleted Prettier config and ignore files.
biome.json Added Biome configuration for formatting and linting.
package.json Bumped to 3.0.0-beta.5, updated scripts, replaced Prettier with Biome, changed CSS dependencies, added semantic-release.
release.config.cjs Added semantic-release config for stable and beta branches, plugins for npm, git, and release notes.
README.md, docs/upgrade-to-v3.md Updated for v3: breaking changes, migration guide, new features, configuration changes, and usage notes.
src/config.ts, src/types.ts Refactored config/types: introduced prefix, suffix, ignorePatterns, deprecated options, removed regex-based whitelists/blacklists, updated types for external library compatibility.
src/handlers/css.ts Major refactor: replaced custom CSS obfuscation logic with css-seasoning and lightningcss-wasm; added new async obfuscation functions; removed legacy helpers.
src/handlers/html.ts Removed deprecated recursive tag extraction functions; refactored main export to arrow function; improved script tag obfuscation logic.
src/handlers/js.ts, src/handlers/js-ast.ts Refactored to exported arrow functions; improved formatting; minor logic tweaks; consistent parameter handling.
src/index.ts Refactored main flow: async/await, unified config, backward compatibility for deprecated options, explicit conversion table saving, updated CLI.
src/utils.ts Refactored utilities: removed random string helpers, added path filtering, improved obfuscation logic, added conversion table loader, converted to arrow functions.
src/tests/html.test.ts, src/tests/js.test.ts, src/tests/utils.test.ts Added new test suites for HTML, JS, and utility functions with comprehensive coverage.
src/handlers/css.test.ts, src/handlers/html.test.ts, src/handlers/js.test.ts, src/utils.test.ts Deleted legacy test files for CSS, HTML, JS, and utilities.
bin/cli.mjs, tsconfig.json Minor formatting: added trailing newlines, excluded release config from TypeScript.
demos/next14-app-router/next-css-obfuscator.config.cjs Added JSDoc type annotation for config object.
demos/next14-app-router/package.json Updated next-css-obfuscator dependency version to 3.0.0-beta.5.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant Config
    participant CSSHandler
    participant Utils
    participant ReleaseWorkflow

    User->>CLI: Run obfuscateCli()
    CLI->>Config: Load and normalize options
    Config->>CSSHandler: obfuscateCssFiles(options)
    CSSHandler->>Utils: Load conversion tables, filter files
    CSSHandler->>CSSHandler: Obfuscate CSS using css-seasoning/lightningcss-wasm
    CSSHandler->>Utils: Save conversion tables
    CLI->>Utils: replaceJsonKeysInFiles(conversionTables, ...)
    Utils->>Utils: Obfuscate HTML/JS files using conversion tables
    CLI->>User: Done

    User->>ReleaseWorkflow: Push or PR to main/beta
    ReleaseWorkflow->>ReleaseWorkflow: Checkout, install, build, patch semantic-release, run release/dry-run
Loading

Possibly related PRs

  • soranoo/next-css-obfuscator#68: Introduces the same new GitHub Actions release workflow for semantic-release automation, directly related to CI/CD configuration changes in this PR.

Suggested labels

released on @beta

Poem

🐇✨
Version three hops in with flair,
New configs and docs—handle with care!
CSS now seasoned, random seeds in tow,
Biome checks code, Prettier must go.
Migrate with guides, let warnings be heeded,
Obfuscation’s improved—just what you needed!
—A rabbit, delighted, in code fields unseated.

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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: 12

🧹 Nitpick comments (7)
biome.json (2)

1-7: VCS configuration requires clarification.

The VCS is disabled ("enabled": false) but still specifies Git settings. This configuration might be confusing.

Consider either:

  1. Enabling VCS if you want Git integration, or
  2. Removing the unnecessary clientKind and useIgnoreFile properties when VCS is disabled.
  "vcs": {
-   "enabled": false,
-   "clientKind": "git",
-   "useIgnoreFile": false
+   "enabled": false
  },

29-65: Comprehensive and well-balanced linting rules.

The linting configuration is thoughtful with appropriate severity levels:

Strengths:

  • Good mix of error, warning, and info levels
  • Focuses on code quality without being overly restrictive
  • Enables TypeScript-specific best practices
  • noExplicitAny as error promotes better typing

Minor considerations:

  • noDebugger as "info" might be too lenient for production code
  • noUnusedVariables as "info" might miss important cleanup opportunities

Consider upgrading these rules for better code quality:

  "correctness": {
-   "noUnusedVariables": "info",
+   "noUnusedVariables": "warn",
    "useArrayLiterals": "error"
  },
  "suspicious": {
-   "noDebugger": "info",
+   "noDebugger": "warn",
src/__tests__/js-ast.test.ts (1)

1484-1485: Consider adding a comment about the test data format

The very long line of minified code could benefit from a comment explaining that it represents real-world minified/bundled JavaScript code to ensure the obfuscation handles such cases correctly.

 it("should handle basic real world example 1", () => {
+  // Testing with real-world minified Next.js bundle code
   const code = `(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[185],{6773:function(e,t,n){Promise.resolve().then(n.bind(n,1845)),Promise.resolve().then(n.bind(n,7388)),Promise.resolve().then(n.bind(n,6016)),Promise.resolve().then(n.bind(n,1120)),Promise.resolve().then(n.bind(n,1255)),Promise.resolve().then(n.t.bind(n,5935,23)),Promise.resolve().then(n.t.bind(n,3710,23)),Promise.resolve().then(n.t.bind(n,3385,23)),Promise.resolve().then(n.bind(n,6212)),Promise.resolve().then(n.bind(n,1267)),Promise.resolve().then(n.bind(n,5322)),Promise.resolve().then(n.bind(n,9149))},6212:function(e,t,n){"use strict";n.r(t),n.d(t,{default:function(){return d}});var r=n(3827),i=n(703),l=n(8792),a={src:"/_next/static/media/some.svg",height:42,width:42,blurWidth:0,blurHeight:0},s=n(4090),o=n(7907);function d(){(0,o.usePathname)();let[e,t]=(0,s.useState)(!1),n=(0,s.useCallback)((e,t)=>{if(e.id===t)return!0;for(let r=0;r<e.children.length;r++)if(n(e.children[r],t))return!0;return!1},[]);return(0,s.useEffect)(()=>{t(n(document.body,"NotFoundPage"))},[n]),e?null:(0,r.jsx)(r.Fragment,{children:(0,r.jsx)("header",{className:"h-[4.4dvw] size-full bg-transparent px-[1.41dvw] flex items-center",children:(0,r.jsx)(l.default,{href:"/",className:"flex items-center flex-shrink-0",children:(0,r.jsx)(i.default,{className:"w-auto h-[1.88dvw]",src:a,alt:"Logo"})})})})}},1267:function(e,t,n){"use strict";n.r(t),n.d(t,{default:function(){return o}});var r=n(3827),i=n(8792),l=n(7907),a=n(4090),s=n(6314);function o(){let e=(0,l.usePathname)(),[t,n]=(0,a.useState)(!1),o=(0,a.useCallback)((e,t)=>{if(e.id===t)return!0;for(let n=0;n<e.children.length;n++)if(o(e.children[n],t))return!0;return!1},[]);(0,a.useEffect)(()=>{n(o(document.body,"NotFoundPage"))},[o]);let d=[{href:"/",label:"Home"},{href:"/tag1",label:"tag1"},{href:"/tag2",label:"tag2"},{href:"/tag3",label:"tag3"},{href:"/tag4",label:"tag4"},],[u,c]=(0,a.useState)(()=>{let t=d.find(t=>t.href===e);return t?t.label:"label"}),[h,f]=(0,a.useState)(()=>{if(e.startsWith("/tag1"))return"tag1";{let t=d.find(t=>t.href===e);return t?t.label:"label"}});return(0,a.useEffect)(()=>{e.startsWith("/tag1")&&f("tag1")},[e]),(0,a.useEffect)(()=>{e.startsWith("/tag1/")&&(f("tag1"),c("tag1"))},[e]),t?null:(0,r.jsx)(r.Fragment,{children:(0,r.jsx)("div",{className:"z-0 w-[11dvw] h-dvh [&_.side-box]:absolute [&_.side-box]:left-[0.94dvw] [&_.side-box]:top-0 [&_.side-box]:-z-10 [&_.side-box]:h-full [&_.side-box]:w-[calc(100%-0.94dvw)] [&_.side-box]:rounded-tl-[0.94dvw] [&_.side-box]:rounded-bl-[0.94dvw] [&_.side-box]:bg-gradient-to-r [&_.side-box]:from-[#2858ff] [&_.side-box]:to-85%",children:(0,r.jsx)("div",{className:"   flex flex-col items-start size-full   *:relative *:w-full *:font-bold   [&_a]:flex [&_a]:items-center [&_a]:w-full [&_a]:pe-[2.82dvw] [&_a]:h-[2.82dvw] [&_a]:transition-[padding_color] [&_a]:ease-bounce [&_a]:duration-300   [&_#side-box-line]:absolute [&_#side-box-line]:left-0 [&_#side-box-line]:top-0 [&_#side-box-line]:-z-10 [&_#side-box-line]:h-full [&_#side-box-line]:w-[0.235dvw] [&_#side-box-line]:transition-opacity [&_#side-box-line]:duration-0 [&_#side-box-line]:rounded-tr-full [&_#side-box-line]:rounded-br-full [&_#side-box-line]:bg-[#2858ff]   ",children:d.map(t=>(0,r.jsx)(s.E.div,{onHoverStart:()=>c(t.label),onHoverEnd:()=>c(h),onClick:()=>f(t.label),children:(0,r.jsxs)(i.default,{href:t.href,className:t.href===e||e.startsWith("/tag1")&&"/tag1"===t.href?"text-white ps-[2.115dvw]":"text-white/50 ps-[1.41dvw]",children:[t.href===e||e.startsWith("/tag1/")&&"/tag1"===t.href?(0,r.jsx)(s.E.div,{transition:{type:"spring",duration:.65,mass:.5},layoutId:"sideBox",className:"side-box"}):null,t.label,t.label===u||e.startsWith("/tag1/")&&"/tag1/"===t.href?(0,r.jsx)(s.E.div,{transition:{type:"spring",duration:.8},layoutId:"sideBoxLine",id:"side-box-line"}):null,]})},t.href))})})})}},9149:function(e,t,n){"use strict";n.r(t);var r=n(4404),i=n(4090),l=n(7717);let a=e=>{let{color:t,height:n,crawl:r,crawlSpeed:a,initialPosition:s,easing:o,speed:d,shadow:u,template:c,zIndex:h=99999999,delay:f}=e,$=null!=t?t:"#29d";return(u||void 0===u)&&(u||"box-shadow:0 0 10px ".concat($,",0 0 5px ").concat($)),i.useEffect(()=>{let e;function t(){clearTimeout(e),e=setTimeout(l.start,null!=f?f:200)}function n(){clearTimeout(e),l.done()}l.configure({trickle:null==r||r,trickleSpeed:null!=a?a:200,minimum:null!=s?s:.55+.2*Math.random(),easing:null!=o?o:"ease-out",speed:null!=d?d:180,template:null!=c?c:'<div class="bar" role="bar"><div class="peg"></div></div>'});var i=document.querySelectorAll("html");function u(e){try{let r=e.target,l=function(e){for(;e&&"a"!==e.tagName.toLowerCase();)e=e.parentElement;return e}(r),a=null==l?void 0:l.href;if(a){var s;let o=window.location.href,d="_blank"===l.target,u=a.startsWith("blob:"),c=function(e,t){let n=new URL(e),r=new URL(t);if(n.hostname===r.hostname&&n.pathname===r.pathname&&n.search===r.search){let i=n.hash,l=r.hash;return i!==l&&n.href.replace(i,"")===r.href.replace(l,"")}return!1}(o,a),h;a===o||c||d||u||e.ctrlKey?(t(),n(),[].forEach.call(i,function(e){e.classList.remove("nprogress-busy")})):(t(),h=(s=window.history).pushState,s.pushState=function(){return n(),[].forEach.call(i,function(e){e.classList.remove("nprogress-busy")}),h.apply(s,arguments)})}}catch(f){t(),n()}}return document.addEventListener("click",u),()=>{document.removeEventListener("click",u)}},[r,a,f,o,s,d,c]),null};t.default=a,a.propTypes={color:r.string,height:r.number,crawl:r.bool,crawlSpeed:r.number,initialPosition:r.number,easing:r.string,speed:r.number,delay:r.number,template:r.string,shadow:r.oneOfType([r.string,r.bool]),zIndex:r.number}},5322:function(e,t,n){"use strict";n.r(t);var r=n(3827),i=n(4090);t.default=()=>{let e=(0,i.useRef)(null);return(0,i.useEffect)(()=>{let t=e.current,n=null==t?void 0:t.getContext("2d"),r={x:.5*window.innerWidth,y:.5*window.innerHeight},i={pointsNumber:8,widthFactor:4,spring:.35,friction:.48},l=Array(i.pointsNumber);for(let a=0;a<i.pointsNumber;a++)l[a]={x:r.x,y:r.y,dx:0,dy:0};let s=(e,t)=>{r.x=e,r.y=t},o=()=>{e.current&&(e.current.width=window.innerWidth,e.current.height=window.innerHeight)},d=e=>{if(n&&(n.strokeStyle="#e2ecfc"),t&&(null==n||n.clearRect(0,0,t.width,t.height)),l.forEach((e,t)=>{let n=0===t?r:l[t-1],a=0===t?.4*i.spring:i.spring;e.dx+=(n.x-e.x)*a,e.dy+=(n.y-e.y)*a,e.dx*=i.friction,e.dy*=i.friction,e.x+=e.dx,e.y+=e.dy}),n){n.lineCap="round",n.beginPath(),n.moveTo(l[0].x,l[0].y);for(let a=1;a<l.length-1;a++){let s=.5*(l[a].x+l[a+1].x),o=.5*(l[a].y+l[a+1].y);n.quadraticCurveTo(l[a].x,l[a].y,s,o),n.lineWidth=i.widthFactor*(i.pointsNumber-a),n.stroke()}n.lineTo(l[l.length-1].x,l[l.length-1].y),n.stroke()}window.requestAnimationFrame(d)},u=()=>{o()},c=e=>{s(e.pageX,e.pageY)},h=e=>{s(e.pageX,e.pageY)},f=e=>{s(e.targetTouches[0].pageX,e.targetTouches[0].pageY)};return window.addEventListener("click",c),window.addEventListener("mousemove",h),window.addEventListener("touchmove",f),window.addEventListener("resize",u),o(),d(0),()=>{window.removeEventListener("click",c),window.removeEventListener("mousemove",h),window.removeEventListener("touchmove",f),window.removeEventListener("resize",u)}},[]),(0,r.jsx)("canvas",{ref:e})}},3385:function(){}},function(e){e.O(0,[314,250,134,336,971,69,744],function(){return e(e.s=6773)}),_N_E=e.O()},]);`;
   const expectedCode = `(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[185],{6773:function(e,t,n){Promise.resolve().then(n.bind(n,1845)),Promise.resolve().then(n.bind(n,7388)),Promise.resolve().then(n.bind(n,6016)),Promise.resolve().then(n.bind(n,1120)),Promise.resolve().then(n.bind(n,1255)),Promise.resolve().then(n.t.bind(n,5935,23)),Promise.resolve().then(n.t.bind(n,3710,23)),Promise.resolve().then(n.t.bind(n,3385,23)),Promise.resolve().then(n.bind(n,6212)),Promise.resolve().then(n.bind(n,1267)),Promise.resolve().then(n.bind(n,5322)),Promise.resolve().then(n.bind(n,9149))},6212:function(e,t,n){\"usestrict\";n.r(t),n.d(t,{default:function(){returnd}});varr=n(3827),i=n(703),l=n(8792),a={src:\"/_next/static/media/some.svg\",height:42,width:42,blurWidth:0,blurHeight:0},s=n(4090),o=n(7907);functiond(){(0,o.usePathname)();let[e,t]=(0,s.useState)(!1),n=(0,s.useCallback)((e,t)=>{if(e.id===t)return!0;for(letr=0;r<e.children.length;r++)if(n(e.children[r],t))return!0;return!1},[]);return(0,s.useEffect)(()=>{t(n(document.body,\"NotFoundPage\"))},[n]),e?null:(0,r.jsx)(r.Fragment,{children:(0,r.jsx)(\"header\",{className:\"{{obfuscated}}\",children:(0,r.jsx)(l.default,{href:\"/\",className:\"{{obfuscated}}\",children:(0,r.jsx)(i.default,{className:\"{{obfuscated}}\",src:a,alt:\"Logo\"})})})})}},1267:function(e,t,n){\"usestrict\";n.r(t),n.d(t,{default:function(){returno}});varr=n(3827),i=n(8792),l=n(7907),a=n(4090),s=n(6314);functiono(){lete=(0,l.usePathname)(),[t,n]=(0,a.useState)(!1),o=(0,a.useCallback)((e,t)=>{if(e.id===t)return!0;for(letn=0;n<e.children.length;n++)if(o(e.children[n],t))return!0;return!1},[]);(0,a.useEffect)(()=>{n(o(document.body,\"NotFoundPage\"))},[o]);letd=[{href:\"/\",label:\"Home\"},{href:\"/tag1\",label:\"tag1\"},{href:\"/tag2\",label:\"tag2\"},{href:\"/tag3\",label:\"tag3\"},{href:\"/tag4\",label:\"tag4\"}],[u,c]=(0,a.useState)(()=>{lett=d.find(t=>t.href===e);returnt?t.label:\"label\"}),[h,f]=(0,a.useState)(()=>{if(e.startsWith(\"/tag1\"))return\"tag1\";{lett=d.find(t=>t.href===e);returnt?t.label:\"label\"}});return(0,a.useEffect)(()=>{e.startsWith(\"/tag1\")&&f(\"tag1\")},[e]),(0,a.useEffect)(()=>{e.startsWith(\"/tag1/\")&&(f(\"tag1\"),c(\"tag1\"))},[e]),t?null:(0,r.jsx)(r.Fragment,{children:(0,r.jsx)(\"div\",{className:\"{{obfuscated}}\",children:(0,r.jsx)(\"div\",{className:\"{{obfuscated}}\",children:d.map(t=>(0,r.jsx)(s.E.div,{onHoverStart:()=>c(t.label),onHoverEnd:()=>c(h),onClick:()=>f(t.label),children:(0,r.jsxs)(i.default,{href:t.href,className:t.href===e||e.startsWith(\"/tag1\")&&\"/tag1\"===t.href?\"{{obfuscated}}\":\"{{obfuscated}}\",children:[t.href===e||e.startsWith(\"/tag1/\")&&\"/tag1\"===t.href?(0,r.jsx)(s.E.div,{transition:{type:\"spring\",duration:.65,mass:.5},layoutId:\"sideBox\",className:\"{{obfuscated}}\"}):null,t.label,t.label===u||e.startsWith(\"/tag1/\")&&\"/tag1/\"===t.href?(0,r.jsx)(s.E.div,{transition:{type:\"spring\",duration:.8},layoutId:\"sideBoxLine\",id:\"side-box-line\"}):null]})},t.href))})})})}},9149:function(e,t,n){\"usestrict\";n.r(t);varr=n(4404),i=n(4090),l=n(7717);leta=e=>{let{color:t,height:n,crawl:r,crawlSpeed:a,initialPosition:s,easing:o,speed:d,shadow:u,template:c,zIndex:h=99999999,delay:f}=e,$=null!=t?t:\"#29d\";return(u||void0===u)&&(u||\"box-shadow:0010px\".concat($,\",005px\").concat($)),i.useEffect(()=>{lete;functiont(){clearTimeout(e),e=setTimeout(l.start,null!=f?f:200)}functionn(){clearTimeout(e),l.done()}l.configure({trickle:null==r||r,trickleSpeed:null!=a?a:200,minimum:null!=s?s:.55+.2*Math.random(),easing:null!=o?o:\"ease-out\",speed:null!=d?d:180,template:null!=c?c:\"<divclass=\\\"bar\\\"role=\\\"bar\\\"><divclass=\\\"peg\\\"></div></div>\"});vari=document.querySelectorAll(\"html\");functionu(e){try{letr=e.target,l=function(e){for(;e&&\"a\"!==e.tagName.toLowerCase();)e=e.parentElement;returne}(r),a=null==l?void0:l.href;if(a){vars;leto=window.location.href,d=\"_blank\"===l.target,u=a.startsWith(\"blob:\"),c=function(e,t){letn=newURL(e),r=newURL(t);if(n.hostname===r.hostname&&n.pathname===r.pathname&&n.search===r.search){leti=n.hash,l=r.hash;returni!==l&&n.href.replace(i,\"\")===r.href.replace(l,\"\")}return!1}(o,a),h;a===o||c||d||u||e.ctrlKey?(t(),n(),[].forEach.call(i,function(e){e.classList.remove(\"nprogress-busy\")})):(t(),h=(s=window.history).pushState,s.pushState=function(){returnn(),[].forEach.call(i,function(e){e.classList.remove(\"nprogress-busy\")}),h.apply(s,arguments)})}}catch(f){t(),n()}}returndocument.addEventListener(\"click\",u),()=>{document.removeEventListener(\"click\",u)}},[r,a,f,o,s,d,c]),null};t.default=a,a.propTypes={color:r.string,height:r.number,crawl:r.bool,crawlSpeed:r.number,initialPosition:r.number,easing:r.string,speed:r.number,delay:r.number,template:r.string,shadow:r.oneOfType([r.string,r.bool]),zIndex:r.number}},5322:function(e,t,n){\"usestrict\";n.r(t);varr=n(3827),i=n(4090);t.default=()=>{lete=(0,i.useRef)(null);return(0,i.useEffect)(()=>{lett=e.current,n=null==t?void0:t.getContext(\"2d\"),r={x:.5*window.innerWidth,y:.5*window.innerHeight},i={pointsNumber:8,widthFactor:4,spring:.35,friction:.48},l=Array(i.pointsNumber);for(leta=0;a<i.pointsNumber;a++)l[a]={x:r.x,y:r.y,dx:0,dy:0};lets=(e,t)=>{r.x=e,r.y=t},o=()=>{e.current&&(e.current.width=window.innerWidth,e.current.height=window.innerHeight)},d=e=>{if(n&&(n.strokeStyle=\"#e2ecfc\"),t&&(null==n||n.clearRect(0,0,t.width,t.height)),l.forEach((e,t)=>{letn=0===t?r:l[t-1],a=0===t?.4*i.spring:i.spring;e.dx+=(n.x-e.x)*a,e.dy+=(n.y-e.y)*a,e.dx*=i.friction,e.dy*=i.friction,e.x+=e.dx,e.y+=e.dy}),n){n.lineCap=\"round\",n.beginPath(),n.moveTo(l[0].x,l[0].y);for(leta=1;a<l.length-1;a++){lets=.5*(l[a].x+l[a+1].x),o=.5*(l[a].y+l[a+1].y);n.quadraticCurveTo(l[a].x,l[a].y,s,o),n.lineWidth=i.widthFactor*(i.pointsNumber-a),n.stroke()}n.lineTo(l[l.length-1].x,l[l.length-1].y),n.stroke()}window.requestAnimationFrame(d)},u=()=>{o()},c=e=>{s(e.pageX,e.pageY)},h=e=>{s(e.pageX,e.pageY)},f=e=>{s(e.targetTouches[0].pageX,e.targetTouches[0].pageY)};returnwindow.addEventListener(\"click\",c),window.addEventListener(\"mousemove\",h),window.addEventListener(\"touchmove\",f),window.addEventListener(\"resize\",u),o(),d(0),()=>{window.removeEventListener(\"click\",c),window.removeEventListener(\"mousemove\",h),window.removeEventListener(\"touchmove\",f),window.removeEventListener(\"resize\",u)}},[]),(0,r.jsx)(\"canvas\",{ref:e})}},3385:function(){}},function(e){e.O(0,[314,250,134,336,971,69,744],function(){returne(e.s=6773)}),_N_E=e.O()}]);`;
src/handlers/css.ts (1)

15-16: Complete or remove the incomplete TODO comment.

The TODO comment "html failed with ." is incomplete and doesn't provide enough context about what failed or what needs to be done.

Either complete the TODO comment with proper context or remove it:

-// TODO: html failed with .
+// TODO: Handle HTML files that fail with specific selectors containing dots
src/utils.ts (3)

284-286: Remove commented out code.

Dead code should be removed rather than left as comments to maintain code cleanliness.

-  // cssPaths.forEach(async (cssPath) => {
-  //   await obfuscateCss(classConversion, cssPath, removeOriginalCss, !enableObfuscateMarkerClasses);
-  // });

289-356: Consider using a local variable instead of reassigning the parameter.

The static analysis tool correctly identifies that reassigning the fileContent parameter can be confusing. While the current implementation works, using a local variable would improve code clarity.

Consider refactoring to use a local variable:

 export const obfuscateKeys = (
   selectorConversion: SelectorConversion,
   fileContent: string,
   contentIgnoreRegexes: RegExp[] = [],
 ) => {
   //ref: https://github.com/n4j1Br4ch1D/postcss-obfuscator/blob/main/utils.js
 
   const usedKeys = new Set<string>();
+  let modifiedContent = fileContent;
   Object.keys(selectorConversion).forEach((key) => {
-    const fileContentOriginal = fileContent;
+    const fileContentOriginal = modifiedContent;
     // let keyUse = escapeRegExp(key.slice(1).replace(/\\/g, ""));
     let keyUse = cssUnescape(key).slice(1);
 
     keyUse = escapeRegExp(keyUse.replace(/\\/g, "")); // escape the key
 
     //? sample: "text-sm w-full\n      text-right\n p-2 flex gap-2 hover:bg-gray-100 dark:hover:bg-red-700 text-right"
     const exactMatchRegex = new RegExp(
       `([\\s"'\\\`]|^)(${keyUse})(?=$|[\\s"'\\\`]|\\\\n|\\\\",|\\\\"})`,
       "g",
     ); // match exact wording & avoid ` ' ""
     // exactMatchRegex = new RegExp(`([\\s"'\\\`]|^)(${keyUse})(?=$|[\\s"'\\\`])`, 'g'); // match exact wording & avoid ` ' ""
 
     const replacement = `$1${selectorConversion[key].slice(1).replace(/\\/g, "").slice(1)}`;
 
-    const matches = fileContent.match(exactMatchRegex);
+    const matches = modifiedContent.match(exactMatchRegex);
     const originalObscuredContentPairs = matches?.map((match) => {
       return {
         originalContent: match,
         obscuredContent: match.replace(exactMatchRegex, replacement),
       };
     });
-    fileContent = fileContent.replace(exactMatchRegex, replacement); // capture preceding space
+    modifiedContent = modifiedContent.replace(exactMatchRegex, replacement); // capture preceding space

Then update all other occurrences of fileContent to modifiedContent within the function and return modifiedContent instead.

🧰 Tools
🪛 Biome (1.9.4)

[error] 320-320: Reassigning a function parameter is confusing.

The parameter is declared here:

Use a local variable instead.

(lint/style/noParameterAssign)


[error] 338-338: Reassigning a function parameter is confusing.

The parameter is declared here:

Use a local variable instead.

(lint/style/noParameterAssign)


117-117: Fix typo: "registery" should be "registry".

There's a consistent typo throughout the code where "registery" should be "registry".

-export const usedKeyRegistery = new Set<string>();
+export const usedKeyRegistry = new Set<string>();
-export const addKeysToRegistery = (usedKeys: Set<string> | string[]) => {
+export const addKeysToRegistry = (usedKeys: Set<string> | string[]) => {
   usedKeys.forEach((key) => {
-    usedKeyRegistery.add(key);
+    usedKeyRegistry.add(key);
   });
 };

Also update all references to this variable and function throughout the codebase.

Also applies to: 495-495

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 612cbc3 and 5278bac.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (27)
  • .github/workflows/auto_test.yml (2 hunks)
  • .github/workflows/release.yml (1 hunks)
  • .prettierignore (0 hunks)
  • .prettierrc (0 hunks)
  • README.md (15 hunks)
  • bin/cli.mjs (1 hunks)
  • biome.json (1 hunks)
  • docs/upgrade-to-v3.md (1 hunks)
  • package.json (3 hunks)
  • release.config.cjs (1 hunks)
  • src/__tests__/html.test.ts (1 hunks)
  • src/__tests__/js-ast.test.ts (63 hunks)
  • src/__tests__/js.test.ts (1 hunks)
  • src/__tests__/utils.test.ts (1 hunks)
  • src/config.ts (1 hunks)
  • src/handlers/css.test.ts (0 hunks)
  • src/handlers/css.ts (1 hunks)
  • src/handlers/html.test.ts (0 hunks)
  • src/handlers/html.ts (1 hunks)
  • src/handlers/js-ast.ts (16 hunks)
  • src/handlers/js.test.ts (0 hunks)
  • src/handlers/js.ts (2 hunks)
  • src/index.ts (3 hunks)
  • src/types.ts (1 hunks)
  • src/utils.test.ts (0 hunks)
  • src/utils.ts (13 hunks)
  • tsconfig.json (2 hunks)
💤 Files with no reviewable changes (6)
  • .prettierignore
  • .prettierrc
  • src/handlers/js.test.ts
  • src/utils.test.ts
  • src/handlers/html.test.ts
  • src/handlers/css.test.ts
🧰 Additional context used
🧬 Code Graph Analysis (11)
src/__tests__/js.test.ts (1)
src/handlers/js.ts (1)
  • searchForwardComponent (13-25)
src/__tests__/utils.test.ts (1)
src/utils.ts (3)
  • findContentBetweenMarker (451-493)
  • getFilenameFromPath (368-371)
  • duplicationCheck (572-575)
src/__tests__/js-ast.test.ts (1)
src/handlers/js-ast.ts (1)
  • searchStringLiterals (80-358)
src/__tests__/html.test.ts (2)
src/types.ts (1)
  • SelectorConversion (9-9)
src/handlers/html.ts (1)
  • obfuscateHtmlClassNames (9-154)
src/handlers/html.ts (3)
src/types.ts (1)
  • SelectorConversion (9-9)
src/utils.ts (1)
  • obfuscateKeys (289-356)
src/handlers/js.ts (1)
  • obfuscateJs (138-196)
src/index.ts (3)
src/types.ts (1)
  • Options (11-55)
src/utils.ts (5)
  • setLogLevel (94-96)
  • log (57-92)
  • findAllFilesWithExt (511-553)
  • getFilenameFromPath (368-371)
  • replaceJsonKeysInFiles (119-287)
src/handlers/css.ts (1)
  • obfuscateCssFiles (142-220)
src/types.ts (1)
src/index.ts (1)
  • OptionalOptions (139-139)
src/handlers/js.ts (3)
src/utils.ts (7)
  • findClosestSymbolPosition (400-449)
  • findContentBetweenMarker (451-493)
  • obfuscateKeys (289-356)
  • addKeysToRegistery (495-499)
  • log (57-92)
  • replaceFirstMatch (555-565)
  • normalizePath (387-389)
src/types.ts (1)
  • SelectorConversion (9-9)
src/handlers/js-ast.ts (1)
  • obfuscateJsWithAst (17-71)
src/handlers/js-ast.ts (2)
src/types.ts (1)
  • SelectorConversion (9-9)
src/utils.ts (1)
  • obfuscateKeys (289-356)
src/config.ts (1)
src/types.ts (2)
  • Options (11-55)
  • OptionalOptions (57-82)
src/utils.ts (3)
src/types.ts (2)
  • LogLevel (7-7)
  • SelectorConversion (9-9)
src/handlers/html.ts (1)
  • obfuscateHtmlClassNames (9-154)
src/handlers/js.ts (1)
  • obfuscateJs (138-196)
🪛 Biome (1.9.4)
src/__tests__/js-ast.test.ts

[error] 51-51: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 82-82: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 126-126: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 156-156: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 192-192: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 225-225: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 264-264: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 305-305: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 348-348: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 387-387: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 426-426: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 473-473: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 522-522: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 561-561: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 592-592: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 621-621: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 658-658: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 695-695: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 730-730: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 771-771: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 812-812: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 857-857: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 902-902: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 935-935: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 970-970: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1003-1003: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1040-1040: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1077-1077: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1118-1118: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1173-1173: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1218-1218: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1257-1257: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1294-1294: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1339-1339: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1390-1390: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1427-1427: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 1458-1458: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

src/utils.ts

[error] 338-338: Reassigning a function parameter is confusing.

The parameter is declared here:

Use a local variable instead.

(lint/style/noParameterAssign)

🪛 LanguageTool
README.md

[style] ~32-~32: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... TailwindCSS 4. - Support nested CSS. - Support CSS idents obfuscation. #### 📌 Config...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~37-~37: Did you mean the adverb “no longer”?
Context: ...d by default. - Default generatorSeed not longer fixed to -1, but a random string. - `...

(NOT_LONGER)


[grammar] ~38-~38: Did you mean the adverb “no longer”?
Context: ...m string. - simplify-seedable mode is not longer supported. Use random mode instead. -...

(NOT_LONGER)


[grammar] ~40-~40: Did you mean the adverb “no longer”?
Context: ...ead. - Deprecated classLength option, not longer supported. - Added ignorePatterns opt...

(NOT_LONGER)


[grammar] ~42-~42: Did you mean the adverb “no longer”?
Context: ...ts that match the regexes or strings. - Not longer preserve TailwindCSS dark mode class na...

(NOT_LONGER)


[misspelling] ~56-~56: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...More configuration options - Now become a independent solution (no need to patch ...

(EN_A_VS_AN)


[misspelling] ~323-~323: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...e original CSS from CSS files if it has a obfuscated version. (NOT recommended ...

(EN_A_VS_AN)

🪛 markdownlint-cli2 (0.17.2)
README.md

20-20: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)


24-24: Blank line inside blockquote
null

(MD028, no-blanks-blockquote)


170-170: Heading levels should only increment by one level at a time
Expected: h4; Actual: h5

(MD001, heading-increment)


188-188: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


318-318: No empty links
null

(MD042, no-empty-links)


319-319: No empty links
null

(MD042, no-empty-links)


323-323: Table pipe style
Expected: leading_and_trailing; Actual: leading_only; Missing trailing pipe

(MD055, table-pipe-style)

🔇 Additional comments (54)
bin/cli.mjs (1)

5-5:

❌ Incorrect review comment

Verify async handling for obfuscateCli() call.

According to the AI summary, obfuscateCli() is now an async function in the updated codebase. If this is correct, the function call should be awaited to ensure the CLI process doesn't exit before the async operation completes.


🏁 Script executed:

#!/bin/bash
# Description: Check if obfuscateCli is an async function that should be awaited
# Expected: Find async function definition and determine if CLI call needs await

# Search for obfuscateCli function definition
ast-grep --pattern 'export const obfuscateCli = async $_'
ast-grep --pattern 'export async function obfuscateCli($_) { $$$ }'
ast-grep --pattern 'const obfuscateCli = async $_'

# Also check for any existing await patterns in CLI usage
rg -A 3 -B 3 'obfuscateCli\(\)'

Length of output: 331


#!/bin/bash
# Search for the obfuscateCli definition in source and dist directories
rg -n 'obfuscateCli' -C 5 src
rg -n 'obfuscateCli' -C 5 dist/index.js

#!/bin/bash
# Search for async definition of obfuscateCli across the repo
rg -n "async function obfuscateCli" -C2 .
rg -n "obfuscateCli\s*=\s*async" -C2 .
rg -n "export.*obfuscateCli" -C2 dist/index.js
rg -n "async.*obfuscateCli" -C2 src

obfuscateCli() remains synchronous – no await required

We searched the entire codebase for an async definition of obfuscateCli (including dist/index.js) and found none. The CLI call in bin/cli.mjs is still a normal, synchronous invocation.

  • bin/cli.mjs (line 5): keep obfuscateCli(); as is.

Likely an incorrect or invalid review comment.

tsconfig.json (2)

16-16: Good addition for consistent line endings.

Adding the newLine: "LF" option ensures consistent line endings across different operating systems.


27-27: Appropriate exclusion of release configuration.

Correctly excluding release.config.cjs from TypeScript compilation since it's a CommonJS configuration file that doesn't need to be processed by the TypeScript compiler.

.github/workflows/auto_test.yml (2)

7-9: Good formatting consistency improvement.

Removing extra spaces in the branch arrays improves YAML formatting consistency.


24-24: Good practice updating to latest action version.

Updating from actions/checkout@v3 to @v4 keeps the workflow current with the latest GitHub Actions version.

release.config.cjs (1)

1-17: Well-configured semantic-release setup with one question.

The semantic-release configuration follows best practices with proper branch setup, standard plugins, and appropriate commit message formatting.

Verify if GitHub plugin should be enabled.

The @semantic-release/github plugin is commented out. This plugin is commonly used to create GitHub releases. Please confirm if this is intentional or if it should be enabled for this project.

package.json (5)

3-3: Major version bump is appropriate for the breaking changes.

The version update to 3.0.0-beta.5 correctly reflects the breaking changes introduced in this release, including deprecated configuration options and architectural changes.


13-13: Good fix for JSON syntax error.

The correction of the pub@beta script syntax resolves a potential JSON parsing issue.


14-15: Well-structured automation scripts.

The addition of semantic-release and lint scripts supports the new automated release workflow and code quality enforcement with Biome.


38-49:

✅ Verification successful

Verify semantic-release and Biome integration.

The migration from Prettier to Biome and addition of semantic-release automation looks good. Ensure the Biome configuration is properly set up in the repository.


🏁 Script executed:

#!/bin/bash
# Check if Biome configuration exists
if [ -f "biome.json" ]; then
    echo "Biome configuration found:"
    cat biome.json
else
    echo "Warning: biome.json not found"
fi

# Check if semantic-release configuration exists
if [ -f "release.config.cjs" ]; then
    echo "Semantic-release configuration found:"
    cat release.config.cjs
else
    echo "Warning: release.config.cjs not found"
fi

# Check if prettier files were removed
if [ -f ".prettierrc" ] || [ -f ".prettierignore" ]; then
    echo "Warning: Prettier configuration files still exist"
    ls -la .prettier*
else
    echo "Prettier configuration files properly removed"
fi

Length of output: 2565


Semantic-release and Biome integration verified

  • biome.json is present and correctly configured for formatting, linting, and import organization.
  • release.config.cjs exists with defined branches and plugins.
  • ✅ Prettier configuration files (.prettierrc / .prettierignore) have been removed.

Everything is set up correctly—no further changes needed.


31-34:

✅ Verification successful

Verify dependency versions for security and compatibility.

The new dependencies css-seasoning and lightningcss-wasm represent a significant architectural change. Please ensure these versions are secure and stable.


🏁 Script executed:

#!/bin/bash
# Check for security advisories on new dependencies
echo "Checking css-seasoning security advisories..."
npm audit --package-lock-only --audit-level moderate 2>/dev/null | grep -A 10 "css-seasoning" || echo "No issues found for css-seasoning"

echo "Checking lightningcss-wasm security advisories..."  
npm audit --package-lock-only --audit-level moderate 2>/dev/null | grep -A 10 "lightningcss-wasm" || echo "No issues found for lightningcss-wasm"

# Check latest versions
echo "Latest css-seasoning version:"
npm view css-seasoning version

echo "Latest lightningcss-wasm version:"
npm view lightningcss-wasm version

Length of output: 768


Dependency audit passed – no security concerns

  • css-seasoning@1.9.0 is the latest release and has no known advisories.
  • lightningcss-wasm@^1.29.3 has no known advisories; its latest minor version is 1.30.1 (no breaking changes).

No further action required.

src/__tests__/js.test.ts (1)

1-128: Excellent comprehensive test suite.

This test file provides thorough coverage of the searchForwardComponent function with well-structured tests that follow the Arrange-Act-Assert pattern. The test cases effectively cover:

  • ✅ Valid component extraction from o.jsx() calls
  • ✅ Proper handling of quoted strings (which should be ignored)
  • ✅ Multiple component matches in single content
  • ✅ Edge cases like empty content and special characters
  • ✅ Complex scenarios with line breaks and nested calls

The tests align perfectly with the regex pattern (?<=\.jsx\()[^,|"|']+ from the implementation, ensuring robust validation of the component extraction logic.

.github/workflows/release.yml (2)

16-20: Appropriate permissions for release workflow.

The permissions are correctly scoped for the release tasks: contents and issues/PR writing for release operations, and id-token for npm provenance.


37-48: Good conditional logic for release vs dry-run.

The conditional execution properly handles different scenarios - full release on push and dry-run on pull requests. Token usage follows security best practices.

src/config.ts (2)

10-37: Excellent deprecation strategy with clear migration path.

The deprecation comments are comprehensive and provide clear guidance for migration to the new structure. This approach maintains backward compatibility while guiding users toward the new API.


25-42: Well-structured configuration reorganization.

The new nested structure for prefix/suffix and ignorePatterns is more logical and provides better organization for selectors vs idents. This aligns well with the CSS transformation engine rebuild mentioned in the PR objectives.

docs/upgrade-to-v3.md (1)

1-23: Excellent migration documentation with comprehensive coverage.

This upgrade guide provides clear and detailed information about breaking changes in v3.0.0. The structured table format makes it easy for users to understand the migration path from v2.x to v3.x.

Key strengths:

  • Clear categorization of configuration changes
  • Helpful notes explaining the reasoning behind changes
  • Proper marking of removed (⛔) and deprecated features
  • Coverage of new features like CSS ident obfuscation

The documentation effectively addresses the major changes mentioned in the PR objectives including TailwindCSS 4 support and nested CSS handling.

src/__tests__/html.test.ts (11)

1-9: Well-structured test imports and setup.

The imports are clean and appropriately typed, correctly importing the SelectorConversion type and the function under test.


11-23: Basic functionality test is well-implemented.

This test correctly verifies the core obfuscation functionality with proper assertions for both the obfuscated content and used keys tracking.


25-43: Excellent test for marker class scoping functionality.

This test properly validates the obfuscate marker class feature, ensuring that obfuscation only occurs within the scope of elements containing the marker class.


45-66: Good negative test case for marker class scoping.

This test correctly verifies that classes outside the marker class scope are not obfuscated, which is crucial for the scoping functionality.


68-88: Comprehensive script tag handling test.

This test covers the important functionality of obfuscating class names within JavaScript code inside script tags. The test data with JSON-like structure is realistic and tests the JavaScript obfuscation integration.


90-101: Good coverage of void tag handling.

This test ensures that self-closing/void tags are handled correctly, which is important for HTML parsing accuracy.


103-116: Appropriate test for HTML comment preservation.

This test verifies that HTML comments are preserved while still obfuscating surrounding content.


118-129: Good edge case coverage for empty and classless HTML.

These tests properly handle edge cases where HTML has no classes or is completely empty, ensuring the function behaves gracefully.

Also applies to: 131-142


144-159: Excellent test for multiple classes per element.

This test verifies that multiple classes on a single element are all obfuscated correctly, which is a common use case.


161-178: Good test for nested HTML structures.

This test covers complex nested HTML with multiple classes across different levels, ensuring the obfuscation works recursively.


237-253: Excellent test for HTML entity handling.

This test addresses a specific issue (#57) related to HTML entities in attributes, ensuring proper escaping and unescaping behavior. The reference to the GitHub issue provides valuable context.

src/__tests__/utils.test.ts (3)

1-11: Clean imports and test organization.

The imports are properly structured and the test file is well-organized with clear section headers.


47-127: Comprehensive test coverage for getFilenameFromPath.

Excellent test suite with thorough edge case coverage including:

  • Unix and Windows paths
  • Files with/without extensions
  • Special characters and spaces
  • Empty strings and directory-only paths
  • Hidden files (dot files)

The test coverage is exemplary and handles all realistic scenarios.


133-221: Thorough test coverage for duplicationCheck.

Comprehensive test suite covering:

  • Arrays with and without duplicates
  • Empty arrays and single-element arrays
  • Case sensitivity
  • Non-adjacent duplicates
  • Similar but unique strings

The test coverage is complete and well-thought-out.

biome.json (3)

8-18: Appropriate file ignore patterns.

The ignored directories are standard for a Node.js/TypeScript project and include all common build artifacts and dependencies.


19-28: Good formatter configuration.

The formatter settings are sensible:

  • 80-character line width (good for readability)
  • 2-space indentation (consistent with modern practices)
  • LF line endings (cross-platform friendly)
  • Import organization enabled (helps maintain clean code)

66-73: Appropriate JavaScript formatting settings.

The JavaScript-specific formatter settings are consistent with modern practices:

  • Double quotes for strings
  • Always use semicolons
  • Trailing commas (helps with git diffs)
src/__tests__/js-ast.test.ts (5)

2-6: LGTM!

Good improvement to use explicit type imports for TypeScript types.


8-10: LGTM!

Good conversion to arrow function syntax for consistency.


51-54: Consistent formatting improvement

Good addition of semicolons to callback return statements for consistent code style.

Also applies to: 82-84, 126-128, 156-158, 192-194, 225-227, 264-266, 305-307, 348-350, 387-389, 426-428, 473-475, 522-524, 561-563, 592-594, 621-623, 658-660, 695-697, 730-732, 771-773, 812-814, 857-859, 902-904, 935-937, 970-972, 1003-1005, 1040-1042, 1077-1079, 1118-1120, 1173-1175, 1218-1220, 1257-1259, 1294-1296, 1339-1341, 1390-1392, 1427-1429, 1458-1461

🧰 Tools
🪛 Biome (1.9.4)

[error] 51-51: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


1395-1401: LGTM!

Clear formatting of the expected array with each element on its own line improves readability.


1463-1467: Helpful documentation added

Good addition of comments to clarify the distinction between raw and cooked values in template literals.

src/handlers/js-ast.ts (5)

1-5: LGTM!

Good reorganization of imports with explicit type imports.


17-22: LGTM!

Good conversion to arrow function syntax for consistency across the codebase.


41-52: Good improvement in variable handling

Using a new newStr variable instead of modifying the original str parameter is a better practice that avoids potential side effects and makes the code more predictable.


80-86: Improved function signature

Good improvements:

  1. Converted to arrow function for consistency
  2. Refined callback return type from void | string to undefined | string which is more semantically correct

115-127: Helpful comments added throughout

Good addition of descriptive comments for each AST node type handler, making the code more maintainable and easier to understand.

Also applies to: 137-143, 157-176, 176-190, 190-204, 204-212, 212-218, 218-226, 226-234, 292-310, 310-324, 324-347

README.md (1)

20-27: LGTM!

Clear announcement of version 3 with appropriate warnings about breaking changes and migration guide reference.

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

20-20: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)


24-24: Blank line inside blockquote
null

(MD028, no-blanks-blockquote)

src/index.ts (5)

1-14: LGTM!

Good improvements:

  • Using explicit node: prefix for Node.js built-in modules
  • Separating type imports
  • Clear module organization

16-16: LGTM!

Appropriate conversion to async function to handle the new asynchronous CSS obfuscation.


41-54: Good backward compatibility handling

Well-implemented backward compatibility for the deprecated classIgnore option, with a TODO comment for future removal.


72-84: Good addition of conversion table persistence

The explicit saving of conversion tables to a JSON file is a good improvement for debugging and analysis purposes.


104-137: LGTM!

Good conversion to async function to properly await the obfuscation process.

src/handlers/html.ts (1)

118-119: Good improvement: Using cssUnescape for proper selector handling

The use of cssUnescape before slicing ensures that escaped CSS selectors are properly handled. This is a necessary improvement for correctness.

src/types.ts (1)

1-86: Well-structured type definitions with clear deprecation notices

The type definitions have been modernized with:

  • Clear deprecation notices for legacy fields
  • Proper integration with external CSS processing library types
  • Improved structure with nested prefix and suffix options
  • Appropriate migration path indicated in deprecation comments
src/handlers/js.ts (1)

13-196: Clean refactoring to arrow functions with improved code style

The refactoring successfully:

  • Converts functions to arrow functions for consistency
  • Maintains all existing functionality
  • Improves code by using a local newContent variable instead of modifying the input
  • Preserves proper error handling with AST fallback
src/utils.ts (1)

17-47: Well-implemented path filtering function!

The shouldIncludePath function provides a clean, unified approach to path filtering with proper normalization and priority handling (blacklist > whitelist).

Comment on lines +31 to +35
- name: Edit First Release Version
run: sed -i 's/\( FIRST_RELEASE\) = "1.0.0"/\1 = "3.0.0"/' node_modules/semantic-release/lib/definitions/constants.js

- name: Edit First Prerelease Version
run: sed -i 's/\( FIRSTPRERELEASE\) = "1"/\1 = "3"/' node_modules/semantic-release/lib/definitions/constants.js
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider a more robust approach for version configuration.

The sed commands modifying semantic-release internal constants are fragile and could break with package updates. Consider using semantic-release configuration options instead.

A more maintainable approach would be to configure this in release.config.cjs:

module.exports = {
  branches: [
    'main',
    { name: 'beta', prerelease: true }
  ],
  plugins: [
    '@semantic-release/commit-analyzer',
    '@semantic-release/release-notes-generator',
    ['@semantic-release/npm', {
      tarballDir: 'dist'
    }],
    ['@semantic-release/github', {
      assets: 'dist/*.tgz'
    }]
  ],
  tagFormat: 'v${version}',
  preset: 'angular'
};

Then remove the sed commands from the workflow.

🤖 Prompt for AI Agents
In .github/workflows/release.yml around lines 31 to 35, the sed commands
directly modify semantic-release internal constants, which is fragile and prone
to break with updates. Instead, remove these sed commands and configure the
versioning and prerelease behavior properly in a release.config.cjs file by
specifying branches, plugins, tagFormat, and preset options as per
semantic-release documentation. This approach is more maintainable and less
error-prone.

/**
* Experimental feature
*/
enableJsAst: true, // Whether to obfuscate JS files using abstract syntax tree parser. (Experimental feature)
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify stability of enableJsAst feature before enabling by default.

Changing enableJsAst from false to true by default could impact users since it was marked as experimental. Ensure this feature is stable enough for production use.


🏁 Script executed:

#!/bin/bash
# Check for any TODO, FIXME, or experimental comments related to JST AST
echo "Checking for experimental or unstable code in JS AST handling:"
rg -A 5 -B 5 "(TODO|FIXME|experimental|unstable|beta)" src/handlers/js-ast.ts src/handlers/js.ts

# Check test coverage for JS AST functionality
echo "Checking JS AST test coverage:"
fd "js.*test" src/ | head -10

Length of output: 390


Update annotation for enableJsAst default change

We’ve confirmed there are no lingering “experimental” markers in the implementation and that dedicated tests (js-ast.test.ts, js.test.ts) cover this feature. Since enableJsAst is now on by default, please:

  • In src/config.ts (line 60), remove or update the “(Experimental feature)” comment to reflect its production readiness.
  • Add a note in your CHANGELOG.md (or README) explaining that enableJsAst is now enabled by default.

This will keep the docs in sync with the code and avoid confusing users.

🤖 Prompt for AI Agents
In src/config.ts at line 60, update the comment for enableJsAst by removing or
revising the "(Experimental feature)" note to indicate it is now stable and
enabled by default. Additionally, add a corresponding entry in CHANGELOG.md or
README to document that enableJsAst is enabled by default, ensuring users are
informed of this change.

markers: ["next-css-obfuscation"], // Classes that indicate component(s) need to obfuscate.
removeMarkersAfterObfuscated: true, // Remove the obfuscation markers from HTML elements after obfuscation.
removeOriginalCss: false, // Delete original CSS from CSS files if it has a obfuscated version.
generatorSeed: undefined, // The seed for the random generator. "undefined" means use random seed.
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify the impact of changing generatorSeed default.

The change from "-1" to undefined for random seed generation is a breaking change that could affect reproducibility of obfuscated output.


🏁 Script executed:

#!/bin/bash
# Check how generatorSeed is used in the codebase
echo "Searching for generatorSeed usage:"
rg -A 3 -B 3 "generatorSeed" --type ts --type js

# Check if there are any migration notes about this change
echo "Checking for migration documentation:"
fd -e md . | xargs rg -l "generatorSeed\|generator.*seed" || echo "No migration docs found mentioning generatorSeed"

Length of output: 3626


I don’t see a definition for stringToNumber in the grep results—let’s locate it to confirm how "-1" was handled versus undefined. This will show whether -1 triggered a reproducible seed or was treated as “use random,” and what stringToNumber(undefined) yields.


🏁 Script executed:

#!/bin/bash
echo "Finding stringToNumber definition and usages..."
rg -A5 -B5 "function stringToNumber" --type ts || \
rg -A5 -B5 "stringToNumber" --type ts

Length of output: 1685


Here’s where the handler defaults now inject a random/timestamp seed when generatorSeed is undefined:

This will confirm the exact lines in src/handlers/css.ts where the change to undefined in src/config.ts becomes a nondeterministic seed.


🏁 Script executed:

#!/bin/bash
echo "Location of Math.random default:"
rg -n "generatorSeed = Math.random" src/handlers/css.ts
echo "Location of Date-based default:"
rg -n "generatorSeed = new Date" src/handlers/css.ts

Length of output: 415


Document a reproducible default or restore the fixed seed

Changing generatorSeed from the string "-1" (which yielded a constant seed via stringToNumber("-1")) to undefined now falls back to nondeterministic generators in both CSS handlers—one using Math.random() and the other Date.now(). This will break any previously reproducible obfuscation outputs.

Please address this by either reinstating a fixed default seed or explicitly documenting the breaking change and providing a migration path.

• src/config.ts:55

-  generatorSeed: "-1", // was a constant, reproducible seed
+  generatorSeed: undefined, // now uses Math.random()/Date.now(), non-reproducible

• src/handlers/css.ts:36

  generatorSeed = Math.random().toString().slice(2, 10), // random 8-digit seed

• src/handlers/css.ts:151

  generatorSeed = new Date().getTime().toString(),     // timestamp seed

– Either restore a deterministic default (e.g. "-1") or add clear migration/docs warning that omitting generatorSeed will produce non-reproducible output.

🤖 Prompt for AI Agents
In src/config.ts at line 55, the default value of generatorSeed was changed from
the string "-1" to undefined, causing the seed to become nondeterministic and
breaking reproducibility. To fix this, either revert the default back to the
fixed string "-1" to maintain deterministic output or, if keeping undefined as
default, add clear documentation and migration notes warning users that omitting
generatorSeed now results in non-reproducible obfuscation outputs. Also verify
and update related code in src/handlers/css.ts at lines 36 and 151 to align with
this decision.

Comment on lines +12 to +41
describe("findContentBetweenMarker", () => {
it("should return the correct content between markers", () => {
const content = "123{{4}5{67}8}901{2345678}9";
const targetStr = "5";
const openSymbol = "{";
const closeSymbol = "}";

const expectedOutput = ["{4}5{67}8", "2345678"];

const result = findContentBetweenMarker(
content,
targetStr,
openSymbol,
closeSymbol,
);
expect(result).toEqual(expectedOutput);
});

// it('should return the correct content between (if marker length > 1)', () => {
// const content = '[Hello_0 [[Hello_1]]! Hello_2 [[Hello_3]]!]';
// const targetStr = 'He';
// const openSymbol = '[[';
// const closeSymbol = ']]';

// const expectedOutput = ['Hello_1', 'Hello_3'];

// const result = findContentBetweenMarker(content, targetStr, openSymbol, closeSymbol);
// expect(result).toEqual(expectedOutput);
// });
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Good test for findContentBetweenMarker but missing edge cases.

The test correctly validates the core functionality, but there are some areas for improvement:

  1. Commented out test: The commented test for multi-character markers should be uncommented if the function supports it, or removed if not supported.
  2. Missing edge cases: Consider adding tests for:
    • Same open and close markers (should throw error per the function implementation)
    • No target string found
    • Unmatched markers
    • Empty content
+  it("should throw error when open and close markers are the same", () => {
+    expect(() => {
+      findContentBetweenMarker("test", "t", "{", "{");
+    }).toThrow("openMarker and closeMarker can not be the same");
+  });
+
+  it("should return empty array when target string not found", () => {
+    const result = findContentBetweenMarker("abc", "x", "{", "}");
+    expect(result).toEqual([]);
+  });
📝 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
describe("findContentBetweenMarker", () => {
it("should return the correct content between markers", () => {
const content = "123{{4}5{67}8}901{2345678}9";
const targetStr = "5";
const openSymbol = "{";
const closeSymbol = "}";
const expectedOutput = ["{4}5{67}8", "2345678"];
const result = findContentBetweenMarker(
content,
targetStr,
openSymbol,
closeSymbol,
);
expect(result).toEqual(expectedOutput);
});
// it('should return the correct content between (if marker length > 1)', () => {
// const content = '[Hello_0 [[Hello_1]]! Hello_2 [[Hello_3]]!]';
// const targetStr = 'He';
// const openSymbol = '[[';
// const closeSymbol = ']]';
// const expectedOutput = ['Hello_1', 'Hello_3'];
// const result = findContentBetweenMarker(content, targetStr, openSymbol, closeSymbol);
// expect(result).toEqual(expectedOutput);
// });
});
describe("findContentBetweenMarker", () => {
it("should return the correct content between markers", () => {
const content = "123{{4}5{67}8}901{2345678}9";
const targetStr = "5";
const openSymbol = "{";
const closeSymbol = "}";
const expectedOutput = ["{4}5{67}8", "2345678"];
const result = findContentBetweenMarker(
content,
targetStr,
openSymbol,
closeSymbol,
);
expect(result).toEqual(expectedOutput);
});
// it('should return the correct content between (if marker length > 1)', () => {
// const content = '[Hello_0 [[Hello_1]]! Hello_2 [[Hello_3]]!]';
// const targetStr = 'He';
// const openSymbol = '[[';
// const closeSymbol = ']]';
//
// const expectedOutput = ['Hello_1', 'Hello_3'];
//
// const result = findContentBetweenMarker(content, targetStr, openSymbol, closeSymbol);
// expect(result).toEqual(expectedOutput);
// });
it("should throw error when open and close markers are the same", () => {
expect(() => {
findContentBetweenMarker("test", "t", "{", "{");
}).toThrow("openMarker and closeMarker can not be the same");
});
it("should return empty array when target string not found", () => {
const result = findContentBetweenMarker("abc", "x", "{", "}");
expect(result).toEqual([]);
});
});
🤖 Prompt for AI Agents
In src/__tests__/utils.test.ts between lines 12 and 41, the existing test for
findContentBetweenMarker covers basic functionality but lacks edge case
coverage. Uncomment the test for multi-character markers if the function
supports it, or remove it if not. Add new tests for edge cases including when
open and close markers are the same (expecting an error), when the target string
is not found, when there are unmatched markers in the content, and when the
content is empty. These additions will ensure more robust validation of the
function's behavior.

Comment on lines +37 to +38
- Default `generatorSeed` not longer fixed to `-1`, but a random string.
- `simplify-seedable` mode is not longer supported. Use `random` mode instead.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix grammatical errors: "not longer" → "no longer"

Multiple instances of incorrect grammar that should be fixed.

-  - Default `generatorSeed` not longer fixed to `-1`, but a random string.
-  - `simplify-seedable` mode is not longer supported. Use `random` mode instead.
+  - Default `generatorSeed` no longer fixed to `-1`, but a random string.
+  - `simplify-seedable` mode is no longer supported. Use `random` mode instead.
-  - Deprecated `classLength` option, not longer supported.
+  - Deprecated `classLength` option, no longer supported.
-  - Not longer preserve TailwindCSS dark mode class names (ie `.dark`). Add the dark mode class name to the `ignorePatterns.selectors` option to preserve it.
+  - No longer preserve TailwindCSS dark mode class names (ie `.dark`). Add the dark mode class name to the `ignorePatterns.selectors` option to preserve it.

Also applies to: 40-40, 42-42

🧰 Tools
🪛 LanguageTool

[grammar] ~37-~37: Did you mean the adverb “no longer”?
Context: ...d by default. - Default generatorSeed not longer fixed to -1, but a random string. - `...

(NOT_LONGER)


[grammar] ~38-~38: Did you mean the adverb “no longer”?
Context: ...m string. - simplify-seedable mode is not longer supported. Use random mode instead. -...

(NOT_LONGER)

🤖 Prompt for AI Agents
In README.md at lines 37-38, 40, and 42, correct the grammatical error "not
longer" to "no longer" in the sentences describing the default `generatorSeed`
and the `simplify-seedable` mode. Review these lines and replace all instances
of "not longer" with "no longer" to fix the grammar.

refreshClassConversionJson: false, // recommended set to true if not in production
allowExtensions: [".jsx", ".tsx", ".js", ".ts", ".html", ".rsc"],
};

```
##### Partially obfuscate

##### Partially obfuscate
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix heading level hierarchy

Another heading level issue that should be fixed for consistency.

-   ##### Partially obfuscate
+   #### Partially obfuscate
📝 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
##### Partially obfuscate
#### Partially obfuscate
🤖 Prompt for AI Agents
In README.md at line 183, the heading "Partially obfuscate" has an incorrect
heading level that disrupts the hierarchy. Adjust the markdown heading level to
ensure it follows the previous headings consistently, maintaining proper nesting
and structure.

options.classConversionJsonFolderPath,
"conversion.json",
);
console.log({ jsonPath });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove debug console.log statement

This appears to be debug code that should be removed before production.

-  console.log({ jsonPath });
📝 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
console.log({ jsonPath });
🤖 Prompt for AI Agents
In src/index.ts at line 77, remove the debug console.log statement that outputs
the jsonPath variable, as it is not needed in production code and should be
cleaned up.

Comment on lines +72 to +76
const { obfuscatedContent, usedKeys: _usedKeys } = obfuscateKeys(
selectorConversion,
attribs.class,
[],
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential issue: contentIgnoreRegexes not applied to HTML class obfuscation

The function receives contentIgnoreRegexes as a parameter but passes an empty array to obfuscateKeys when obfuscating HTML class attributes. This means content ignore patterns won't be applied to class names in HTML attributes, which could be inconsistent with how JavaScript content is handled (where contentIgnoreRegexes is properly passed on line 124).

Consider applying the content ignore patterns consistently:

const { obfuscatedContent, usedKeys: _usedKeys } = obfuscateKeys(
  selectorConversion,
  attribs.class,
-  [],
+  contentIgnoreRegexes,
);
📝 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
const { obfuscatedContent, usedKeys: _usedKeys } = obfuscateKeys(
selectorConversion,
attribs.class,
[],
);
const { obfuscatedContent, usedKeys: _usedKeys } = obfuscateKeys(
selectorConversion,
attribs.class,
contentIgnoreRegexes,
);
🤖 Prompt for AI Agents
In src/handlers/html.ts around lines 72 to 76, the call to obfuscateKeys for
HTML class attributes incorrectly passes an empty array instead of the
contentIgnoreRegexes parameter, causing ignore patterns not to be applied. Fix
this by replacing the empty array argument with the contentIgnoreRegexes
parameter to ensure consistent application of ignore patterns during class name
obfuscation.

Comment on lines +189 to +215
} else {
return item;
}
});
}
cssObj.stylesheet.rules = recursive(cssObj.stylesheet.rules).flat();
return cssObj;
}

function renameCssSelector(oldSelector: string, newSelector: string, cssObj: any) {
function recursive(rules: any[]): any[] {
return rules.map((item: any) => {
if (item.rules) {
return { ...item, rules: recursive(item.rules) };
} else if (item.selectors) {
// remove empty selectors
item.selectors = item.selectors.filter((selector: any) => selector !== "");

let updatedSelectors = item.selectors.map((selector: any) =>
selector === oldSelector ? newSelector : selector
);

return { ...item, selectors: updatedSelectors };
} else {
return item;
}
});
}

cssObj.stylesheet.rules = recursive(cssObj.stylesheet.rules);
return cssObj;
}

function obfuscateCss(
selectorConversion: SelectorConversion,
cssPath: string,
replaceOriginalSelector: boolean = false,
isFullObfuscation: boolean = false,
outCssPath?: string,
) {
if (!outCssPath) {
outCssPath = cssPath;
} else if (!fs.existsSync(path.dirname(outCssPath))) {
fs.mkdirSync(path.dirname(outCssPath));
}

let cssContent = fs.readFileSync(cssPath, "utf-8");

let cssObj = css.parse(cssContent);
const cssRulesCount = cssObj.stylesheet.rules.length;

if (isFullObfuscation) {
Object.keys(selectorConversion).forEach((key) => {
usedKeyRegistery.add(key);
});
} else {
// join all selectors start with ":" (eg. ":is")
Object.keys(selectorConversion).forEach((key) => {
if (key.startsWith(":")) {
usedKeyRegistery.add(key);
}
});

// join all selectors with action selectors
const actionSelectors = getAllSelector(cssObj).filter((selector) => selector.match(findActionSelectorsRegex));
actionSelectors.forEach((actionSelector) => {
usedKeyRegistery.add(actionSelector);
});

// join all Tailwind CSS [child] selectors (eg. ".\[\&_\.side-box\]\:absolute .side-box")
const tailwindCssChildSelectors = getAllSelector(cssObj).filter((selector) => selector.startsWith(".\\["));
tailwindCssChildSelectors.forEach((tailwindCssChildSelector) => {
usedKeyRegistery.add(tailwindCssChildSelector);
});

// join all child selectors (eg. ">*")
const universalSelectors = getAllSelector(cssObj).filter((selector) => selector.includes(">"));
universalSelectors.forEach((universalSelector) => {
usedKeyRegistery.add(universalSelector);
});
}

// modify css rules
usedKeyRegistery.forEach((key) => {
const originalSelectorName = key;
const obfuscatedSelectorName = selectorConversion[key];
if (obfuscatedSelectorName) {
if (replaceOriginalSelector) {
cssObj = renameCssSelector(originalSelectorName, selectorConversion[key], cssObj);
} else {
cssObj = copyCssData(originalSelectorName, selectorConversion[key], cssObj);
}
}
Object.entries(newConversionTables.idents).forEach(([key, value]) => {
if (!tables.idents[key]) {
// If it doesn't exist, create a new entry
tables.idents[key] = value;
}
});
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace forEach with for...of loop to properly await async operations.

Using forEach with an async callback doesn't wait for the promises to resolve. This means the function might return before all CSS files are processed, leading to incomplete conversion tables.

Replace the forEach loop with a for...of loop:

-  cssPaths.forEach(async (cssPath) => {
-    const { conversionTables: newConversionTables } = await obfuscateCss({
+  for (const cssPath of cssPaths) {
+    const { conversionTables: newConversionTables } = obfuscateCss({
       cssPath: cssPath,
       conversionTables: conversionTables,
 
       prefix,
       suffix,
       mode,
       ignorePatterns,
       generatorSeed,
       removeOriginalCss,
     });
 
     // Merge the conversion tables
     Object.entries(newConversionTables.selectors).forEach(([key, value]) => {
       if (!tables.selectors[key]) {
         // If it doesn't exist, create a new entry
         tables.selectors[key] = value;
       }
     });
     Object.entries(newConversionTables.idents).forEach(([key, value]) => {
       if (!tables.idents[key]) {
         // If it doesn't exist, create a new entry
         tables.idents[key] = value;
       }
     });
-  });
+  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/handlers/css.ts between lines 189 and 215, the async operations inside
the forEach loop are not awaited, causing potential incomplete processing of CSS
files. Replace the forEach loop with a for...of loop to properly await each
asynchronous call to obfuscateCss, ensuring all CSS files are processed
sequentially and the conversion tables are fully merged before proceeding.

Comment on lines +60 to +74
let transformerMode: TransformProps["mode"] =
mode === "simplify" ? "minimal" : "hash";
if (!transformerMode) {
// @ts-expect-error - "simplify-seedable" is deprecated but for backward compatibility
if (mode === "simplify-seedable") {
log(
"warn",
"CSS obfuscation",
"The 'simplify-seedable' mode is deprecated, please use 'random' or 'simplify' instead. Now will fall back to 'random' mode.",
);
transformerMode = "hash";
}
log("error", "CSS obfuscation", `Invalid mode: ${mode}`);
throw new Error(`Invalid mode: ${mode}`);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix logical error in deprecated mode handling.

The code logs a warning about falling back to 'random' mode for the deprecated 'simplify-seedable' mode, but then continues to throw an error. The error should only be thrown for truly invalid modes.

Apply this fix to handle the deprecated mode correctly:

 let transformerMode: TransformProps["mode"] =
   mode === "simplify" ? "minimal" : "hash";
 if (!transformerMode) {
   // @ts-expect-error - "simplify-seedable" is deprecated but for backward compatibility
   if (mode === "simplify-seedable") {
     log(
       "warn",
       "CSS obfuscation",
       "The 'simplify-seedable' mode is deprecated, please use 'random' or 'simplify' instead. Now will fall back to 'random' mode.",
     );
     transformerMode = "hash";
-    }
+  } else {
     log("error", "CSS obfuscation", `Invalid mode: ${mode}`);
     throw new Error(`Invalid mode: ${mode}`);
+  }
 }
📝 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
let transformerMode: TransformProps["mode"] =
mode === "simplify" ? "minimal" : "hash";
if (!transformerMode) {
// @ts-expect-error - "simplify-seedable" is deprecated but for backward compatibility
if (mode === "simplify-seedable") {
log(
"warn",
"CSS obfuscation",
"The 'simplify-seedable' mode is deprecated, please use 'random' or 'simplify' instead. Now will fall back to 'random' mode.",
);
transformerMode = "hash";
}
log("error", "CSS obfuscation", `Invalid mode: ${mode}`);
throw new Error(`Invalid mode: ${mode}`);
}
let transformerMode: TransformProps["mode"] =
mode === "simplify" ? "minimal" : "hash";
if (!transformerMode) {
// @ts-expect-error - "simplify-seedable" is deprecated but for backward compatibility
if (mode === "simplify-seedable") {
log(
"warn",
"CSS obfuscation",
"The 'simplify-seedable' mode is deprecated, please use 'random' or 'simplify' instead. Now will fall back to 'random' mode.",
);
transformerMode = "hash";
} else {
log("error", "CSS obfuscation", `Invalid mode: ${mode}`);
throw new Error(`Invalid mode: ${mode}`);
}
}
🤖 Prompt for AI Agents
In src/handlers/css.ts around lines 60 to 74, the code incorrectly throws an
error after logging a warning for the deprecated 'simplify-seedable' mode, which
should instead be handled as a valid fallback. Modify the logic so that when
mode is 'simplify-seedable', it logs the warning and sets transformerMode to
'hash' without throwing an error. Only throw an error if the mode is neither
valid nor the deprecated 'simplify-seedable'.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @soranoo - I've reviewed your changes - here's some feedback:

  • There is a lot of commented-out legacy code remaining in the CSS and HTML handlers—please remove unused code blocks to keep the codebase clean and maintainable.
  • The replaceJsonKeysInFiles utility has grown very large and deeply nested—consider extracting parts of its logic into smaller helper functions or modules to improve readability.
  • The conversion-table JSON is written both in the CSS handler and again in index.ts—you might centralize that responsibility to avoid duplicate file-writing logic.
Here's what I looked at during the review
  • 🟡 General issues: 5 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

// exactMatchRegex = new RegExp(`([\\s"'\\\`]|^)(${keyUse})(?=$|[\\s"'\\\`])`, 'g'); // match exact wording & avoid ` ' ""

const replacement = `$1` + selectorConversion[key].slice(1).replace(/\\/g, "");
const replacement = `$1${selectorConversion[key].slice(1).replace(/\\/g, "").slice(1)}`;
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Extra .slice(1) drops the first character of the replacement value

The second .slice(1) likely removes a needed character from the replacement. Removing it should preserve the intended class name.

idents: {},
};

cssPaths.forEach(async (cssPath) => {
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): Async callback in forEach isn’t awaited

Use a for...of loop or Promise.all to ensure all async operations complete before returning.

Suggested implementation:

  for (const cssPath of cssPaths) {
    const { conversionTables: newConversionTables } = await obfuscateCss({
      cssPath: cssPath,
      conversionTables: conversionTables,

      prefix,
      suffix,
      mode,
      ignorePatterns,
      generatorSeed,
      removeOriginalCss,
    });

If the containing function is not already async, you must add the async keyword to its declaration.

options.classConversionJsonFolderPath,
"conversion.json",
);
console.log({ jsonPath });
Copy link

Choose a reason for hiding this comment

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

nitpick: Remove leftover debug log

Comment on lines +44 to +53
if (options.classIgnore) {
if (ignorePatterns && !Array.isArray(ignorePatterns)) {
ignorePatterns.selectors?.push(...options.classIgnore);
} else {
ignorePatterns = {
selectors: [...(ignorePatterns || []), ...options.classIgnore],
idents: [...(ignorePatterns || [])],
};
}
}
Copy link

Choose a reason for hiding this comment

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

suggestion: Deprecated classIgnore is always defined, consider checking its length

Since options.classIgnore defaults to an empty array, check options.classIgnore.length > 0 before merging to avoid unnecessary operations.

Suggested change
if (options.classIgnore) {
if (ignorePatterns && !Array.isArray(ignorePatterns)) {
ignorePatterns.selectors?.push(...options.classIgnore);
} else {
ignorePatterns = {
selectors: [...(ignorePatterns || []), ...options.classIgnore],
idents: [...(ignorePatterns || [])],
};
}
}
if (options.classIgnore && options.classIgnore.length > 0) {
if (ignorePatterns && !Array.isArray(ignorePatterns)) {
ignorePatterns.selectors?.push(...options.classIgnore);
} else {
ignorePatterns = {
selectors: [...(ignorePatterns || []), ...options.classIgnore],
idents: [...(ignorePatterns || [])],
};
}
}

* @param blackListedFolderPaths - Paths to exclude (higher priority than whitelist)
* @returns - True if the path should be included, false otherwise
*/
const shouldIncludePath = (
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): Whitelist/blacklist matching using includes may match partial segments

Using String.includes may result in unintended matches within folder names. Use path boundaries or regex anchors to match entire folder names only.

Suggested implementation:

  // Check if the path is blacklisted (higher priority)
  const isBlacklisted = blackListedFolderPaths.some((excludePath) => {
    if (typeof excludePath === "string") {
      // Match only whole path segments
      const pattern = new RegExp(`(^|/)${escapeRegExp(excludePath)}(/|$)`);
      return pattern.test(normalizedPath);
    }
    return excludePath.test(normalizedPath);
  // Check if the path is whitelisted (if any whitelist is provided)
  if (whiteListedFolderPaths.length > 0) {
    const isWhitelisted = whiteListedFolderPaths.some((includePath) => {
      if (typeof includePath === "string") {
        // Match only whole path segments
        const pattern = new RegExp(`(^|/)${escapeRegExp(includePath)}(/|$)`);
        return pattern.test(normalizedPath);
      }
      return includePath.test(normalizedPath);
    });
    return isWhitelisted;
  }
/**
 * Escapes special characters for use in a RegExp constructor
 */
function escapeRegExp(str: string): string {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}


// Reconstruct ignore patterns for backward compatibility
// TODO: Remove in the next major version
let ignorePatterns = options.ignorePatterns;
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
let ignorePatterns = options.ignorePatterns;
let {ignorePatterns} = options;


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

Comment on lines 105 to 109
const argv = yargs.option("config", {
alias: "c",
type: "string",
description: "Path to the config file"
description: "Path to the config file",
}).argv;
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
const argv = yargs.option("config", {
alias: "c",
type: "string",
description: "Path to the config file"
description: "Path to the config file",
}).argv;
const {argv} = yargs.option("config", {
alias: "c",
type: "string",
description: "Path to the config file",
});


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

Comment on lines +37 to +46
const isWhitelisted =
whiteListedFolderPaths.length === 0 ||
whiteListedFolderPaths.some((includePath) => {
if (typeof includePath === "string") {
return normalizedPath.includes(includePath);
}
return includePath.test(normalizedPath);
});

return isWhitelisted;
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const isWhitelisted =
whiteListedFolderPaths.length === 0 ||
whiteListedFolderPaths.some((includePath) => {
if (typeof includePath === "string") {
return normalizedPath.includes(includePath);
}
return includePath.test(normalizedPath);
});
return isWhitelisted;
return whiteListedFolderPaths.length === 0 ||
whiteListedFolderPaths.some((includePath) => {
if (typeof includePath === "string") {
return normalizedPath.includes(includePath);
}
return includePath.test(normalizedPath);
});


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

Copy link

@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: 0

🧹 Nitpick comments (9)
README.md (9)

17-17: Maintain Heading Hierarchy
The heading ### 🎉 Version 3 has NOW been released jumps from an h1 title without an h2 in between, which violates heading level hierarchy. Consider changing ### to ## to ensure proper nesting.

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

17-17: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)


27-29: Enhance Bullet List Variation
All three bullets start with “Support”. For improved readability, consider rephrasing one or more items, e.g.:

  • “Add support for TailwindCSS 4”
  • “Introduce nested CSS processing”
  • “Enable CSS idents obfuscation”
🧰 Tools
🪛 LanguageTool

[style] ~29-~29: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... TailwindCSS 4. - Support nested CSS. - Support CSS idents obfuscation. #### 📌 Config...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


34-34: Fix Grammar: “not longer” → “no longer” and add “the”
Replace with:

- Default `generatorSeed` not longer fixed to `-1`, but a random string.
+ The default `generatorSeed` is no longer fixed to `-1`, but uses a random string.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~34-~34: You might be missing the article “the” here.
Context: ...stoption is now enabled by default. - DefaultgeneratorSeednot longer fixed to-1...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[grammar] ~34-~34: Did you mean the adverb “no longer”?
Context: ...d by default. - Default generatorSeed not longer fixed to -1, but a random string. - `...

(NOT_LONGER)


35-35: Fix Grammar: “not longer” → “no longer”
Replace with:

- `simplify-seedable` mode is not longer supported. Use `random` mode instead.
+ `simplify-seedable` mode is no longer supported. Use the `random` mode instead.
🧰 Tools
🪛 LanguageTool

[grammar] ~35-~35: Did you mean the adverb “no longer”?
Context: ...m string. - simplify-seedable mode is not longer supported. Use random mode instead. -...

(NOT_LONGER)


37-37: Fix Grammar: “not longer” → “no longer”
Replace with:

- Deprecated `classLength` option, not longer supported.
+ Deprecated `classLength` option, no longer supported.
🧰 Tools
🪛 LanguageTool

[grammar] ~37-~37: Did you mean the adverb “no longer”?
Context: ...ead. - Deprecated classLength option, not longer supported. - Added ignorePatterns opt...

(NOT_LONGER)


39-39: Fix Grammar: “Not longer preserve” → “No longer preserve”
Replace with:

- Not longer preserve TailwindCSS dark mode class names (ie `.dark`). Add the dark mode...
+ No longer preserve TailwindCSS dark mode class names (i.e., `.dark`). Add the dark mode...
🧰 Tools
🪛 LanguageTool

[grammar] ~39-~39: Did you mean the adverb “no longer”?
Context: ...ts that match the regexes or strings. - Not longer preserve TailwindCSS dark mode class na...

(NOT_LONGER)


167-167: Fix Heading Hierarchy
The heading ##### Obfuscate all files should use #### instead of ##### to maintain a single-level increment under its parent section.

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

167-167: Heading levels should only increment by one level at a time
Expected: h4; Actual: h5

(MD001, heading-increment)


180-180: Fix Heading Hierarchy
The heading ##### Partially obfuscate should be #### to ensure proper nesting and consistency with prior headings.


320-320: Fix Table Formatting: missing trailing pipe
Add a trailing | to close the table row:

-|removeOriginalCss|boolean|false|Delete original CSS...|
+|removeOriginalCss|boolean|false|Delete original CSS... (*NOT recommended* using in partial obfuscation)|
🧰 Tools
🪛 LanguageTool

[misspelling] ~320-~320: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...e original CSS from CSS files if it has a obfuscated version. (NOT recommended ...

(EN_A_VS_AN)

🪛 markdownlint-cli2 (0.17.2)

320-320: Table pipe style
Expected: leading_and_trailing; Actual: leading_only; Missing trailing pipe

(MD055, table-pipe-style)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5278bac and 82e440e.

⛔ Files ignored due to path filters (1)
  • demos/next14-app-router/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • README.md (15 hunks)
  • demos/next14-app-router/next-css-obfuscator.config.cjs (1 hunks)
  • demos/next14-app-router/package.json (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • demos/next14-app-router/package.json
  • demos/next14-app-router/next-css-obfuscator.config.cjs
🧰 Additional context used
🪛 LanguageTool
README.md

[style] ~29-~29: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... TailwindCSS 4. - Support nested CSS. - Support CSS idents obfuscation. #### 📌 Config...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[uncategorized] ~34-~34: You might be missing the article “the” here.
Context: ...stoption is now enabled by default. - DefaultgeneratorSeednot longer fixed to-1...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[grammar] ~34-~34: Did you mean the adverb “no longer”?
Context: ...d by default. - Default generatorSeed not longer fixed to -1, but a random string. - `...

(NOT_LONGER)


[grammar] ~35-~35: Did you mean the adverb “no longer”?
Context: ...m string. - simplify-seedable mode is not longer supported. Use random mode instead. -...

(NOT_LONGER)


[grammar] ~37-~37: Did you mean the adverb “no longer”?
Context: ...ead. - Deprecated classLength option, not longer supported. - Added ignorePatterns opt...

(NOT_LONGER)


[grammar] ~39-~39: Did you mean the adverb “no longer”?
Context: ...ts that match the regexes or strings. - Not longer preserve TailwindCSS dark mode class na...

(NOT_LONGER)


[misspelling] ~53-~53: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...More configuration options - Now become a independent solution (no need to patch ...

(EN_A_VS_AN)


[uncategorized] ~201-~201: The preposition “to” seems more likely in this position.
Context: ...ver work as expected, tweak the options with your own needs. 2. Add the following c...

(AI_EN_LECTOR_REPLACEMENT_PREPOSITION)


[misspelling] ~320-~320: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...e original CSS from CSS files if it has a obfuscated version. (NOT recommended ...

(EN_A_VS_AN)


[uncategorized] ~328-~328: You might be missing the article “an” here.
Context: ...rue|Whether to obfuscate JS files using abstract syntax tree parser.

`contentIgn...

(AI_EN_LECTOR_MISSING_DETERMINER_AN)

🪛 markdownlint-cli2 (0.17.2)
README.md

17-17: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)


21-21: Blank line inside blockquote
null

(MD028, no-blanks-blockquote)


167-167: Heading levels should only increment by one level at a time
Expected: h4; Actual: h5

(MD001, heading-increment)


185-185: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


315-315: No empty links
null

(MD042, no-empty-links)


316-316: No empty links
null

(MD042, no-empty-links)


320-320: Table pipe style
Expected: leading_and_trailing; Actual: leading_only; Missing trailing pipe

(MD055, table-pipe-style)

🔇 Additional comments (20)
README.md (20)

15-16: Formatting Update: Section Separator
The horizontal rule has been updated to visually separate the v3 announcement.


19-20: Approve Blockquote Notices
The [!IMPORTANT] callout clearly highlights the breaking changes and essential migration guide link.


22-23: Approve Upgrade Tip
The [!TIP] callout provides a succinct reminder to avoid unnecessary upgrades.


25-25: Approve Feature Changes Heading
This new section clearly outlines the primary feature updates introduced in v3.0.


31-31: Approve Configuration Changes Heading
This heading appropriately introduces the list of breaking configuration updates.


33-33: Approve Default JS AST Option
Enabling enableJsAst by default aligns documentation with the updated code behavior.


36-36: Approve Deprecation of Regex Options
Switching to whiteListedFolderPaths and blackListedFolderPaths consolidates and clarifies path filtering options.


38-38: Approve Addition of ignorePatterns Option
Introducing ignorePatterns provides a flexible way to exclude selectors and idents.


40-40: Approve Merging classIgnore
Merging classIgnore into ignorePatterns.selectors simplifies and unifies the API.


41-41: Approve Renaming Prefix/Suffix Options
Renaming classPrefix/classSuffix to prefix/suffix improves consistency and clarity.


173-173: Approve Mode Example
The code example correctly reflects the supported random and simplify modes consistent with v3 documentation.


183-184: Approve Caution Callout
The [!CAUTION] callout effectively warns users about partial obfuscation instability.


189-190: Approve Mode Clarification
The example remains consistent with the two supported modes after deprecating simplify-seedable.


201-202: Approve Partial Obfuscation Tip
The suggestion to tweak options for individual needs is clear and useful.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~201-~201: The preposition “to” seems more likely in this position.
Context: ...ver work as expected, tweak the options with your own needs. 2. Add the following c...

(AI_EN_LECTOR_REPLACEMENT_PREPOSITION)


273-273: Skip: Informal Section Intro
This line is purely narrative (“If you are interested in my setting…”). No action needed.


281-281: Skip: Example Configuration Snippet
This code example aligns with the documentation and requires no changes.


308-310: Approve Table Updates for Prefix/Suffix
The table now accurately documents the new nested prefix and suffix options for selectors and idents.


315-316: Approve Whitelist/Blacklist Path Options
The addition of whiteListedFolderPaths and blackListedFolderPaths entries aligns with the updated filter API.

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

315-315: No empty links
null

(MD042, no-empty-links)


316-316: No empty links
null

(MD042, no-empty-links)


321-321: Approve generatorSeed Table Entry
The documentation now clearly states that an undefined generatorSeed produces a random seed.


328-328: Approve enableJsAst Default
Marking this experimental feature as enabled by default accurately reflects the updated code behavior.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~328-~328: You might be missing the article “an” here.
Context: ...rue|Whether to obfuscate JS files using abstract syntax tree parser.

`contentIgn...

(AI_EN_LECTOR_MISSING_DETERMINER_AN)

@soranoo soranoo merged commit d9d2d5b into main May 31, 2025
6 checks passed
@soranoo soranoo deleted the beta branch May 31, 2025 10:12
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