Skip to content
This repository was archived by the owner on May 3, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a05445b
build: splitted more packages
matteobruni Apr 30, 2026
ca1a5bb
build: splitted more packages
matteobruni May 1, 2026
a4bb475
build: splitted more packages
matteobruni May 1, 2026
0c243b2
feat: created nx plugin and added integration with nx
matteobruni May 1, 2026
5cb41af
feat: created nx plugin and added integration with nx
matteobruni May 1, 2026
1f6adcf
feat: added rollup bundle plugin
matteobruni May 1, 2026
1782fdf
build(deps): bump eslint from 10.2.1 to 10.3.0
dependabot[bot] May 1, 2026
14091f7
build: removed legacy code, improved bundle support for both webpack …
matteobruni May 1, 2026
a97377b
Merge branch 'main' into dev
matteobruni May 1, 2026
f16672d
Merge branch 'dev' into dependabot/npm_and_yarn/dev/eslint-10.3.0
matteobruni May 1, 2026
dbb267f
Merge pull request #208 from tsparticles/dependabot/npm_and_yarn/dev/…
matteobruni May 1, 2026
aca4472
feat: improved nx plugin and support, renamed bundle package with web…
matteobruni May 2, 2026
6e5e3d1
Merge remote-tracking branch 'origin/dev' into dev
matteobruni May 2, 2026
578db7a
build: fixed build and restored dist stats
matteobruni May 2, 2026
ad3e416
build: fixed a folder name
matteobruni May 2, 2026
245bb39
build: updated lockfile
matteobruni May 3, 2026
2b71f74
Squashed 'utils/' content from commit 844ce3a
matteobruni May 3, 2026
227921d
chore: import utils sources from tsparticles/utils
matteobruni May 3, 2026
7883212
build: imported utils repository in the cli repository, ready to be i…
matteobruni May 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,46 @@ or
tsparticles-cli build
```

### Build in an Nx workspace

The `build` command can delegate to Nx targets when it detects an Nx workspace.

```bash
tsparticles-cli build --nx
tsparticles-cli build --nx --clean --lint --tsc
tsparticles-cli build --legacy
pnpm nx run @tsparticles/cli-command-build:tsc
```

- `--nx`: forces Nx-target execution when required targets exist
- `--legacy`: disables Nx-aware mode and runs the original in-process pipeline
- default behavior in this workspace: with no granular flags, `build`/`build:ci` Nx aggregate targets are preferred when available

Comment on lines +51 to +65
Inside this repository, the local plugin `@tsparticles/cli-nx-plugin` augments package projects under `commands/*`, `packages/*`, and `utils/*` with canonical aliases like `clean`, `prettify`, `prettify:ci`, `tsc`, `bundle`, and `distfiles`.

## Workspace commands (development)

From the `cli` root:

```bash
pnpm run show:projects
pnpm run build
pnpm run build:affected
pnpm run build:ci
pnpm run lint
pnpm run lint:ci
pnpm run test
pnpm run test:ci
```

### Focused Nx commands

```bash
pnpm nx show project @tsparticles/cli-command-build --json
pnpm nx run @tsparticles/cli-command-build:build
pnpm nx run @tsparticles/cli-nx-plugin:build
```

### Create

#### Preset
Expand Down
1 change: 1 addition & 0 deletions commands/build-bundle-rollup/.dependency-cruiser.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("../build-bundle-webpack/.dependency-cruiser.cjs");
13 changes: 13 additions & 0 deletions commands/build-bundle-rollup/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[![banner](https://particles.js.org/images/banner2.png)](https://particles.js.org)

# tsParticles CLI - Build Bundle Rollup

Internal command package used by `@tsparticles/cli-command-build` to run Rollup-based bundle steps.

## Usage

From the CLI workspace root:

```bash
pnpm nx run @tsparticles/cli-command-build-bundle-rollup:build
```
23 changes: 23 additions & 0 deletions commands/build-bundle-rollup/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import path from "path";
import {fileURLToPath} from "url";
import {defineConfig} from "eslint/config";
import tsParticlesESLintConfig from "@tsparticles/eslint-config";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

export default defineConfig([
tsParticlesESLintConfig,
{
languageOptions: {
parserOptions: {
project: [path.join(__dirname, "src", "tsconfig.json")],
tsconfigRootDir: __dirname,
sourceType: "module"
}
},
rules: {
"no-console": "off"
}
}
]);

76 changes: 76 additions & 0 deletions commands/build-bundle-rollup/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"name": "@tsparticles/cli-command-build-bundle-rollup",
"version": "3.4.7",
"license": "MIT",
"type": "module",
"publishConfig": {
"access": "public",
"tagVersionPrefix": "v"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tsparticles/cli.git",
"directory": "commands/build-bundle-rollup"
},
"prettier": "@tsparticles/prettier-config",
"scripts": {
"prettify:ci:src": "prettier --check ./src/*",
"prettify:ci:readme": "prettier --check ./README.md",
"prettify:src": "prettier --write ./src/*",
"prettify:readme": "prettier --write ./README.md",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx --cache --cache-location .cache/eslint/.eslintcache --cache-strategy metadata --fix",
"lint:ci": "eslint src --ext .js,.jsx,.ts,.tsx --cache --cache-location .cache/eslint/.eslintcache --cache-strategy metadata",
"circular-deps": "depcruise src --include-only '^src' --validate --output-type err-long",
"compile": "pnpm run build:ts",
"compile:ci": "pnpm run build:ts",
"build:ts": "pnpm run build:ts:cjs",
"build:ts:cjs": "tsc -p src",
"build": "pnpm run clear:dist && pnpm run prettify:src && pnpm run lint && pnpm run compile && pnpm run circular-deps && pnpm run prettify:readme",
"build:ci": "pnpm run clear:dist && pnpm run prettify:ci:src && pnpm run lint:ci && pnpm run compile:ci && pnpm run prettify:ci:readme",
"clear:dist": "rimraf ./dist",
"prepack": "pnpm run build"
},
"peerDependencies": {
"commander": "^14"
},
"dependencies": {
"@swc/core": "^1.15.32",
"@tsparticles/depcruise-config": "workspace:^",
"@tsparticles/eslint-config": "workspace:^",
"@tsparticles/prettier-config": "workspace:^",
"@tsparticles/rollup-plugin": "workspace:^",
"@tsparticles/tsconfig": "workspace:^",
"dependency-cruiser": "^17.3.10",
"eslint": "^10.3.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsdoc": "^62.9.0",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-tsdoc": "^0.5.2",
"klaw": "^4.1.0",
"lookpath": "^1.2.3",
"path-scurry": "^2.0.2",
"prettier": "^3.8.3",
"prettier-plugin-multiline-arrays": "^4.1.7",
"prompts": "^2.4.2",
"rimraf": "^6.1.3",
"rollup": "^4.60.2",
"swc-loader": "^0.2.7",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

swc-loader is a webpack loader and has no place in the rollup-bundle package.

swc-loader is webpack-specific — it integrates SWC into webpack's loader pipeline and cannot be used by rollup. The rollup package already has @swc/core as a direct runtime dependency; for SWC in rollup you would need a rollup plugin (e.g. rollup-plugin-swc3), not a webpack loader. Remove swc-loader from this package.

🐛 Proposed fix
-    "swc-loader": "^0.2.7",
📝 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
"swc-loader": "^0.2.7",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commands/build-bundle-rollup/package.json` at line 57, Remove the
webpack-specific dependency "swc-loader" from the package.json of the rollup
bundle package because it's not usable with Rollup; instead ensure the package
uses `@swc/core` (already present) or a Rollup plugin such as rollup-plugin-swc3
when SWC transforms are needed in the Rollup build. Locate the "swc-loader"
entry in package.json and delete that dependency, and if build tooling requires
SWC during bundling, add or document the appropriate Rollup plugin (e.g.,
rollup-plugin-swc3) rather than a webpack loader.

"typescript": "^6.0.3",
"typescript-eslint": "^8.59.1"
},
"devDependencies": {
"@types/estree": "^1.0.8",
"@types/klaw": "^3.0.7",
"@types/node": "^25.6.0",
"@types/prompts": "^2.4.9",
"browserslist": "^4.28.2",
"commander": "^14.0.3",
"copyfiles": "^2.4.1",
"cross-env": "^10.1.0",
"ts-node": "^10.9.2",
"vitest": "^4.1.5"
},
"description": "tsParticles CLI",
"main": "dist/bundle-rollup.js",
"author": "Matteo Bruni <matteo.bruni@me.com>"
}
10 changes: 10 additions & 0 deletions commands/build-bundle-rollup/renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"baseBranches": [
"dev"
],
"extends": [
"config:base"
]
}

37 changes: 37 additions & 0 deletions commands/build-bundle-rollup/src/bundle-rollup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Command } from "commander";
import { bundleRollup } from "./utils.js";
import { existsSync } from "node:fs";

const bundleRollupCommand = new Command("bundle:rollup");

bundleRollupCommand.description("Bundle the tsParticles library using Rollup");
bundleRollupCommand.option(
"--ci",
"Do all build steps for CI, no fixing files, only checking if they are formatted correctly, sets silent to true by default",
false,
);
bundleRollupCommand.option(
"-s, --silent <boolean>",
"Reduce the amount of output during the build, defaults to false, except when --ci is set",
false,
);

bundleRollupCommand.action(async () => {
const opts = bundleRollupCommand.opts(),
ci = !!opts["ci"],
silentOpt = opts["silent"] as string | boolean,
silent = silentOpt === "false" ? false : !!silentOpt || ci,
basePath = process.cwd();

if (!existsSync(basePath)) {
throw new Error("Provided path does not exist");
}

if (!(await bundleRollup(basePath, silent))) {
throw new Error("Rollup bundling failed");
}

console.info("Rollup bundling completed successfully!");
});

export { bundleRollup, bundleRollupCommand };
118 changes: 118 additions & 0 deletions commands/build-bundle-rollup/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* eslint-disable sort-imports */
import { existsSync } from "node:fs";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { loadParticlesBundle } from "@tsparticles/rollup-plugin";
import { type OutputOptions, type RollupOptions, rollup } from "rollup";

