Skip to content

build: move to Vite, oxlint, Vitest and release-please; fix uploads of several files - #38

Open
nd0ut wants to merge 19 commits into
masterfrom
build-tooling-and-tests
Open

nd0ut wants to merge 19 commits into
masterfrom
build-tooling-and-tests

Conversation

@nd0ut

@nd0ut nd0ut commented Sep 30, 2026

Copy link
Copy Markdown
Member

Summary

Modernizes the tooling, adds end-to-end tests and CI, and fixes the bugs the tests found. Each commit has one purpose, and they're meant to be kept rather than squashed: release-please builds the changelog from their messages.

 build     Rollup + uglify        ->  Vite 8 (Rolldown, Oxc)
 lint      ESLint 7               ->  oxlint (the 33 rules it supports)
 tests     none                   ->  Vitest 5 browser mode, real editors + real uploads
 CI        none                   ->  GitHub Actions: checks, release-please, deploy
 release   release-it by hand     ->  release-please PR -> tag, release, assets, deploy
src/
├── adapter.js         # picks the code for the Redactor on the page (was wrappers.js + checkRedactor.js)
├── globals.js         # jQuery, Redactor, RedactorX read from window
├── common/            # named exports only
└── redactor{2,3,5,X}/ # each version exports one object; insertHtml -> insertFiles
test/
├── suite.js           # every case, written once
└── adapters.js        # per-version differences
docs/redactor-{5,x,3,2}.md   # README split per version

Behaviour changes, found by the new tests:

 upload with multiple: true
-  Redactor 3: each image insert replaced the previous one  -> 1 of N images inserted
-  Redactor X: fileInfo.crop.width threw on files without crop -> 1 of N images inserted
+  all files go to the editor in one insertFiles call        -> N of N inserted
 integration option on Redactor X
-  "Redactor/undefined; ..."
+  "Redactor/X1.4.6; ..."
 page without jQuery or Redactor
-  TypeError: Cannot read properties of undefined (reading 'Redactor')
+  console.error("Uploadcare: Redactor not found.")
 Redactor X toolbar button without buttonIconEnabled
-  empty square
+  rx-icon-file

Evidence

  • Before: inserts every file when multiple is on failed on Redactor 3 (2 images uploaded, 1 in the editor) and on Redactor X (TypeError: Cannot read properties of undefined (reading 'width')).
    After: npm test: 4 test files passed, 1 skipped; 43 tests passed, 13 skipped.
  • Skipped tests: cases that don't apply to a version; the non-image upload, because the demo project only accepts images (runs with UPLOADCARE_PUBLIC_KEY); and Redactor 5, which needs a licensed build.
  • Build output compared with the Rollup build: same functions and strings, license banner and "use strict" kept, unminified 14.8 KB -> 13.5 KB.
  • npm ci from the committed lockfile, lint and all three workflows (actionlint) pass. A release-please dry run against this repo finds v3.1.0 as the last release.

Merge Danger

Door: two-way

Nothing is released by merging. release-please opens a release pull request for 3.2.0, and only merging that one tags, publishes and deploys.

Blast Radius: consumers

  • The bundles are ES2015 now, not ES5. Pages that must run on ES5-only browsers such as IE 11 would break on the next release. Vite 8's bundler and minifier can't produce ES5.
  • buttonIconEnabled no longer does anything on Redactor X, because the button always has an icon.
  • The Redactor X demo commits Imperavi's build (demo/redactorX/redactorx.js), which will be served from gh-pages. Imperavi's license restricts redistribution and use in open-source projects without written consent, so check with Imperavi before this is deployed.
  • Before the first release, turn on "Allow GitHub Actions to create and approve pull requests" in Settings → Actions, and remove release-as from release-please-config.json after 3.2.0.

🤖 Generated with Claude Code

nd0ut and others added 14 commits September 30, 2026 11:43
The deploy scripts printed errors but still exited with 0, so a failed push
looked like a success. They now set a non-zero exit code. The release branch
commit message reads the version from package.json instead of the npm
environment, so it is also right when the script runs with plain node.

`npm run deploy` builds once and pushes to the release branch and gh-pages;
`deploy:release` and `deploy:demo` each build first, so they work from a
fresh clone. gh-pages is updated from 2 to 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wrappers.js becomes adapter.js, which checks for Redactor 5, X, 3 and 2 in
turn and exports the code for the one on the page. checkRedactor.js repeated
the same checks and is gone: the entry file registers the plugin when an
adapter is found and logs "Redactor not found" otherwise.

Every module now uses named exports, and each version exports its functions
as one object. jQuery, Redactor and RedactorX come from src/globals.js, which
reads them from window.

Also fixes the lint errors left by the Redactor X and 5 contributions:
src/.eslintrc.json set ecmaVersion 5 with ES modules, which ESLint can't
parse, so lint had not checked src at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With `multiple: true`, only one file ended up in the editor:

* Redactor 3: each image insert replaced the image the previous insert left
  selected, so only the last image stayed. All images now go in one insert.
* Redactor X: files uploaded together have no crop info, so reading
  `fileInfo.crop.width` threw after the first image. The size now falls back
  to the original image info.

Each version now gets all uploaded files in one call, `insertFiles`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The integration string only checked for Redactor 3 and 2, so on Redactor X
it reported "Redactor/undefined". It now reports the Redactor X version, for
example "Redactor/X1.4.6".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Redactor 2 check read `$.Redactor` without checking that jQuery is on the
page, so a page without jQuery threw "Cannot read properties of undefined"
and the intended error message never showed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Redactor X shows toolbar buttons as icons only, so without
`buttonIconEnabled` the Uploadcare button was an empty square. It now always
shows `rx-icon-file`, or `buttonIcon` when set, like on Redactor 5.
`buttonIconEnabled` has no effect on Redactor X.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vite 8 builds both files in one run: the plugin placeholders are filled by a
small plugin, the minified file keeps the license banner through postBanner,
and "use strict" is kept. Rollup, its plugins, rimraf and rollup.config.js
are removed; Vite empties dist itself.

The output is now ES2015 instead of ES5. Rolldown prints ES2015 shorthand
properties and the Oxc minifier uses template literals, and neither can
target ES5. The bundles have the same functions as before; the unminified
file is 13.5 KB instead of 14.8 KB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.oxlintrc.json keeps the 33 rules of eslint-config-uploadcare that oxlint
supports, with the same settings. The 63 it does not have are formatting
rules, React rules for JSX this project doesn't have, and two syntax rules
strict mode already enforces. ESLint, its config package and all .eslintrc
files are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r mode

`npm test` builds the plugin and runs it in headless Chromium against the
real Redactor builds from demo/ and the real Uploadcare Widget, uploading
real files to Uploadcare. Each case is written once in test/suite.js and
runs for every version; test/adapters.js holds what differs between them.

The cases cover the toolbar button options, the options passed to the
widget, the show, done and cancel events, and uploads: images, the crop
step, several files, imageTag, uploadCompleteCallback, and a page without
Redactor.

Two groups skip without extra setup: the Uploadcare demo project only takes
images, so the non-image test needs UPLOADCARE_PUBLIC_KEY, and the Redactor 5
tests need a licensed build in test/vendor/redactor5/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
husky 4 only runs hooks set up in a `husky` section of package.json or a
.huskyrc file, and there was neither. The `precommit` script is a husky 0.x
convention that husky 4 ignores, so nothing ran on commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…please

* ci.yml runs lint, the tests and the build on pushes to master and on pull
  requests.
* release.yml runs release-please on every push to master. It keeps a
  release pull request with the next version and the changelog from
  Conventional Commits; merging it tags the release, creates the GitHub
  release, attaches the bundles and calls the deploy.
* deploy.yml pushes a release tag's build to the release branch and the demo
  to gh-pages. It can also run by hand with a tag, to redeploy or roll back.

release-please replaces release-it. The first release is pinned to 3.2.0, so
the hand-written 3.2.0 changelog entry is removed and package.json goes back
to 3.1.0, the last release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The three demo pages share one layout and stylesheet: a tab per Redactor
version, the editor, and the full code to copy, including the jQuery script
the plugin needs. The Redactor X page says the button shows once you click
into the text, and shows how to listen to the plugin events there.

The footer link used utm_campaign=uploadcare-ckeditor; it now uses
uploadcare-redactor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README keeps the overview and links a guide for each version in docs/,
with its requirements, script tags, supported options, events and demo.
It also says that Uploadcare Widget, which the Uploadcare docs now call
jQuery File Uploader, has been deprecated since September 1, 2025, and that
support for the new File Uploader is in progress. The heading and the
package description say "Uploadcare Widget" instead of "File Uploader".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DEVELOPMENT.md covers the build, lint and test commands, the CI workflows,
the Conventional Commits format release-please reads, and how releases and
deploys work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings September 30, 2026 08:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Upload ordering, HTML injection, accessibility, licensed deployment, and Redactor 5 coverage remain unresolved.

Review effort: Balanced
Findings: 2 High severity · 3 Medium severity

Open (5)
What changed in this PR

Modernizes build, lint, testing, CI, release automation, documentation, and demos while fixing multi-file uploads across supported Redactor versions.