const rollupConfigCandidates = ["rollup.config.mjs", "rollup.config.js", "rollup.config.cjs"] as const,
emptyCount = 0;

/**
* @param configData -
* @returns -
*/
function normalizeRollupConfigs(configData: RollupOptions | RollupOptions[]): RollupOptions[] {
return Array.isArray(configData) ? configData : [configData];
}

/**
* @param basePath -
* @returns -
*/
async function loadRollupConfig(basePath: string): Promise<RollupOptions[]> {
for (const configName of rollupConfigCandidates) {
const configPath = path.join(basePath, configName);

if (!existsSync(configPath)) {
continue;
}

const importedConfig = (await import(pathToFileURL(configPath).href)) as {
default?: RollupOptions | RollupOptions[];
};

if (importedConfig.default) {
return normalizeRollupConfigs(importedConfig.default);
}
}

const packageJsonPath = path.join(basePath, "package.json");

if (!existsSync(packageJsonPath)) {
throw new Error("No rollup config file found and package.json is missing");
}

const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")) as {
version?: string;
},
version = packageJson.version ?? "0.0.0";

return loadParticlesBundle({
dir: basePath,
version,
});
}

/**
* @param bundleResult -
* @param output -
* @returns -
*/
async function writeRollupOutput(
bundleResult: Awaited<ReturnType<typeof rollup>>,
output: OutputOptions,
): Promise<void> {
if (output.file || output.dir) {
await bundleResult.write(output);

return;
}

await bundleResult.generate(output);
}

/**
* @param basePath -
* @param silent -
* @returns true if the bundle was created
*/
export async function bundleRollup(basePath: string, silent: boolean): Promise<boolean> {
if (!silent) {
console.info("Rollup bundling started");
}

try {
const configs = await loadRollupConfig(basePath);

for (const config of configs) {
const bundleResult = await rollup(config);
let outputs: OutputOptions[] = [];

if (config.output) {
outputs = Array.isArray(config.output) ? config.output : [config.output];
}

if (outputs.length === emptyCount) {
throw new Error("Rollup config is missing output settings");
}

for (const output of outputs) {
await writeRollupOutput(bundleResult, output);
}

await bundleResult.close();
}
} catch (e) {
console.error(e);

return false;
}
Comment on lines +86 to +111
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

bundleResult leaks when an error is thrown before close()

If the outputs.length === 0 guard at line 98 throws, or if writeRollupOutput (line 102) rejects, execution jumps directly to the outer catch block, which only logs and returns falsebundleResult.close() is never called. Rollup's RollupBuild can hold open file handles/watchers, so this is a real resource leak for any error path.

🛡️ Proposed fix – wrap output processing in try/finally
     for (const config of configs) {
       const bundleResult = await rollup(config);
       let outputs: OutputOptions[] = [];
 
+      try {
         if (config.output) {
           outputs = Array.isArray(config.output) ? config.output : [config.output];
         }
 
-        if (outputs.length === emptyCount) {
+        if (outputs.length === 0) {
           throw new Error("Rollup config is missing output settings");
         }
 
         for (const output of outputs) {
           await writeRollupOutput(bundleResult, output);
         }
-
-      await bundleResult.close();
+      } finally {
+        await bundleResult.close();
+      }
     }
📝 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
try {
const configs = await loadRollupConfig(basePath);
for (const config of configs) {
const bundleResult = await rollup(config);
let outputs: OutputOptions[] = [];
if (config.output) {
outputs = Array.isArray(config.output) ? config.output : [config.output];
}
if (outputs.length === emptyCount) {
throw new Error("Rollup config is missing output settings");
}
for (const output of outputs) {
await writeRollupOutput(bundleResult, output);
}
await bundleResult.close();
}
} catch (e) {
console.error(e);
return false;
}
try {
const configs = await loadRollupConfig(basePath);
for (const config of configs) {
const bundleResult = await rollup(config);
let outputs: OutputOptions[] = [];
try {
if (config.output) {
outputs = Array.isArray(config.output) ? config.output : [config.output];
}
if (outputs.length === 0) {
throw new Error("Rollup config is missing output settings");
}
for (const output of outputs) {
await writeRollupOutput(bundleResult, output);
}
} finally {
await bundleResult.close();
}
}
} catch (e) {
console.error(e);
return false;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commands/build-bundle-rollup/src/utils.ts` around lines 86 - 111, When
iterating configs after loadRollupConfig, ensure RollupBuild resources aren't
leaked by wrapping the per-config processing (the call to rollup(config), the
outputs handling, and writeRollupOutput calls) in a try/finally so that
bundleResult.close() is always invoked; specifically, inside the loop where you
create const bundleResult = await rollup(config) (and before you inspect
outputs/ call writeRollupOutput), add a try { ... } finally { if (bundleResult
&& typeof bundleResult.close === "function") await bundleResult.close(); } to
guarantee close runs on both normal and error paths, and keep the existing outer
catch to log and return false.


if (!silent) {
console.info("Rollup bundling completed");
}

return true;
}
55 changes: 55 additions & 0 deletions commands/build-bundle-rollup/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"compilerOptions": {
"rootDir": ".",
"outDir": ".",
"resolveJsonModule": true,
"composite": true,
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": [
"ESNext",
"ES2024",
"ES2023",
"ES2022",
"ES2021",
"ES2020",
"ES2019",
"ES2018",
"ES2017",
"ES2016",
"ES2015"
],
"types": [
"node",
"klaw",
"prompts",
"eslint"
],
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"skipLibCheck": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true
},
"include": [
"src/**/*"
]
}

Loading
Loading