Changes:

  • Migrates from Rollup/ESLint/release-it to Vite, oxlint, Vitest, and release-please.
  • Introduces version adapters and batched file insertion.
  • Adds browser E2E coverage, CI/deployment workflows, documentation, and refreshed demos.
File Description
vitest.config.mjs Configures browser E2E tests.
vite.config.mjs Configures Vite bundles and version replacement.
test/​suite.js Adds shared integration test suite.
test/​redactorX.test.js Runs tests for Redactor X.
test/​redactor5.test.js Conditionally runs Redactor 5 tests.
test/​redactor3.test.js Runs tests for Redactor 3.
test/​redactor2.test.js Runs tests for Redactor 2.
test/​no-redactor.test.js Tests missing-editor handling.
test/​helpers.js Adds browser and upload test helpers.
test/​adapters.js Defines version-specific test adapters.
src/​wrappers.js Removes legacy adapter selection.
src/​uploadcare.js Registers the selected adapter safely.
src/​redactorX/​start.js Ensures Redactor X buttons have icons.
src/​redactorX/​insertHtml.js Removes single-file insertion.
src/​redactorX/​insertFiles.js Adds multi-file insertion.
src/​redactorX/​init.js Converts initialization to named exports.
src/​redactorX/​index.js Exposes the Redactor X adapter.
src/​redactorX/​getVersion.js Reads the global Redactor X version.
src/​redactorX/​getSelection.js Converts selection helper export.
src/​redactorX/​broadcast.js Converts event helper export.
src/​redactorX/​addPlugin.js Uses the global Redactor X API.
src/​redactor5/​plugin.js Refactors Redactor 5 plugin exports.
src/​redactor5/​loadWidget.js Converts widget loader export.
src/​redactor5/​index.js Exposes the Redactor 5 adapter.
src/​redactor5/​getVersion.js Reads the global Redactor 5 API.
src/​redactor5/​addPlugin.js Registers the named plugin class.
src/​redactor3/​start.js Converts toolbar initialization export.
src/​redactor3/​insertHtml.js Removes single-image insertion.
src/​redactor3/​insertFiles.js Adds batched image insertion.
src/​redactor3/​init.js Converts initialization to named exports.
src/​redactor3/​index.js Exposes the Redactor 3 adapter.
src/​redactor3/​getVersion.js Reads the global Redactor version.
src/​redactor3/​getSelection.js Converts selection helper export.
src/​redactor3/​broadcast.js Converts event helper export.
src/​redactor3/​addPlugin.js Uses the global Redactor API.
src/​redactor2/​start.js Converts toolbar initialization export.
src/​redactor2/​insertFiles.js Adds multi-file insertion.
src/​redactor2/​init.js Converts initialization to named exports.
src/​redactor2/​index.js Exposes the Redactor 2 adapter.
src/​redactor2/​getVersion.js Safely detects Redactor 2.
src/​redactor2/​getSelection.js Converts selection helper export.
src/​redactor2/​broadcast.js Converts callback helper export.
src/​redactor2/​addPlugin.js Uses global jQuery registration.
src/​globals.js Centralizes browser globals.
src/​common/​show.js Uses the selected adapter for dialogs.
src/​common/​normalizeOptions.js Converts option normalization export.
src/​common/​loadWidget.js Converts widget loader export.
src/​common/​getFileUrl.js Converts URL helper export.
src/​common/​done.js Inserts resolved files as one batch.
src/​common/​applyIntegrationOption.js Adds Redactor X integration metadata.
src/​checkRedactor.js Removes legacy editor detection.
src/​adapter.js Selects the available Redactor adapter.
src/​.eslintrc.json Removes source ESLint configuration.
scripts/​deploy-release.js Improves release deployment failures.
scripts/​deploy-demo.js Propagates demo deployment failures.
scripts/​.eslintrc.json Removes script ESLint configuration.
rollup.config.js Removes the Rollup build.
release-please-config.json Configures release-please.
README.md Adds version-specific documentation links.
package.json Updates tooling and scripts.
index.html Refreshes demo navigation.
docs/​redactor-x.md Documents Redactor X integration.
docs/​redactor-5.md Documents Redactor 5 integration.
docs/​redactor-3.md Documents Redactor 3 integration.
docs/​redactor-2.md Documents Redactor 2 integration.
DEVELOPMENT.md Documents development and release workflows.
demo/​redactorX/​redactorx.min.css Adds Redactor X demo styling.
demo/​redactorX/​index.html Adds the Redactor X demo.
demo/​redactor3/​index.html Refreshes the Redactor 3 demo.
demo/​redactor2/​index.html Refreshes the Redactor 2 demo.
demo/​assets/​app.css Redesigns shared demo styling.
CHANGELOG.md Hands future entries to release-please.
.release-please-manifest.json Records the current release version.
.release-it.json Removes release-it configuration.
.oxlintrc.json Adds oxlint configuration.
.gitignore Ignores test vendor builds and artifacts.
.github/​workflows/​release.yml Automates releases and assets.
.github/​workflows/​deploy.yml Adds reusable release deployment.
.github/​workflows/​ci.yml Adds lint, test, and build CI.
.eslintrc.json Removes root ESLint configuration.
.eslintignore Removes obsolete ESLint ignores.
Files not reviewed (1)
  • demo/redactorX/redactorx.min.css: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: npm run deploy

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed that this deploy would publish Imperavi's Redactor X build from demo/redactorX/. Whether it can be redistributed is being checked with Imperavi. Until that's settled, the options are to leave the Redactor X demo out of the deploy or to drop the build from the repo; this thread stays open until that's decided.

🤖 Written by Claude Code on behalf of @nd0ut

Comment on lines +47 to +49
plugin.app.insertion.insertHtml(
'<a href="' + fileUrl + '" data-file="' + fileInfo.uuid + '">' + fileInfo.name + '</a>'
)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 5f55ea8. Redactor 2 and 3 had the same problem: the Redactor 2 image alt and the link text on Redactor 2, 3 and X went into HTML unescaped (Redactor 3's link insert treats the text as HTML too). File names now go through a new escapeHtml helper. A new test uploads an image named a"><img src=x onerror=alert(1)>.png and checks that the name stays text; it fails without the escaping.

🤖 Written by Claude Code on behalf of @nd0ut

Comment thread demo/assets/app.css Outdated
Comment on lines 12 to 13
--color-blue: #238de6;
--color-dark-blue: #187cce;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 68146f5. The demo blue is now #1b6fbf (5.16:1 against white) for the links and the active tab, and #155a9c (7.06:1) for hover.

🤖 Written by Claude Code on behalf of @nd0ut

Comment thread src/redactor3/insertFiles.js Outdated
Comment on lines +18 to +23
else {
plugin.app.api('module.link.insert', {
url: fileUrl,
text: fileInfo.name,
id: fileInfo.uuid,
})

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in b26c8ba. The order was image, image, document: the batched image insert went in before the links. Inserting the files one at a time in order doesn't work on Redactor 3: each image insert replaces whatever is selected, so image, link, image leaves only the last image. Uploads that include images now go in as one block of HTML in upload order, and the images still become Redactor 3 image blocks. Links on their own still use the link insert, so they stay inside the current paragraph. The several-files test now checks the order.

🤖 Written by Claude Code on behalf of @nd0ut

Comment thread test/redactor5.test.js

// __REDACTOR5_BUILD__ is the build path when test/vendor/redactor5/redactor.min.js
// exists, see vitest.config.mjs
if (__REDACTOR5_BUILD__) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Right, the Redactor 5 suite always skips in CI. Redactor 5 is licensed, so the build can't be committed or fetched from a public URL, and the public demo build only runs on imperavi.com. Running it in CI would need a licensed build stored somewhere private, such as an encrypted secret or a private package. For now the skip is documented in DEVELOPMENT.md, and the suite runs locally once a licensed build is placed in test/vendor/redactor5/.

🤖 Written by Claude Code on behalf of @nd0ut

nd0ut and others added 5 commits September 30, 2026 11:58
File names come from the uploaded files and went into HTML unescaped: the
image alt on Redactor 2 and the link text on Redactor 2, 3 and X. A name
such as `a"><img src=x onerror=alert(1)>.png` added its own markup to the
editor. Names now go through escapeHtml; Redactor 5 already built its link
through the DOM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Images were collected into one image insert after the links, so an image,
document, image upload came out as image, image, document. Separate inserts
in order don't work: each image insert replaces the image the one before it
left selected. Files that include images now go in as one block of HTML in
upload order; links alone still use the link insert, which puts them inside
the current paragraph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#238de6 had 3.49:1 against white, below the 4.5:1 AA needs for normal-size
link text and for white text on the active version tab. #1b6fbf has 5.16:1,
and the hover color #155a9c has 7.06:1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…test

Each test file now checks the editor on the page before creating any
editor: a wrong build in demo/ stops the file with, for example,
"Redactor 3 build on the page: expected 'undefined' to match /^3\./".

The show-event test also checks that the plugin reports exactly that editor
and the plugin version in the integration option, so the plugin picking the
wrong version's code, such as Redactor 5 code on a Redactor 3 page, fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`npm start` runs the Vite dev server on port 3000 instead of browser-sync.
Vite is already a dependency, so browser-sync and its 332 packages go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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