From 4ee38db68c83e58d5f07a16f2f411b00298b29e0 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Tue, 26 May 2026 23:15:51 +0300 Subject: [PATCH 01/42] feature: client: view: flac: add support --- client/modules/view/types.js | 2 +- client/modules/view/types.spec.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/client/modules/view/types.js b/client/modules/view/types.js index 2e001fe1..320a4bc8 100644 --- a/client/modules/view/types.js +++ b/client/modules/view/types.js @@ -1,7 +1,7 @@ import {extname} from 'node:path'; import currify from 'currify'; -export const isAudio = (name) => /\.(mp3|ogg|m4a)$/i.test(name); +export const isAudio = (name) => /\.(mp3|ogg|m4a|flac)$/i.test(name); const testRegExp = currify((name, reg) => reg.test(name)); const getRegExp = (ext) => RegExp(`\\.${ext}$`, 'i'); diff --git a/client/modules/view/types.spec.js b/client/modules/view/types.spec.js index 7ed159f4..f0542c18 100644 --- a/client/modules/view/types.spec.js +++ b/client/modules/view/types.spec.js @@ -8,6 +8,13 @@ test('cloudcmd: client: view: types: isAudio', (t) => { t.end(); }); +test('cloudcmd: client: view: types: isAudio: flac', (t) => { + const result = isAudio('hello.flac'); + + t.ok(result); + t.end(); +}); + test('cloudcmd: client: view: types: isAudio: no', (t) => { const result = isAudio('hello'); From fc3d5ac70092424351f75b463d9625c88dc02d66 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Tue, 26 May 2026 23:17:05 +0300 Subject: [PATCH 02/42] chore: cloudcmd: v19.19.0 --- ChangeLog | 5 +++++ HELP.md | 3 ++- README.md | 2 +- package.json | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index b854b51c..49ed06d5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2026.05.26, v19.19.0 + +feature: +- 4ee38db6 client: view: flac: add support + 2026.05.26, v19.18.1 feature: diff --git a/HELP.md b/HELP.md index bed9199e..e8012fe7 100644 --- a/HELP.md +++ b/HELP.md @@ -1,4 +1,4 @@ -# Cloud Commander v19.18.1 +# Cloud Commander v19.19.0 ### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL] @@ -1119,6 +1119,7 @@ There are a lot of ways to be involved in `Cloud Commander` development: ## Version history +- *2026.05.26*, **[v19.19.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.19.0)** - *2026.05.26*, **[v19.18.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.18.1)** - *2026.05.26*, **[v19.18.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.18.0)** - *2026.05.17*, **[v19.17.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.17.0)** diff --git a/README.md b/README.md index 664c6a24..0934beaa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cloud Commander v19.18.1 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] +# Cloud Commander v19.19.0 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] ### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL] diff --git a/package.json b/package.json index 5ea51169..13e3a4dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcmd", - "version": "19.18.1", + "version": "19.19.0", "type": "module", "author": "coderaiser (https://github.com/coderaiser)", "description": "File manager for the web with console and editor", From f8edddd442b2cf71c08b6f910b75def2445edd8f Mon Sep 17 00:00:00 2001 From: coderaiser Date: Sat, 30 May 2026 13:12:09 +0200 Subject: [PATCH 03/42] chore: .nvmrc --- .nvmrc | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 1efe0ac6..00000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v20.15.1 From 45ec062bce19c5615af28a17b3a2da1760903012 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Mon, 1 Jun 2026 13:24:07 +0300 Subject: [PATCH 04/42] docs: readme: deploy on InstaPods --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0934beaa..933d89e0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cloud Commander v19.19.0 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] +# Cloud Commander v19.19.0 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deply on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL] ### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL] @@ -15,6 +15,8 @@ [GitterIMGURL]: https://img.shields.io/gitter/room/coderaiser/cloudcmd.js.svg [DeployURL]: https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd "Deploy" [DeployIMG]: https://www.herokucdn.com/deploy/button.png +[DeployInstaPodsIMG]: https://img.shields.io/badge/deploy%20on-InstaPods-blue +[DeployInstaPodsURL]: https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd **Cloud Commander** a file manager for the web with console and editor. From c558e44a1c8e7fb1e2d8dc001d960713c15a1318 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Mon, 1 Jun 2026 13:29:58 +0300 Subject: [PATCH 05/42] docs: readme: InstaPods --- HELP.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/HELP.md b/HELP.md index e8012fe7..0b32ca8d 100644 --- a/HELP.md +++ b/HELP.md @@ -1,6 +1,6 @@ # Cloud Commander v19.19.0 -### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL] +### [Main][MainURL] [Blog][BlogURL] [Demo][DemoURL] [Deploy](#deploy) [MainURL]: https://cloudcmd.io "Main" [BlogURL]: https://blog.cloudcmd.io "Blog" @@ -14,6 +14,9 @@ [TERMUX]: https://termux.com "Termux" [INLY]: https://github.com/coderaiser/node-inly "Extract archive" +[DeployInstaPodsIMG]: https://img.shields.io/badge/deploy%20on-InstaPods-blue +[DeployInstaPodsURL]: https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd + **Cloud Commander** is a file manager for the web. It includes a command-line console and a text editor. Cloud Commander helps you manage your server and work with files, directories and programs in a web browser from any computer, mobile or tablet. ![Cloud Commander](https://cloudcmd.io/img/logo/cloudcmd.png "Cloud Commander") @@ -930,9 +933,9 @@ ln -s ./sites-available/io.cloudcmd.io ./sites-enabled ## Deploy -`Cloud Commander` can be easily deployed to [Heroku](https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd "Deploy to Heroku"). +`Cloud Commander` can be easily deployed to [InstaPods][DeployInstaPodsURL]. After deploy you receive email with credentials. -[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.png "Deploy to Heroku")](https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd) +[![Deploy on InstaPods](https://instapods.com/deploy-button.svg)](https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd) ## Docker From d2176c93fbb12174d5326ec20005539ad5485a79 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 1 Jun 2026 22:55:57 +0300 Subject: [PATCH 06/42] chore: actions: typo --- .github/workflows/nodejs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index b9ed9cb5..0c09d72b 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -22,7 +22,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - name: Install Redrun - run: bun i redrun -g --no-save + run: bun i redrun typos.ai -g --no-save - name: Install run: bun i --no-save - name: Lint @@ -39,7 +39,7 @@ jobs: - name: Typos Install run: cargo install typos-cli || echo 'already installed' - name: Typos - run: typos --write-changes + run: typos --write-changes --format json | typos.ai - name: Commit fixes uses: EndBug/add-and-commit@v10 continue-on-error: true From 220cc5557b3989026fcb72a472d23c56e0e2bced Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 1 Jun 2026 23:32:27 +0300 Subject: [PATCH 07/42] chore: actions: API_KEY --- .github/workflows/nodejs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 0c09d72b..ca55a0c4 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -6,6 +6,7 @@ jobs: runs-on: ubuntu-latest env: NAME: cloudcmd + API_KEY: ${{ secrets.API_KEY }} strategy: matrix: node-version: From f1d80282ebd82a9dedf3111bf02b0fa12ef6ec60 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Mon, 1 Jun 2026 20:39:12 +0000 Subject: [PATCH 08/42] =?UTF-8?q?chore:=20cloudcmd:=20actions:=20lint=20?= =?UTF-8?q?=E2=98=98=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 933d89e0..332bb7fc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cloud Commander v19.19.0 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deply on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL] +# Cloud Commander v19.19.0 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deploy on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL] ### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL] From 76d7bf4d73c177b6046d46671939713248bb2c41 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Tue, 2 Jun 2026 10:03:23 +0200 Subject: [PATCH 09/42] feature: docker: typos, shellcheck: add --- docker/Dockerfile.io | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.io b/docker/Dockerfile.io index 7b3a9304..64ee6953 100644 --- a/docker/Dockerfile.io +++ b/docker/Dockerfile.io @@ -23,7 +23,7 @@ ENV DEBIAN_FRONTEND=noninteractive \ ARG UBUNTU_DEPS="libatomic1 curl wget git net-tools iproute2 software-properties-common" ARG RUST_DEPS="build-essential" ARG DEPS="pv gcc gdb strace upx-ucl less ffmpeg net-tools netcat-openbsd mc far2l iputils-ping vim bat fzf locales sudo command-not-found ncdu aptitude htop btop hexyl tmux" -ARG PALABRA_DEPS="nvm node rust go deno fasm nvchad rizin yara gdu f4" +ARG PALABRA_DEPS="nvm node rust go deno fasm nvchad rizin yara gdu f4 typos shellcheck" ARG BUN_DEPS="palabra wisdom nupdate version-io redrun superc8 supertape madrun redlint putout renamify-cli runny redfork cline" RUN apt-get update && \ From b3b0c1d1595ad08d8792fd4c880e819eab9032fb Mon Sep 17 00:00:00 2001 From: coderaiser Date: Wed, 3 Jun 2026 13:00:56 +0000 Subject: [PATCH 10/42] chore: package: rm unused optimize-css-assets-webpack-plugin --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 13e3a4dd..a6b65f6f 100644 --- a/package.json +++ b/package.json @@ -195,7 +195,6 @@ "morgan": "^1.6.1", "multi-rename": "^3.0.0", "nodemon": "^3.0.1", - "optimize-css-assets-webpack-plugin": "^6.0.1", "path-browserify": "^1.0.1", "philip": "^3.0.0", "place": "^1.1.4", From 21e1dcf0e58fd8e5c8804c7abda86da27811f2b4 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Wed, 3 Jun 2026 17:27:27 +0300 Subject: [PATCH 11/42] chore: actions: typos --- .github/workflows/nodejs.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index ca55a0c4..b9e5759b 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -6,7 +6,8 @@ jobs: runs-on: ubuntu-latest env: NAME: cloudcmd - API_KEY: ${{ secrets.API_KEY }} + TYPOS_AI_KEY: ${{ secrets.TYPOS_AI_KEY }} + TYPOS_AI_RUN: 1 strategy: matrix: node-version: @@ -23,7 +24,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - name: Install Redrun - run: bun i redrun typos.ai -g --no-save + run: bun i redrun typos.ai palabra -g --no-save - name: Install run: bun i --no-save - name: Lint @@ -38,9 +39,9 @@ jobs: target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Typos Install - run: cargo install typos-cli || echo 'already installed' + run: palabra i typos - name: Typos - run: typos --write-changes --format json | typos.ai + run: typos.ai - name: Commit fixes uses: EndBug/add-and-commit@v10 continue-on-error: true From 9d06279ba81241149f5aa81ac6a25edf5c6a7f27 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Wed, 3 Jun 2026 23:39:27 +0300 Subject: [PATCH 12/42] featur: docker: io: add gh --- docker/Dockerfile.io | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.io b/docker/Dockerfile.io index 64ee6953..9a0956ae 100644 --- a/docker/Dockerfile.io +++ b/docker/Dockerfile.io @@ -11,19 +11,19 @@ COPY package.json /usr/local/share/cloudcmd/ ENV DEBIAN_FRONTEND=noninteractive \ NVM_DIR=/usr/local/share/nvm \ - PREFIX=/usr/local \ npm_config_cache=/tmp/npm-cache \ GOPATH=/usr/local/share/go \ PATH=/usr/local/share/bun/bin:$PATH \ BUN_INSTALL=/usr/local/share/bun \ NPM_CONFIG_CACHE=/tmp/.npm \ + NPM_CONFIG_PREFIX=/usr/local \ PALABRA_DIR=/usr/local/share \ XDG_CONFIG_HOME=/usr/local/etc ARG UBUNTU_DEPS="libatomic1 curl wget git net-tools iproute2 software-properties-common" ARG RUST_DEPS="build-essential" ARG DEPS="pv gcc gdb strace upx-ucl less ffmpeg net-tools netcat-openbsd mc far2l iputils-ping vim bat fzf locales sudo command-not-found ncdu aptitude htop btop hexyl tmux" -ARG PALABRA_DEPS="nvm node rust go deno fasm nvchad rizin yara gdu f4 typos shellcheck" +ARG PALABRA_DEPS="nvm node rust go deno fasm nvchad rizin yara gdu f4 typos shellcheck gh" ARG BUN_DEPS="palabra wisdom nupdate version-io redrun superc8 supertape madrun redlint putout renamify-cli runny redfork cline" RUN apt-get update && \ From b149288545917f0d8be0f8280e9ac4217b62d443 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Wed, 3 Jun 2026 23:40:16 +0300 Subject: [PATCH 13/42] feature: docker: io: NPM_CONFIG_PACKAGE_LOCK=false --- docker/Dockerfile.io | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/Dockerfile.io b/docker/Dockerfile.io index 9a0956ae..1f8ed476 100644 --- a/docker/Dockerfile.io +++ b/docker/Dockerfile.io @@ -17,6 +17,7 @@ ENV DEBIAN_FRONTEND=noninteractive \ BUN_INSTALL=/usr/local/share/bun \ NPM_CONFIG_CACHE=/tmp/.npm \ NPM_CONFIG_PREFIX=/usr/local \ + NPM_CONFIG_PACKAGE_LOCK=false \ PALABRA_DIR=/usr/local/share \ XDG_CONFIG_HOME=/usr/local/etc From 4d7116b467e91c71a67d4ebe09a6bce60c524e36 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Wed, 3 Jun 2026 23:47:50 +0300 Subject: [PATCH 14/42] chore: docker: io: git --- docker/Dockerfile.io | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.io b/docker/Dockerfile.io index 1f8ed476..d5a24459 100644 --- a/docker/Dockerfile.io +++ b/docker/Dockerfile.io @@ -32,7 +32,7 @@ RUN apt-get update && \ apt-get autoremove && \ apt-get install -y ${UBUNTU_DEPS} ${RUST_DEPES} ${DEPS} && \ echo "> Install git" && \ - #add-apt-repository ppa:git-core/ppa -y && \ + add-apt-repository ppa:git-core/ppa -y && \ echo "> Update command-not-found database. Run 'sudo apt update' to populate it." && \ apt-get update && \ apt-get upgrade -y && \ From ea584aceeae316dde770992ed29c411e119b2068 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Thu, 4 Jun 2026 15:33:54 +0300 Subject: [PATCH 15/42] chore: actions: typos.ai --- .github/workflows/nodejs.yml | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index b9e5759b..607f249f 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -6,8 +6,6 @@ jobs: runs-on: ubuntu-latest env: NAME: cloudcmd - TYPOS_AI_KEY: ${{ secrets.TYPOS_AI_KEY }} - TYPOS_AI_RUN: 1 strategy: matrix: node-version: @@ -24,24 +22,15 @@ jobs: with: node-version: ${{ matrix.node-version }} - name: Install Redrun - run: bun i redrun typos.ai palabra -g --no-save + run: bun i redrun -g --no-save - name: Install run: bun i --no-save - name: Lint run: redrun fix:lint - - uses: actions/cache@v5 + - name: Typos + uses: coderaiser/typos.ai@v1.1.2 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Typos Install - run: palabra i typos - - name: Typos - run: typos.ai + key: ${{ secrets.TYPOS_AI_KEY }} - name: Commit fixes uses: EndBug/add-and-commit@v10 continue-on-error: true From e59167b1fa37aff3174a305519205fc06445d262 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 4 Jun 2026 16:02:02 +0300 Subject: [PATCH 16/42] chore: actions: permissions --- .github/workflows/nodejs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 607f249f..6ac0e744 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,6 +1,8 @@ name: Node CI on: - push +permissions: + contents: write jobs: build: runs-on: ubuntu-latest @@ -27,7 +29,7 @@ jobs: run: bun i --no-save - name: Lint run: redrun fix:lint - - name: Typos + - name: Typos uses: coderaiser/typos.ai@v1.1.2 with: key: ${{ secrets.TYPOS_AI_KEY }} From f5c0aa5c4c6192ff68a8d4263f3b13b0a9b3eada Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 4 Jun 2026 17:28:34 +0300 Subject: [PATCH 17/42] Update nodejs.yml --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 6ac0e744..337b607c 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -30,7 +30,7 @@ jobs: - name: Lint run: redrun fix:lint - name: Typos - uses: coderaiser/typos.ai@v1.1.2 + uses: coderaiser/typos.ai@v1.1.5 with: key: ${{ secrets.TYPOS_AI_KEY }} - name: Commit fixes From ef83a61a0c6257445395568d0ffc9e58f0d56854 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Thu, 4 Jun 2026 17:35:33 +0300 Subject: [PATCH 18/42] chore: actions: typos --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 337b607c..9e24773d 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -30,7 +30,7 @@ jobs: - name: Lint run: redrun fix:lint - name: Typos - uses: coderaiser/typos.ai@v1.1.5 + uses: coderaiser/typos.ai@v1.1.8 with: key: ${{ secrets.TYPOS_AI_KEY }} - name: Commit fixes From 78b206e06b59cae8eccefa978637b9d080498a1d Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 15 Jun 2026 14:47:09 +0300 Subject: [PATCH 19/42] feature: client: key: Ctrl + L -> Ctrl + Shift + L: Log out (#466) --- HELP.md | 2 +- client/key/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HELP.md b/HELP.md index 0b32ca8d..561a7a7f 100644 --- a/HELP.md +++ b/HELP.md @@ -190,12 +190,12 @@ Then, start the server again with `cloudcmd` and reload the page. | `Ctrl + R` | refresh | `Ctrl + D` | clear local storage | `Ctrl + A` | select all files in a panel -| `Ctrl + L` | logout | `Ctrl + M` | [rename selected files](https://github.com/coderaiser/cloudcmd/releases/tag/v12.1.0) in editor | `Ctrl + U` | swap panels | `Ctrl + F3` | sort by name | `Ctrl + F5` | sort by date | `Ctrl + F6` | sort by size +| `Ctrl + Shift + L` | logout | `Ctrl + Command + .` | show/hide dot files | `Up` | move cursor up | `Down` | move cursor down diff --git a/client/key/index.js b/client/key/index.js index 6f55746c..283a7f11 100644 --- a/client/key/index.js +++ b/client/key/index.js @@ -443,7 +443,7 @@ async function _switchKey(event) { break; case KEY.L: - if (ctrlMeta) { + if (ctrlMeta && shift) { CloudCmd.logOut(); event.preventDefault(); } From c36c32cb084420400b678e37d61f18efb71efc7a Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 15 Jun 2026 14:48:32 +0300 Subject: [PATCH 20/42] chore: lint --- .github/workflows/nodejs.yml | 4 +++- static/user-menu.spec.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 9e24773d..429e5568 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -24,9 +24,11 @@ jobs: with: node-version: ${{ matrix.node-version }} - name: Install Redrun - run: bun i redrun -g --no-save + run: bun i redrun madrun -g --no-save - name: Install run: bun i --no-save + - name: Init Madrun + run: madrun --init - name: Lint run: redrun fix:lint - name: Typos diff --git a/static/user-menu.spec.js b/static/user-menu.spec.js index e1543608..76f3a517 100644 --- a/static/user-menu.spec.js +++ b/static/user-menu.spec.js @@ -168,7 +168,9 @@ test('cloudcmd: static: user menu: C: exists: cancel', async (t) => { const {write} = DOM.IO; getCurrentByName.returns({}); - confirm.resolves([Error('cancel')]); + confirm.resolves([ + Error('cancel'), + ]); await defaultMenu[name]({ DOM, From a2a2d51ec106d2b0be8dc0bf352f2f4dce6fafea Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 15 Jun 2026 14:50:37 +0300 Subject: [PATCH 21/42] chore: cloudcmd: v19.19.1 --- ChangeLog | 7 +++++++ HELP.md | 3 ++- README.md | 2 +- package.json | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 49ed06d5..c5e0fbca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2026.06.15, v19.19.1 + +feature: +- 78b206e0 client: key: Ctrl + L -> Ctrl + Shift + L: Log out (#466) +- b1492885 docker: io: NPM_CONFIG_PACKAGE_LOCK=false +- 76d7bf4d docker: typos, shellcheck: add + 2026.05.26, v19.19.0 feature: diff --git a/HELP.md b/HELP.md index 561a7a7f..2967f0d0 100644 --- a/HELP.md +++ b/HELP.md @@ -1,4 +1,4 @@ -# Cloud Commander v19.19.0 +# Cloud Commander v19.19.1 ### [Main][MainURL] [Blog][BlogURL] [Demo][DemoURL] [Deploy](#deploy) @@ -1122,6 +1122,7 @@ There are a lot of ways to be involved in `Cloud Commander` development: ## Version history +- *2026.06.15*, **[v19.19.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.19.1)** - *2026.05.26*, **[v19.19.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.19.0)** - *2026.05.26*, **[v19.18.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.18.1)** - *2026.05.26*, **[v19.18.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.18.0)** diff --git a/README.md b/README.md index 332bb7fc..dfe8d8c6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cloud Commander v19.19.0 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deploy on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL] +# Cloud Commander v19.19.1 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deploy on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL] ### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL] diff --git a/package.json b/package.json index a6b65f6f..4c50b3fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcmd", - "version": "19.19.0", + "version": "19.19.1", "type": "module", "author": "coderaiser (https://github.com/coderaiser)", "description": "File manager for the web with console and editor", From 9c50fe53e797a6bcd48adbde51708c1db90dafe3 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 29 Jun 2026 17:48:04 +0300 Subject: [PATCH 22/42] feature: cloudcmd: @babel/code-frame v8.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c50b3fc..f7df45bc 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ "yargs-parser": "^22.0.0" }, "devDependencies": { - "@babel/code-frame": "^7.22.5", + "@babel/code-frame": "^8.0.0", "@babel/core": "^7.22.5", "@babel/plugin-transform-optional-chaining": "^7.21.0", "@babel/preset-env": "^7.0.0", From 4e22a4eddeadf969077ecb28977cceaf7759ec3c Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 29 Jun 2026 17:48:04 +0300 Subject: [PATCH 23/42] feature: cloudcmd: @babel/core v8.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f7df45bc..7f824e4f 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ }, "devDependencies": { "@babel/code-frame": "^8.0.0", - "@babel/core": "^7.22.5", + "@babel/core": "^8.0.1", "@babel/plugin-transform-optional-chaining": "^7.21.0", "@babel/preset-env": "^7.0.0", "@cloudcmd/clipboard": "^2.0.0", From ad3bf95e5f6946b41ebea8f66699b68d842987ba Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 29 Jun 2026 17:48:05 +0300 Subject: [PATCH 24/42] feature: cloudcmd: @babel/plugin-transform-optional-chaining v8.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7f824e4f..30187537 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ "devDependencies": { "@babel/code-frame": "^8.0.0", "@babel/core": "^8.0.1", - "@babel/plugin-transform-optional-chaining": "^7.21.0", + "@babel/plugin-transform-optional-chaining": "^8.0.1", "@babel/preset-env": "^7.0.0", "@cloudcmd/clipboard": "^2.0.0", "@cloudcmd/create-element": "^2.0.0", From 601489861021ec5a0a255d880fd571112413b98b Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 29 Jun 2026 17:48:06 +0300 Subject: [PATCH 25/42] feature: cloudcmd: @babel/preset-env v8.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 30187537..b836fb34 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,7 @@ "@babel/code-frame": "^8.0.0", "@babel/core": "^8.0.1", "@babel/plugin-transform-optional-chaining": "^8.0.1", - "@babel/preset-env": "^7.0.0", + "@babel/preset-env": "^8.0.2", "@cloudcmd/clipboard": "^2.0.0", "@cloudcmd/create-element": "^2.0.0", "@cloudcmd/modal": "^4.0.0", From 579c9e41d7c2032175a03c36bf50bc5e52047ac4 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 29 Jun 2026 17:48:06 +0300 Subject: [PATCH 26/42] feature: cloudcmd: serve-once v4.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b836fb34..164ec939 100644 --- a/package.json +++ b/package.json @@ -205,7 +205,7 @@ "request": "^2.76.0", "rimraf": "^6.0.1", "scroll-into-view-if-needed": "^3.0.4", - "serve-once": "^3.0.1", + "serve-once": "^4.0.1", "smalltalk": "^5.0.0", "style-loader": "^4.0.0", "superc8": "^12.6.0", From 046a4ef22ecd999aa4c71ec06da21d0117a7b3be Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 29 Jun 2026 17:54:20 +0300 Subject: [PATCH 27/42] chore: lint --- .github/workflows/docker-io.yml | 2 ++ .github/workflows/docker.yml | 2 ++ server/cloudcmd.spec.js | 2 +- server/markdown/index.spec.js | 2 +- server/route.spec.js | 2 +- server/user-menu.spec.js | 2 +- test/rest/config.js | 2 +- test/rest/copy.js | 2 +- test/rest/fs.js | 2 +- test/rest/move.js | 2 +- test/rest/pack.js | 2 +- test/rest/rename.js | 2 +- test/server/modulas.js | 2 +- test/static.js | 2 +- 14 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docker-io.yml b/.github/workflows/docker-io.yml index beff4589..d7a5afe0 100644 --- a/.github/workflows/docker-io.yml +++ b/.github/workflows/docker-io.yml @@ -1,4 +1,6 @@ name: Docker IO +permissions: + contents: write on: - push jobs: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1a85b29e..5c81d368 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,4 +1,6 @@ name: Docker CI +permissions: + contents: write on: push: tags: diff --git a/server/cloudcmd.spec.js b/server/cloudcmd.spec.js index 97949cd3..a7e7589c 100644 --- a/server/cloudcmd.spec.js +++ b/server/cloudcmd.spec.js @@ -1,6 +1,6 @@ import path, {dirname} from 'node:path'; import {fileURLToPath} from 'node:url'; -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import {test, stub} from 'supertape'; import cloudcmd, { _isDev, diff --git a/server/markdown/index.spec.js b/server/markdown/index.spec.js index 466271bd..f705915e 100644 --- a/server/markdown/index.spec.js +++ b/server/markdown/index.spec.js @@ -3,7 +3,7 @@ import {join} from 'node:path'; import {promisify} from 'node:util'; import {tryToCatch} from 'try-to-catch'; import test from 'supertape'; -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import {cloudcmd} from '#server/cloudcmd'; import markdown from './index.js'; diff --git a/server/route.spec.js b/server/route.spec.js index 7387a9fc..ed19fdc6 100644 --- a/server/route.spec.js +++ b/server/route.spec.js @@ -4,7 +4,7 @@ import {Readable} from 'node:stream'; import fs from 'node:fs'; import {tryToCatch} from 'try-to-catch'; import {test, stub} from 'supertape'; -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import {cloudcmd} from '#server/cloudcmd'; import {_getReadDir} from './route.js'; diff --git a/server/user-menu.spec.js b/server/user-menu.spec.js index bb78b4e8..7c5fcf2c 100644 --- a/server/user-menu.spec.js +++ b/server/user-menu.spec.js @@ -2,7 +2,7 @@ import {dirname, join} from 'node:path'; import {fileURLToPath} from 'node:url'; import {readFileSync} from 'node:fs'; import {test, stub} from 'supertape'; -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import {putout} from 'putout'; import {userMenu} from './user-menu.js'; diff --git a/test/rest/config.js b/test/rest/config.js index 737d422e..b45a0d18 100644 --- a/test/rest/config.js +++ b/test/rest/config.js @@ -1,4 +1,4 @@ -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import test from 'supertape'; import {cloudcmd} from '#server/cloudcmd'; diff --git a/test/rest/copy.js b/test/rest/copy.js index f11ef7d4..e6d8a03f 100644 --- a/test/rest/copy.js +++ b/test/rest/copy.js @@ -1,7 +1,7 @@ import {dirname, join} from 'node:path'; import {fileURLToPath} from 'node:url'; import {mkdirSync} from 'node:fs'; -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import test from 'supertape'; import {rimraf} from 'rimraf'; import {cloudcmd} from '#server/cloudcmd'; diff --git a/test/rest/fs.js b/test/rest/fs.js index 721320a2..db438a71 100644 --- a/test/rest/fs.js +++ b/test/rest/fs.js @@ -1,4 +1,4 @@ -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import test from 'supertape'; import {cloudcmd} from '#server/cloudcmd'; diff --git a/test/rest/move.js b/test/rest/move.js index 696b36a3..073c520f 100644 --- a/test/rest/move.js +++ b/test/rest/move.js @@ -1,7 +1,7 @@ import {EventEmitter} from 'node:events'; import wait from '@iocmd/wait'; import {test, stub} from 'supertape'; -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import {cloudcmd} from '#server/cloudcmd'; test('cloudcmd: rest: move', async (t) => { diff --git a/test/rest/pack.js b/test/rest/pack.js index 8a970299..4c7a49ed 100644 --- a/test/rest/pack.js +++ b/test/rest/pack.js @@ -6,7 +6,7 @@ import test from 'supertape'; import tar from 'tar-stream'; import gunzip from 'gunzip-maybe'; import pullout from 'pullout'; -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import {cloudcmd} from '#server/cloudcmd'; const __filename = fileURLToPath(import.meta.url); diff --git a/test/rest/rename.js b/test/rest/rename.js index 8dd8e1f8..eb773dba 100644 --- a/test/rest/rename.js +++ b/test/rest/rename.js @@ -2,7 +2,7 @@ import fs from 'node:fs'; import test from 'supertape'; import {Volume} from 'memfs'; import {ufs} from 'unionfs'; -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import {cloudcmd} from '#server/cloudcmd'; test('cloudcmd: rest: rename', async (t) => { diff --git a/test/server/modulas.js b/test/server/modulas.js index 5c7c6d9e..0085449e 100644 --- a/test/server/modulas.js +++ b/test/server/modulas.js @@ -1,7 +1,7 @@ import {createRequire} from 'node:module'; import {dirname, join} from 'node:path'; import {fileURLToPath} from 'node:url'; -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import {test, stub} from 'supertape'; import {cloudcmd} from '#server/cloudcmd'; import modulas from '../../server/modulas.js'; diff --git a/test/static.js b/test/static.js index cdb6b9e7..0f0da5cb 100644 --- a/test/static.js +++ b/test/static.js @@ -1,5 +1,5 @@ import {Buffer} from 'node:buffer'; -import serveOnce from 'serve-once'; +import {serveOnce} from 'serve-once'; import test from 'supertape'; import criton from 'criton'; import {cloudcmd} from '#server/cloudcmd'; From c04d363c4670373501c2d25b05d940b0126246d8 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 29 Jun 2026 18:16:34 +0300 Subject: [PATCH 28/42] test: e2e: spawn --- test-e2e/ratelimit.js | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/test-e2e/ratelimit.js b/test-e2e/ratelimit.js index 632916c5..54cdbca8 100644 --- a/test-e2e/ratelimit.js +++ b/test-e2e/ratelimit.js @@ -1,15 +1,10 @@ import process from 'node:process'; +import {spawn} from 'node:child_process'; import {test} from 'supertape'; -let i = 0; - test('cloudcmd: server: ratelimit: x-forwarded-for', async (t) => { const PORT = 3000; - - process.env.PORT = PORT; - process.env.CLOUDCMD_LOG = 0; - - await import('../bin/cloudcmd.js'); + const child = await run(PORT); const {status} = await fetch(`http://localhost:${PORT}`, { headers: { @@ -17,7 +12,7 @@ test('cloudcmd: server: ratelimit: x-forwarded-for', async (t) => { }, }); - process.kill(process.pid, 'SIGUSR1'); + child.kill(); t.notEqual(status, 500); t.end(); @@ -27,18 +22,34 @@ test('cloudcmd: server: ratelimit', async (t) => { const PORT = 3001; const STATUS = 429; - process.env.PORT = PORT; - process.env.CLOUDCMD_LOG = 0; - - await import(`../bin/cloudcmd.js?${i++}`); + const child = await run(PORT); for (let i = 0; i < 1000; i++) { await fetch(`http://localhost:${PORT}`); } const {status} = await fetch(`http://localhost:${PORT}`); - process.kill(process.pid, 'SIGUSR1'); + child.kill(); t.equal(status, STATUS); t.end(); }); + +function run(port) { + return new Promise((resolve, reject) => { + const child = spawn(new URL('../bin/cloudcmd.js', import.meta.url).pathname, [], { + env: { + ...process.env, + PORT: port, + }, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + child.stdout.on('data', (a) => { + if (a.toString().includes('url')) + resolve(child); + }); + + child.on('error', reject); + }); +} From 8629f6797246ab2e1422861606e09c489eb9af11 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 29 Jun 2026 18:19:08 +0300 Subject: [PATCH 29/42] feature: cloudcmd: docker: io: arm --- .github/workflows/docker-io.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-io.yml b/.github/workflows/docker-io.yml index d7a5afe0..8cbeb042 100644 --- a/.github/workflows/docker-io.yml +++ b/.github/workflows/docker-io.yml @@ -51,7 +51,7 @@ jobs: with: context: . file: docker/Dockerfile.io - platforms: linux/amd64 + platforms: linux/amd64,linux/arm64 push: true tags: | coderaiser/cloudcmd:io From 92c79830f50b723e0dbf5689ba8cd922c773b3cf Mon Sep 17 00:00:00 2001 From: coderaiser Date: Wed, 15 Jul 2026 08:21:03 +0000 Subject: [PATCH 30/42] chore: madrun: test: rm test-e2e --- .madrun.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.madrun.js b/.madrun.js index 7452328c..0e1d2941 100644 --- a/.madrun.js +++ b/.madrun.js @@ -28,7 +28,7 @@ export default { 'lint:fresh': () => run('lint', '--fresh'), 'fix:lint': async () => `putout --rulesdir rules --fix . && redlint fix`, 'lint:stream': () => run('lint', '-f stream'), - 'test': () => [testEnv, `tape '{test,test-e2e}/**/*.js' '{bin,client,static,common,server}/**/*.spec.js' -f fail`], + 'test': () => [testEnv, `tape '{test}/**/*.js' '{bin,client,static,common,server}/**/*.spec.js' -f fail`], 'test:e2e': () => `tape 'test-e2e/**/*.js'`, 'test:client': () => `tape 'test/client/**/*.js'`, 'test:server': () => `tape 'test/**/*.js' 'server/**/*.spec.js' 'common/**/*.spec.js'`, From a52a6aae628f7dd1b1032d069a2f02a1e3bac63e Mon Sep 17 00:00:00 2001 From: coderaiser Date: Wed, 15 Jul 2026 09:36:03 +0000 Subject: [PATCH 31/42] test: coverage --- client/key/vim/find.spec.js | 32 +++++++++++++++++++++++++++++++- server/rest/index.js | 16 ++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/client/key/vim/find.spec.js b/client/key/vim/find.spec.js index 59c9a249..c1164aea 100644 --- a/client/key/vim/find.spec.js +++ b/client/key/vim/find.spec.js @@ -1,6 +1,6 @@ import test from 'supertape'; import {getDOM} from './globals.fixture.js'; -import {_next, _previous} from './find.js'; +import {_next, _previous, find, findNext, findPrevious} from './find.js'; globalThis.DOM = getDOM(); @@ -11,9 +11,39 @@ test('cloudcmd: client: vim: _next', (t) => { t.end(); }); +test('cloudcmd: client: vim: _next: increment', (t) => { + const result = _next(0, 2); + + t.equal(result, 1, 'should return 1'); + t.end(); +}); + test('cloudcmd: client: vim: _previous', (t) => { const result = _previous(0, 2); t.equal(result, 1, 'should return 1'); t.end(); }); + +test('cloudcmd: client: vim: _previous: decrement', (t) => { + const result = _previous(1, 2); + + t.equal(result, 0, 'should return 0'); + t.end(); +}); + +test('cloudcmd: client: vim: findNext: after find', (t) => { + find('a', ['alpha', 'beta', 'apple']); + const result = findNext(); + + t.equal(result, 'beta', 'should return next found name'); + t.end(); +}); + +test('cloudcmd: client: vim: findPrevious: after find', (t) => { + find('a', ['alpha', 'beta', 'apple']); + const result = findPrevious(); + + t.equal(result, 'apple', 'should return previous found name'); + t.end(); +}); diff --git a/server/rest/index.js b/server/rest/index.js index 68bec54e..c3a3eda0 100644 --- a/server/rest/index.js +++ b/server/rest/index.js @@ -32,6 +32,8 @@ const UserError = (msg) => { return error; }; +export const _UserError = UserError; + export default currify(({config, fs = _fs, moveFiles = _moveFiles}, request, response, next) => { const name = ponse.getPathName(request); const regExp = RegExp(`^${apiURL}`); @@ -153,6 +155,8 @@ function getPackReg(packer) { return /\.tar\.gz$/; } +export const _getPackReg = getPackReg; + function streamPack(cmd, response, packer) { const noop = () => {}; const filename = cmd.replace(getPackReg(packer), ''); @@ -172,6 +176,8 @@ function getCMD(cmd) { return cmd; } +export const _getCMD = getCMD; + const getMoveMsg = (names) => formatMsg('move', names); const getRenameMsg = (from, to) => { @@ -276,6 +282,8 @@ function rename(rootDir, from, to, fs, callback) { return fs.rename(fromRooted, toRooted, fn); } +export const _rename = rename; + function pack(from, to, names, config, fn) { const rootDir = config('root'); const packer = config('packer'); @@ -294,6 +302,8 @@ function pack(from, to, names, config, fn) { operation('pack', packer, from, to, names, fn); } +export const _pack = pack; + function extract(from, to, config, fn) { const rootDir = config('root'); @@ -307,6 +317,8 @@ function extract(from, to, config, fn) { operation('extract', config('packer'), from, to, fn); } +export const _extract = extract; + function getPacker(operation, packer) { if (operation === 'extract') return inly; @@ -317,6 +329,8 @@ function getPacker(operation, packer) { return jaguar.pack; } +export const _getPacker = getPacker; + function operation(op, packer, from, to, names, fn) { if (!fn) { fn = names; @@ -345,6 +359,8 @@ function operation(op, packer, from, to, names, fn) { }); } +export const _operation = operation; + function copy(from, to, names, fn) { copymitter(from, to, names) .on('error', fn) From a9e29eee8563e18953f53fec8d07cf200d90e540 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Wed, 15 Jul 2026 10:31:07 +0000 Subject: [PATCH 32/42] test: cloudcmd: coverage --- client/key/binder.spec.js | 28 +++++++++ client/key/vim/index.spec.js | 80 ++++++++++++++++++++++++++ server/cloudcmd.spec.js | 65 +++++++++++++++++++++ server/rest/index.spec.js | 107 ++++++++++++++++++++++++++++++++++- server/validate.spec.js | 24 ++++++++ 5 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 client/key/binder.spec.js diff --git a/client/key/binder.spec.js b/client/key/binder.spec.js new file mode 100644 index 00000000..f9d54fdd --- /dev/null +++ b/client/key/binder.spec.js @@ -0,0 +1,28 @@ +import {test} from 'supertape'; +import {createBinder} from './binder.js'; + +test('client: key: binder: isBind: default', (t) => { + const binder = createBinder(); + + t.notOk(binder.isBind(), 'should not be bind by default'); + t.end(); +}); + +test('client: key: binder: setBind', (t) => { + const binder = createBinder(); + + binder.setBind(); + + t.ok(binder.isBind(), 'should be bind'); + t.end(); +}); + +test('client: key: binder: unsetBind', (t) => { + const binder = createBinder(); + + binder.setBind(); + binder.unsetBind(); + + t.notOk(binder.isBind(), 'should not be bind'); + t.end(); +}); diff --git a/client/key/vim/index.spec.js b/client/key/vim/index.spec.js index e1ca4eee..a10650ad 100644 --- a/client/key/vim/index.spec.js +++ b/client/key/vim/index.spec.js @@ -1,6 +1,7 @@ import {test, stub} from 'supertape'; import {getDOM, getCloudCmd} from './globals.fixture.js'; import vim, {selectFile as vimSelectFile} from './index.js'; +import * as finder from './find.js'; globalThis.DOM = getDOM(); globalThis.CloudCmd = getCloudCmd(); @@ -675,3 +676,82 @@ test('cloudcmd: client: vim: rename', async (t) => { t.calledWithNoArgs(renameCurrent); t.end(); }); + +test('cloudcmd: client: key: cc: operationCopy', (t) => { + const show = stub(); + const preventDefault = stub(); + + const Operation = { + show, + }; + + const event = { + preventDefault, + }; + + vim('c', event, { + Operation, + }); + + vim('c', event, { + Operation, + }); + + t.calledWith(show, ['copy'], 'should show copy operation'); + t.end(); +}); + +test('cloudcmd: client: key: mm: operationMove', (t) => { + const show = stub(); + const preventDefault = stub(); + + const Operation = { + show, + }; + + const event = { + preventDefault, + }; + + vim('m', event, { + Operation, + }); + + vim('m', event, { + Operation, + }); + + t.calledWith(show, ['move'], 'should show move operation'); + t.end(); +}); + +test('cloudcmd: client: key: n: findNext: real', (t) => { + const setCurrentByName = stub(); + + finder.find('a', ['alpha', 'beta', 'apple']); + + const event = {}; + + vim('n', event, { + setCurrentByName, + }); + + t.calledWith(setCurrentByName, ['beta'], 'should set current by next found name'); + t.end(); +}); + +test('cloudcmd: client: key: N: findPrevious: real', (t) => { + const setCurrentByName = stub(); + + finder.find('a', ['alpha', 'beta', 'apple']); + + const event = {}; + + vim('N', event, { + setCurrentByName, + }); + + t.calledWith(setCurrentByName, ['apple'], 'should set current by previous found name'); + t.end(); +}); + diff --git a/server/cloudcmd.spec.js b/server/cloudcmd.spec.js index a7e7589c..8b31f3b5 100644 --- a/server/cloudcmd.spec.js +++ b/server/cloudcmd.spec.js @@ -10,6 +10,7 @@ import cloudcmd, { _initAuth, _getIndexPath, } from '#server/cloudcmd'; +import {connect} from '../test/before.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -178,3 +179,67 @@ test('cloudcmd: sw', async (t) => { t.equal(status, 200, 'should return sw'); t.end(); }); + +test('cloudcmd: no params', (t) => { + const middle = cloudcmd(); + + t.ok(Array.isArray(middle), 'should return middleware list when no params passed'); + t.end(); +}); + +test('cloudcmd: listen: terminal', async (t) => { + const {done} = await connect({ + config: { + terminal: true, + }, + }); + + await done(); + + t.pass('should listen with terminal enabled'); + t.end(); +}); + +test('cloudcmd: middle: dropbox', async (t) => { + const {port, done} = await connect({ + config: { + dropbox: true, + dropboxToken: 'hello', + }, + }); + + const response = await fetch(`http://localhost:${port}/api/v1/dropbox/nonexistent`); + + await done(); + + t.ok(response.status, 'should mount dropbox route'); + t.end(); +}); + + +test('cloudcmd: logout', async (t) => { + const {status} = await request.get('/logout'); + + t.equal(status, 401, 'should return 401 for /logout'); + t.end(); +}); + +test('cloudcmd: modules', (t) => { + const middle = cloudcmd({ + modules: { + hello: () => {}, + }, + }); + + t.ok(Array.isArray(middle), 'should return middleware list with modules'); + t.end(); +}); + +test('cloudcmd: setUrl: cloudcmd.js', async (t) => { + const {status} = await request.get('/cloudcmd.js'); + + t.equal(status, 200, 'should serve cloudcmd.js'); + t.end(); +}); + + diff --git a/server/rest/index.spec.js b/server/rest/index.spec.js index 0fd64d7c..0b25b876 100644 --- a/server/rest/index.spec.js +++ b/server/rest/index.spec.js @@ -1,4 +1,4 @@ -import {test} from 'supertape'; +import {test, stub} from 'supertape'; import {tryToCatch} from 'try-to-catch'; import { _formatMsg, @@ -6,6 +6,13 @@ import { _isRootWin32, _isRootAll, _onPUT, + _UserError, + _getPackReg, + _getCMD, + _rename, + _pack, + _extract, + _getPacker, } from './index.js'; test('rest: formatMsg', (t) => { @@ -70,3 +77,101 @@ test('rest: onPUT: no callback', async (t) => { t.equal(e.message, 'callback should be a function!', 'should throw when no callback'); t.end(); }); + + +test('rest: UserError: message', (t) => { + const result = _UserError('hello'); + + t.equal(result.message, 'hello', 'should set message'); + t.end(); +}); + +test('rest: UserError: code', (t) => { + const result = _UserError('hello'); + + t.equal(result.code, 'EUSER', 'should set code'); + t.end(); +}); + +test('rest: getPackReg: zip', (t) => { + const result = _getPackReg('zip'); + + t.ok(result.test('file.zip'), 'should match .zip'); + t.end(); +}); + +test('rest: getPackReg: tar', (t) => { + const result = _getPackReg('tar'); + + t.ok(result.test('file.tar.gz'), 'should match .tar.gz'); + t.end(); +}); + +test('rest: getCMD: with slash', (t) => { + const result = _getCMD('/move'); + + t.equal(result, 'move', 'should strip leading slash'); + t.end(); +}); + +test('rest: getCMD: no slash', (t) => { + const result = _getCMD('move'); + + t.equal(result, 'move', 'should return as is'); + t.end(); +}); + +test('rest: rename: no from', async (t) => { + const callback = stub(); + + _rename('/', null, 'to', null, callback); + + const msg = '"from" should be filled'; + + t.calledWith(callback, [_UserError(msg)], 'should return UserError'); + t.end(); +}); + +test('rest: rename: no to', async (t) => { + const callback = stub(); + + _rename('/', 'from', null, null, callback); + + const msg = '"to" should be filled'; + + t.calledWith(callback, [_UserError(msg)], 'should return UserError'); + t.end(); +}); + +test('rest: rename: success', (t) => { + const callback = stub(); + const fs = { + rename: stub(), + }; + + _rename('/root', 'from', 'to', fs, callback); + + t.calledWith(fs.rename, ['/root/from', '/root/to', callback], 'should call fs.rename'); + t.end(); +}); + +test('rest: getPacker: extract', (t) => { + const result = _getPacker('extract', 'zip'); + + t.equal(typeof result, 'function', 'should return function'); + t.end(); +}); + +test('rest: getPacker: pack zip', (t) => { + const result = _getPacker('pack', 'zip'); + + t.equal(typeof result, 'function', 'should return function'); + t.end(); +}); + +test('rest: getPacker: pack tar', (t) => { + const result = _getPacker('pack', 'tar'); + + t.equal(typeof result, 'function', 'should return function'); + t.end(); +}); diff --git a/server/validate.spec.js b/server/validate.spec.js index 76243e30..d2fd1bd9 100644 --- a/server/validate.spec.js +++ b/server/validate.spec.js @@ -131,3 +131,27 @@ test('validate: theme: wrong', (t) => { t.calledWith(exit, [msg], 'should call exit'); t.end(); }); + + +test('validate: menu: not valid', (t) => { + const exit = stub(); + const msg = 'cloudcmd --menu: could be "supermenu" or "aleman" only'; + + validate.menu('hello', { + exit, + }); + + t.calledWith(exit, [msg], 'should call fn'); + t.end(); +}); + +test('validate: menu: valid', (t) => { + const exit = stub(); + + validate.menu('supermenu', { + exit, + }); + + t.notCalled(exit, 'should not call fn'); + t.end(); +}); From 8258cdb651205a26e527bc066d0c50f6b4f7b165 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Wed, 15 Jul 2026 21:18:06 +0200 Subject: [PATCH 33/42] chore: rm rulesdir --- .madrun.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.madrun.js b/.madrun.js index 0e1d2941..9910aa7c 100644 --- a/.madrun.js +++ b/.madrun.js @@ -26,7 +26,7 @@ export default { 'watch:lint': () => 'nodemon -w client -w server -w test -w common -w .webpack -x "putout -s"', 'fresh:lint': () => run('lint', '--fresh'), 'lint:fresh': () => run('lint', '--fresh'), - 'fix:lint': async () => `putout --rulesdir rules --fix . && redlint fix`, + 'fix:lint': async () => `putout --fix . && redlint fix`, 'lint:stream': () => run('lint', '-f stream'), 'test': () => [testEnv, `tape '{test}/**/*.js' '{bin,client,static,common,server}/**/*.spec.js' -f fail`], 'test:e2e': () => `tape 'test-e2e/**/*.js'`, From 7edea50296e115a2c15a43917baca1647593f811 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Wed, 15 Jul 2026 23:26:28 +0300 Subject: [PATCH 34/42] chore: lint --- .madrun.js | 2 +- client/dom/current-file.spec.js | 21 ++++++++------------- client/dom/index.js | 6 +----- client/dom/load-remote.js | 16 +++++++++++----- client/key/vim/find.spec.js | 8 +++++++- client/key/vim/index.spec.js | 1 - client/modules/view/index.spec.js | 4 +++- common/util.spec.js | 11 +++++++++-- server/cloudcmd.spec.js | 18 ++++++++++-------- server/exit.spec.js | 17 ++++++++++------- server/rest/index.spec.js | 19 ++++++++++--------- server/validate.spec.js | 1 - test/server/env.js | 20 ++++++++++++-------- 13 files changed, 82 insertions(+), 62 deletions(-) diff --git a/.madrun.js b/.madrun.js index 9910aa7c..9c75535f 100644 --- a/.madrun.js +++ b/.madrun.js @@ -21,7 +21,7 @@ export default { 'start:dev', ]), 'lint:all': () => run('lint:progress'), - 'lint': () => 'redlint fix; putout . --rulesdir rules', + 'lint': () => 'redlint fix; putout .', 'lint:progress': () => run('lint', '-f progress'), 'watch:lint': () => 'nodemon -w client -w server -w test -w common -w .webpack -x "putout -s"', 'fresh:lint': () => run('lint', '--fresh'), diff --git a/client/dom/current-file.spec.js b/client/dom/current-file.spec.js index 6bf0509b..85b18a29 100644 --- a/client/dom/current-file.spec.js +++ b/client/dom/current-file.spec.js @@ -19,14 +19,13 @@ test('current-file: setCurrentName: setAttribute', (t) => { currentFile.setCurrentName('hello', current); + globalThis.DOM = DOM; + globalThis.CloudCmd = CloudCmd; + t.calledWith(setAttribute, [ 'data-name', 'js-file-aGVsbG8=', ], 'should call setAttribute'); - - globalThis.DOM = DOM; - globalThis.CloudCmd = CloudCmd; - t.end(); }); @@ -41,14 +40,13 @@ test('current-file: setCurrentName: setAttribute: cyrillic', (t) => { currentFile.setCurrentName('ай', current); + globalThis.DOM = DOM; + globalThis.CloudCmd = CloudCmd; + t.calledWith(setAttribute, [ 'data-name', 'js-file-JUQwJUIwJUQwJUI5', ], 'should call setAttribute'); - - globalThis.DOM = DOM; - globalThis.CloudCmd = CloudCmd; - t.end(); }); @@ -76,11 +74,10 @@ test('current-file: emit', (t) => { currentFile.setCurrentName('hello', current); - t.calledWith(emit, ['current-file', current], 'should call emit'); - globalThis.DOM = DOM; globalThis.CloudCmd = CloudCmd; + t.calledWith(emit, ['current-file', current], 'should call emit'); t.end(); }); @@ -96,14 +93,12 @@ test('current-file: setCurrentName: return', (t) => { globalThis.CloudCmd = getCloudCmd(); const current = create(); - const result = currentFile.setCurrentName('hello', current); - t.equal(result, link, 'should return link'); - globalThis.DOM = DOM; globalThis.CloudCmd = CloudCmd; + t.equal(result, link, 'should return link'); t.end(); }); diff --git a/client/dom/index.js b/client/dom/index.js index f6ce6bc7..a82b6f72 100644 --- a/client/dom/index.js +++ b/client/dom/index.js @@ -442,11 +442,7 @@ export function getFilenames(files) { if (name === '..') allFiles.shift(); - const names = allFiles.map((current) => { - return DOM.getCurrentName(current); - }); - - return names; + return allFiles.map(DOM.getCurrentName); } /** diff --git a/client/dom/load-remote.js b/client/dom/load-remote.js index 43af0cf5..5be8e327 100644 --- a/client/dom/load-remote.js +++ b/client/dom/load-remote.js @@ -33,13 +33,19 @@ export const loadRemote = callbackify(async (name, options) => { local = [module.local]; } - const localURL = local.map((url) => prefix + url); + const localURL = []; - const remoteURL = remoteTmpls.map((tmpl) => { - return rendy(tmpl, { + for (const url of local) { + localURL.push(prefix + url); + } + + const remoteURL = []; + + for (const tmpl of remoteTmpls) { + remoteURL.push(rendy(tmpl, { version, - }); - }); + })); + } if (online) { const [e] = await tryToCatch(load.parallel, remoteURL); diff --git a/client/key/vim/find.spec.js b/client/key/vim/find.spec.js index c1164aea..191a3652 100644 --- a/client/key/vim/find.spec.js +++ b/client/key/vim/find.spec.js @@ -1,6 +1,12 @@ import test from 'supertape'; import {getDOM} from './globals.fixture.js'; -import {_next, _previous, find, findNext, findPrevious} from './find.js'; +import { + _next, + _previous, + find, + findNext, + findPrevious, +} from './find.js'; globalThis.DOM = getDOM(); diff --git a/client/key/vim/index.spec.js b/client/key/vim/index.spec.js index a10650ad..231c6577 100644 --- a/client/key/vim/index.spec.js +++ b/client/key/vim/index.spec.js @@ -754,4 +754,3 @@ test('cloudcmd: client: key: N: findPrevious: real', (t) => { t.calledWith(setCurrentByName, ['apple'], 'should set current by previous found name'); t.end(); }); - diff --git a/client/modules/view/index.spec.js b/client/modules/view/index.spec.js index 8d184548..abfbe3f4 100644 --- a/client/modules/view/index.spec.js +++ b/client/modules/view/index.spec.js @@ -31,8 +31,10 @@ test('cloudcmd: client: view: initConfig', (t) => { test('cloudcmd: client: view: initConfig: no options', (t) => { const config = _initConfig(); + const result = typeof config; + const expected = 'object'; - t.equal(typeof config, 'object'); + t.equal(result, expected); t.end(); }); diff --git a/common/util.spec.js b/common/util.spec.js index 6a32848c..6eee1087 100644 --- a/common/util.spec.js +++ b/common/util.spec.js @@ -106,11 +106,18 @@ test('util: getRegExp: no', (t) => { }); test('util: escapeRegExp: no str', (t) => { - t.equal(escapeRegExp(1), 1); + const result = escapeRegExp(1); + const expected = 1; + + t.equal(result, expected); t.end(); }); test('util: escapeRegExp', (t) => { - t.equal(escapeRegExp('#hello'), '\\#hello'); + const result = escapeRegExp('#hello'); + const expected = '\\#hello'; + + t.equal(result, expected); t.end(); }); + diff --git a/server/cloudcmd.spec.js b/server/cloudcmd.spec.js index 8b31f3b5..d79aa4c1 100644 --- a/server/cloudcmd.spec.js +++ b/server/cloudcmd.spec.js @@ -12,6 +12,8 @@ import cloudcmd, { } from '#server/cloudcmd'; import {connect} from '../test/before.js'; +const noop = () => {}; + const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -159,17 +161,20 @@ test('cloudcmd: auth: accept: no auth', (t) => { test('cloudcmd: getIndexPath: production', (t) => { const isDev = false; - const name = path.join(__dirname, '..', 'dist', 'index.html'); - t.equal(_getIndexPath(isDev), name); + const result = _getIndexPath(isDev); + const expected = path.join(__dirname, '..', 'dist', 'index.html'); + + t.equal(result, expected); t.end(); }); test('cloudcmd: getIndexPath: development', (t) => { const isDev = true; - const name = path.join(__dirname, '..', 'dist-dev', 'index.html'); + const result = _getIndexPath(isDev); + const expected = path.join(__dirname, '..', 'dist-dev', 'index.html'); - t.equal(_getIndexPath(isDev), name); + t.equal(result, expected); t.end(); }); @@ -216,7 +221,6 @@ test('cloudcmd: middle: dropbox', async (t) => { t.end(); }); - test('cloudcmd: logout', async (t) => { const {status} = await request.get('/logout'); @@ -227,7 +231,7 @@ test('cloudcmd: logout', async (t) => { test('cloudcmd: modules', (t) => { const middle = cloudcmd({ modules: { - hello: () => {}, + hello: noop, }, }); @@ -241,5 +245,3 @@ test('cloudcmd: setUrl: cloudcmd.js', async (t) => { t.equal(status, 200, 'should serve cloudcmd.js'); t.end(); }); - - diff --git a/server/exit.spec.js b/server/exit.spec.js index 3b454818..7776576f 100644 --- a/server/exit.spec.js +++ b/server/exit.spec.js @@ -4,44 +4,47 @@ import exit from './exit.js'; test('cloudcmd: exit: process.exit', (t) => { const {exit: exitOriginal} = process; + const exitStub = stub(); - process.exit = stub(); + process.exit = exitStub; exit(); - t.calledWith(process.exit, [1], 'should call process.exit'); process.exit = exitOriginal; + t.calledWith(exitStub, [1], 'should call process.exit'); t.end(); }); test('cloudcmd: exit: console.error', (t) => { const {exit: exitOriginal} = process; const {error} = console; + const errorStub = stub(); - console.error = stub(); + console.error = errorStub; process.exit = stub(); exit('hello world'); - t.calledWith(console.error, ['hello world'], 'should call console.error'); process.exit = exitOriginal; console.error = error; + t.calledWith(errorStub, ['hello world'], 'should call console.error'); t.end(); }); test('cloudcmd: exit.error: console.error: error', (t) => { const {exit: exitOriginal} = process; const {error} = console; + const errorStub = stub(); - console.error = stub(); + console.error = errorStub; process.exit = stub(); exit(Error('hello world')); - t.calledWith(console.error, ['hello world'], 'should call console.error'); - process.exit = exitOriginal; console.error = error; + t.calledWith(errorStub, ['hello world'], 'should call console.error'); t.end(); }); + diff --git a/server/rest/index.spec.js b/server/rest/index.spec.js index 0b25b876..9c672095 100644 --- a/server/rest/index.spec.js +++ b/server/rest/index.spec.js @@ -10,8 +10,6 @@ import { _getPackReg, _getCMD, _rename, - _pack, - _extract, _getPacker, } from './index.js'; @@ -78,7 +76,6 @@ test('rest: onPUT: no callback', async (t) => { t.end(); }); - test('rest: UserError: message', (t) => { const result = _UserError('hello'); @@ -96,14 +93,14 @@ test('rest: UserError: code', (t) => { test('rest: getPackReg: zip', (t) => { const result = _getPackReg('zip'); - t.ok(result.test('file.zip'), 'should match .zip'); + t.match('file.zip', result, 'should match .zip'); t.end(); }); test('rest: getPackReg: tar', (t) => { const result = _getPackReg('tar'); - t.ok(result.test('file.tar.gz'), 'should match .tar.gz'); + t.match('file.tar.gz', result, 'should match .tar.gz'); t.end(); }); @@ -121,25 +118,29 @@ test('rest: getCMD: no slash', (t) => { t.end(); }); -test('rest: rename: no from', async (t) => { +test('rest: rename: no from', (t) => { const callback = stub(); _rename('/', null, 'to', null, callback); const msg = '"from" should be filled'; - t.calledWith(callback, [_UserError(msg)], 'should return UserError'); + t.calledWith(callback, [ + _UserError(msg), + ], 'should return UserError'); t.end(); }); -test('rest: rename: no to', async (t) => { +test('rest: rename: no to', (t) => { const callback = stub(); _rename('/', 'from', null, null, callback); const msg = '"to" should be filled'; - t.calledWith(callback, [_UserError(msg)], 'should return UserError'); + t.calledWith(callback, [ + _UserError(msg), + ], 'should return UserError'); t.end(); }); diff --git a/server/validate.spec.js b/server/validate.spec.js index d2fd1bd9..639f185c 100644 --- a/server/validate.spec.js +++ b/server/validate.spec.js @@ -132,7 +132,6 @@ test('validate: theme: wrong', (t) => { t.end(); }); - test('validate: menu: not valid', (t) => { const exit = stub(); const msg = 'cloudcmd --menu: could be "supermenu" or "aleman" only'; diff --git a/test/server/env.js b/test/server/env.js index 323c0a99..34bc8959 100644 --- a/test/server/env.js +++ b/test/server/env.js @@ -4,34 +4,37 @@ import * as env from '../../server/env.js'; test('env: small', (t) => { process.env.cloudcmd_hello = 'world'; - t.equal(env.parse('hello'), 'world', 'should parse string from env'); delete process.env.cloudcmd_hello; + + t.equal(env.parse('hello'), 'world', 'should parse string from env'); t.end(); }); test('env: big', (t) => { process.env.CLOUDCMD_HELLO = 'world'; - t.equal(env.parse('hello'), 'world', 'should parse string from env'); delete process.env.CLOUDCMD_HELLO; + + t.equal(env.parse('hello'), 'world', 'should parse string from env'); t.end(); }); test('env: bool: false', (t) => { process.env.cloudcmd_terminal = 'false'; - t.notOk(env.bool('terminal'), 'should return false'); delete process.env.cloudcmd_terminal; + + t.notOk(env.bool('terminal'), 'should return false'); t.end(); }); test('env: bool: true', (t) => { process.env.cloudcmd_terminal = 'true'; - t.ok(env.bool('terminal'), 'should be true'); - delete process.env.cloudcmd_terminal; + + t.ok(env.bool('terminal'), 'should be true'); t.end(); }); @@ -39,9 +42,10 @@ test('env: bool: undefined', (t) => { const {cloudcmd_terminal} = process.env; process.env.cloudcmd_terminal = undefined; - - t.notOk(env.bool('terminal'), 'should be undefined'); - process.env.cloudcmd_terminal = cloudcmd_terminal; + + const result = env.bool('terminal'); + + t.notOk(result); t.end(); }); From fe185cc93e3fd3fe04eb971e4ecbe30fd8ec270d Mon Sep 17 00:00:00 2001 From: coderaiser Date: Wed, 15 Jul 2026 20:27:25 +0000 Subject: [PATCH 35/42] =?UTF-8?q?chore:=20cloudcmd:=20actions:=20lint=20?= =?UTF-8?q?=E2=98=98=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/util.spec.js | 1 - server/exit.spec.js | 1 - test/before.js | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/common/util.spec.js b/common/util.spec.js index 6eee1087..f239bd67 100644 --- a/common/util.spec.js +++ b/common/util.spec.js @@ -120,4 +120,3 @@ test('util: escapeRegExp', (t) => { t.equal(result, expected); t.end(); }); - diff --git a/server/exit.spec.js b/server/exit.spec.js index 7776576f..94d627ac 100644 --- a/server/exit.spec.js +++ b/server/exit.spec.js @@ -47,4 +47,3 @@ test('cloudcmd: exit.error: console.error: error', (t) => { t.calledWith(errorStub, ['hello world'], 'should call console.error'); t.end(); }); - diff --git a/test/before.js b/test/before.js index 970a4a4f..08a50f04 100644 --- a/test/before.js +++ b/test/before.js @@ -50,8 +50,7 @@ function before(options, fn = options) { })); server.listen(() => { - fn(server - .address().port, promisify(after)); + fn(server.address().port, promisify(after)); }); } From 0638ca32cba97b30352eaec70cdf9c6789bc7df5 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 16 Jul 2026 12:18:43 +0300 Subject: [PATCH 36/42] feature: migrate from webpack to rspack --- .madrun.js | 2 +- {.webpack => .rspack}/css.js | 21 +++++-------- {.webpack => .rspack}/html.js | 0 {.webpack => .rspack}/js.js | 55 ++++++++++++++++++++--------------- package.json | 9 ++---- rspack.config.js | 10 +++++++ 6 files changed, 53 insertions(+), 44 deletions(-) rename {.webpack => .rspack}/css.js (53%) rename {.webpack => .rspack}/html.js (100%) rename {.webpack => .rspack}/js.js (81%) create mode 100644 rspack.config.js diff --git a/.madrun.js b/.madrun.js index 9c75535f..efa8ce98 100644 --- a/.madrun.js +++ b/.madrun.js @@ -39,7 +39,7 @@ export default { 'coverage': async () => [testEnv, `c8 ${await cutEnv('test')}`], 'coverage:report': () => 'c8 report --reporter=lcov', 'report': () => 'c8 report --reporter=lcov', - '6to5': () => [buildEnv, 'webpack --progress'], + '6to5': () => [buildEnv, 'rspack build --config rspack.config.js'], '6to5:client': () => run('6to5', '--mode production'), '6to5:client:dev': async () => await run('6to5', '--mode development', { NODE_ENV: 'development', diff --git a/.webpack/css.js b/.rspack/css.js similarity index 53% rename from .webpack/css.js rename to .rspack/css.js index 311290cc..10d28250 100644 --- a/.webpack/css.js +++ b/.rspack/css.js @@ -1,19 +1,18 @@ import {env} from 'node:process'; -import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; -import MiniCssExtractPlugin from 'mini-css-extract-plugin'; +import {rspack} from '@rspack/core'; +const {CssExtractRspackPlugin} = rspack; const isDev = env.NODE_ENV === 'development'; -const clean = (a) => a.filter(Boolean); -const plugins = clean([ - new MiniCssExtractPlugin({ +const plugins = [ + new CssExtractRspackPlugin({ filename: '[name].css', }), -]); +]; const rules = [{ test: /\.css$/i, - use: [MiniCssExtractPlugin.loader, { + use: [CssExtractRspackPlugin.loader, { loader: 'css-loader', options: { url: true, @@ -33,13 +32,7 @@ export default { optimization: { minimize: !isDev, minimizer: [ - new CssMinimizerPlugin({ - minimizerOptions: { - preset: ['default', { - svgo: false, - }], - }, - }), + new rspack.LightningCssMinimizerRspackPlugin(), ], }, }; diff --git a/.webpack/html.js b/.rspack/html.js similarity index 100% rename from .webpack/html.js rename to .rspack/html.js diff --git a/.webpack/js.js b/.rspack/js.js similarity index 81% rename from .webpack/js.js rename to .rspack/js.js index fdc88556..fae11fc8 100644 --- a/.webpack/js.js +++ b/.rspack/js.js @@ -1,12 +1,15 @@ import {resolve, sep} from 'node:path'; +import {fileURLToPath} from 'node:url'; import {env} from 'node:process'; -import webpack from 'webpack'; -import WebpackBar from 'webpackbar'; +import {rspack} from '@rspack/core'; + +const resolveModule = (a) => fileURLToPath(import.meta.resolve(a)); const { EnvironmentPlugin, NormalModuleReplacementPlugin, -} = webpack; + ProvidePlugin, +} = rspack; const modules = './modules'; const dirModules = './client/modules'; @@ -22,27 +25,34 @@ const dist = resolve(rootDir, 'dist'); const distDev = resolve(rootDir, 'dist-dev'); const devtool = isDev ? 'eval' : 'source-map'; -const notEmpty = (a) => a; -const clean = (array) => array.filter(notEmpty); - const noParse = (a) => a.endsWith('.spec.js'); -const options = { - babelrc: true, -}; -const rules = clean([ - !isDev && { - test: /\.[mc]?js$/, +// codegen.macro is a babel-macro (build-time codegen), not supported by +// Rspack's native SWC transform, so client/sw/sw.js (the only file that +// uses it) keeps going through babel-loader. Everything else uses +// Rspack's built-in SWC loader, which is the main source of the speedup. +const rules = [ + { + test: /sw\/sw\.js$/, exclude: /node_modules/, loader: 'babel-loader', }, - isDev && { + { test: /\.[mc]?js$/, - exclude: /node_modules/, - loader: 'babel-loader', - options, + exclude: [/node_modules/, /sw\/sw\.js$/], + loader: 'builtin:swc-loader', + options: { + jsc: { + parser: { + syntax: 'ecmascript', + }, + }, + env: { + targets: 'defaults', + }, + }, }, -]); +]; const plugins = [ new NormalModuleReplacementPlugin(/^node:/, (resource) => { @@ -52,8 +62,7 @@ const plugins = [ new EnvironmentPlugin({ NODE_ENV, }), - new WebpackBar(), - new webpack.ProvidePlugin({ + new ProvidePlugin({ process: 'process/browser', }), ]; @@ -99,9 +108,9 @@ export default { 'node:path': 'path', }, fallback: { - path: import.meta.resolve('path-browserify'), - process: import.meta.resolve('process/browser'), - util: import.meta.resolve('util'), + path: resolveModule('path-browserify'), + process: resolveModule('process/browser'), + util: resolveModule('util'), }, }, devtool, @@ -156,7 +165,7 @@ export default { }, plugins, performance: { - maxEntrypointSize: 600_000, + maxEntrypointSize: 800_000, maxAssetSize: 600_000, }, }; diff --git a/package.json b/package.json index 164ec939..f680e519 100644 --- a/package.json +++ b/package.json @@ -162,6 +162,8 @@ "@cloudcmd/stub": "^5.0.0", "@iocmd/wait": "^2.1.0", "@putout/eslint-flat": "^4.0.0", + "@rspack/cli": "^2.1.4", + "@rspack/core": "^2.1.4", "@supertape/loader-css": "^1.0.0", "@types/node-fetch": "^2.6.11", "auto-globals": "^4.0.0", @@ -171,7 +173,6 @@ "clean-css-loader": "^4.2.1", "codegen.macro": "^4.0.0", "css-loader": "^7.1.2", - "css-minimizer-webpack-plugin": "^8.0.0", "css-modules-require-hook": "^4.2.3", "cssnano-preset-default": "^8.0.1", "domtokenlist-shim": "^1.2.0", @@ -190,7 +191,6 @@ "load.js": "^3.0.0", "madrun": "^13.0.0", "memfs": "^4.2.0", - "mini-css-extract-plugin": "^2.9.2", "minor": "^1.2.2", "morgan": "^1.6.1", "multi-rename": "^3.0.0", @@ -215,10 +215,7 @@ "unionfs": "^4.0.0", "url-loader": "^4.0.0", "util": "^0.12.5", - "webpack": "^5.99.9", - "webpack-cli": "^7.0.2", - "webpack-merge": "^6.0.1", - "webpackbar": "^7.0.0" + "webpack-merge": "^6.0.1" }, "imports": { "#css/": "./css/", diff --git a/rspack.config.js b/rspack.config.js new file mode 100644 index 00000000..5dcb033d --- /dev/null +++ b/rspack.config.js @@ -0,0 +1,10 @@ +import {merge} from 'webpack-merge'; +import * as htmlConfig from './.rspack/html.js'; +import cssConfig from './.rspack/css.js'; +import jsConfig from './.rspack/js.js'; + +export default merge([ + jsConfig, + htmlConfig, + cssConfig, +]); From cd4a2cec7675d1b3cacd3b276b23211bd8e5ea8e Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 16 Jul 2026 09:28:42 +0000 Subject: [PATCH 37/42] =?UTF-8?q?chore:=20cloudcmd:=20actions:=20lint=20?= =?UTF-8?q?=E2=98=98=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .rspack/js.js | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/.rspack/js.js b/.rspack/js.js index fae11fc8..251d85f6 100644 --- a/.rspack/js.js +++ b/.rspack/js.js @@ -31,28 +31,25 @@ const noParse = (a) => a.endsWith('.spec.js'); // Rspack's native SWC transform, so client/sw/sw.js (the only file that // uses it) keeps going through babel-loader. Everything else uses // Rspack's built-in SWC loader, which is the main source of the speedup. -const rules = [ - { - test: /sw\/sw\.js$/, - exclude: /node_modules/, - loader: 'babel-loader', - }, - { - test: /\.[mc]?js$/, - exclude: [/node_modules/, /sw\/sw\.js$/], - loader: 'builtin:swc-loader', - options: { - jsc: { - parser: { - syntax: 'ecmascript', - }, - }, - env: { - targets: 'defaults', +const rules = [{ + test: /sw\/sw\.js$/, + exclude: /node_modules/, + loader: 'babel-loader', +}, { + test: /\.[mc]?js$/, + exclude: [/node_modules/, /sw\/sw\.js$/], + loader: 'builtin:swc-loader', + options: { + jsc: { + parser: { + syntax: 'ecmascript', }, }, + env: { + targets: 'defaults', + }, }, -]; +}]; const plugins = [ new NormalModuleReplacementPlugin(/^node:/, (resource) => { From b7bb363aa877e24412ad83908f65ae461bb30722 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 16 Jul 2026 12:23:46 +0000 Subject: [PATCH 38/42] test: cloudfunc: coverage --- common/cloudfunc.spec.js | 94 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/common/cloudfunc.spec.js b/common/cloudfunc.spec.js index dca24b5a..f4cabead 100644 --- a/common/cloudfunc.spec.js +++ b/common/cloudfunc.spec.js @@ -2,12 +2,17 @@ import {readFileSync} from 'node:fs'; import test from 'supertape'; import {montag} from 'montag'; import * as cheerio from 'cheerio'; +import {tryCatch} from 'try-catch'; import { _getSize, getPathLink, buildFromJSON, _getDataName, dateFormatter, + formatMsg, + getTitle, + getDotDot, + getHeaderField, } from '#common/cloudfunc'; const templatePath = new URL('../tmpl/fs', import.meta.url).pathname; @@ -221,6 +226,95 @@ test('cloudfunc: _getDataName', (t) => { t.end(); }); +test('cloudfunc: formatMsg: name', (t) => { + const result = formatMsg('hello', 'world'); + const expected = 'hello: ok("world")'; + + t.equal(result, expected, 'should format message with name'); + t.end(); +}); + +test('cloudfunc: formatMsg: no name', (t) => { + const result = formatMsg('hello'); + const expected = 'hello: ok'; + + t.equal(result, expected, 'should format message without name'); + t.end(); +}); + +test('cloudfunc: getTitle: no options', (t) => { + const result = getTitle(); + + t.ok(result, 'should return a title string even without options'); + t.end(); +}); + +test('cloudfunc: getTitle: with name', (t) => { + const result = getTitle({name: 'MyName'}); + + t.ok(result.includes('MyName'), 'should return title with name'); + t.end(); +}); + +test('cloudfunc: getHeaderField: sort not name', (t) => { + const result = getHeaderField('size', 'asc', 'name'); + + t.equal(result, 'name', 'should return plain name when sort does not match'); + t.end(); +}); + +test('cloudfunc: getHeaderField: sort name asc', (t) => { + const result = getHeaderField('name', 'asc', 'name'); + + t.equal(result, 'name', 'should return plain name when name asc'); + t.end(); +}); + +test('cloudfunc: getDotDot: root', (t) => { + const result = getDotDot('/'); + + t.equal(result, '/', 'should return / for root path'); + t.end(); +}); + +test('cloudfunc: getPathLink: /a/b/c', (t) => { + const {pathLink} = template; + const result = getPathLink('/a/b/c/', '', pathLink); + + t.ok(result, 'should build path link for 3-segment path'); + t.end(); +}); + +test('cloudfunc: getPathLink: no url', (t) => { + const [error] = tryCatch(getPathLink); + + t.equal(error.message, 'url could not be empty!', 'should throw when url is empty'); + t.end(); +}); + +test('cloudfunc: getPathLink: no template', (t) => { + const [error] = tryCatch(getPathLink, '/'); + + t.equal(error.message, 'template could not be empty!', 'should throw when template is empty'); + t.end(); +}); + +test('cloudfunc: getHeaderField: sort name desc', (t) => { + const result = getHeaderField('name', 'desc', 'name'); + const expected = 'name↓'; + + t.equal(result, expected, 'should return name with down arrow'); + t.end(); +}); + +test('cloudfunc: getDotDot: normal path', (t) => { + const result = getDotDot('/hello/world/'); + const expected = '/hello'; + + t.equal(result, expected, 'should return parent directory'); + t.end(); +}); + test('cloudfunc: buildFromJSON: formatDate', (t) => { const data = { path: '/media/', From fec5538f0158345ceeee097a1469ad7abb964cbc Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 16 Jul 2026 12:26:33 +0000 Subject: [PATCH 39/42] test: common: util: coverage --- common/util.spec.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/common/util.spec.js b/common/util.spec.js index f239bd67..5a19a8be 100644 --- a/common/util.spec.js +++ b/common/util.spec.js @@ -5,6 +5,8 @@ import { getRegExp, escapeRegExp, getExt, + time, + timeEnd, } from '#common/util'; test('getExt: no extension', (t) => { @@ -120,3 +122,17 @@ test('util: escapeRegExp', (t) => { t.equal(result, expected); t.end(); }); + +test('util: time', (t) => { + const [error] = tryCatch(time, 'test'); + + t.notOk(error, 'should not throw'); + t.end(); +}); + +test('util: timeEnd', (t) => { + const [error] = tryCatch(timeEnd, 'test'); + + t.notOk(error, 'should not throw'); + t.end(); +}); From e4ff4a78cc0c4dd64e8b6670b82a6389501bee32 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 16 Jul 2026 12:27:35 +0000 Subject: [PATCH 40/42] =?UTF-8?q?chore:=20cloudcmd:=20actions:=20lint=20?= =?UTF-8?q?=E2=98=98=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/cloudfunc.spec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/cloudfunc.spec.js b/common/cloudfunc.spec.js index f4cabead..b679feeb 100644 --- a/common/cloudfunc.spec.js +++ b/common/cloudfunc.spec.js @@ -250,9 +250,11 @@ test('cloudfunc: getTitle: no options', (t) => { }); test('cloudfunc: getTitle: with name', (t) => { - const result = getTitle({name: 'MyName'}); + const result = getTitle({ + name: 'MyName', + }); - t.ok(result.includes('MyName'), 'should return title with name'); + t.match(result, 'MyName', 'should return title with name'); t.end(); }); From 943bdecd3e486f6ef1d518273b046fa8ffc3bd21 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 16 Jul 2026 15:29:08 +0000 Subject: [PATCH 41/42] test: coverage --- client/dom/dom-tree.spec.js | 103 +++++++++++++++++++++++++- client/modules/config/input.spec.js | 107 +++++++++++++++++++++++++++- client/sw/register.spec.js | 2 +- server/distribute/log.spec.js | 31 ++++++++ server/env.spec.js | 14 ++++ server/themes.spec.js | 19 +++++ 6 files changed, 271 insertions(+), 5 deletions(-) diff --git a/client/dom/dom-tree.spec.js b/client/dom/dom-tree.spec.js index 13f56f6d..64a28b29 100644 --- a/client/dom/dom-tree.spec.js +++ b/client/dom/dom-tree.spec.js @@ -1,7 +1,16 @@ -import test from 'supertape'; +import {test, stub} from 'supertape'; import {create} from 'auto-globals'; import {tryCatch} from 'try-catch'; -import {isContainClass} from './dom-tree.js'; +import { + isContainClass, + getByTag, + getById, + getByClass, + getByDataName, + getByClassAll, + hide, + show, +} from './dom-tree.js'; test('dom: isContainClass: no element', (t) => { const [e] = tryCatch(isContainClass); @@ -38,3 +47,93 @@ test('dom: isContainClass: contains: array', (t) => { t.calledWith(contains, [className], 'should call contains'); t.end(); }); + +test('dom: getByTag', (t) => { + const getElementsByTagName = stub(); + const element = {getElementsByTagName}; + + getByTag('div', element); + + t.calledWith(getElementsByTagName, ['div'], 'should call getElementsByTagName'); + t.end(); +}); + +test('dom: getById', (t) => { + const querySelector = stub(); + const element = {querySelector}; + + getById('my-id', element); + + t.calledWith(querySelector, ['#my-id'], 'should call querySelector with id selector'); + t.end(); +}); + +test('dom: getByClassAll', (t) => { + const getElementsByClassName = stub(); + const element = {getElementsByClassName}; + + getByClassAll('my-class', element); + + t.calledWith(getElementsByClassName, ['my-class'], 'should call getElementsByClassName'); + t.end(); +}); + +test('dom: getByClass: calls getByClassAll', (t) => { + const element = { + getElementsByClassName: stub().returns(['first']), + }; + + const result = getByClass('my-class', element); + + t.equal(result, 'first', 'should return first element from class list'); + t.end(); +}); + +test('dom: getByDataName', (t) => { + const querySelector = stub(); + const element = {querySelector}; + + getByDataName('hello', element); + + t.calledWith(querySelector, ['[data-name="hello"]'], 'should call querySelector with data-name selector'); + t.end(); +}); + +test('dom: hide', (t) => { + const add = stub(); + const element = { + classList: {add}, + }; + + hide(element); + + t.calledWith(add, ['hidden'], 'should add hidden class'); + t.end(); +}); + +test('dom: show', (t) => { + const remove = stub(); + const element = { + classList: {remove}, + }; + + show(element); + + t.calledWith(remove, ['hidden'], 'should remove hidden class'); + t.end(); +}); + +test('dom: getByClassAll: without element uses document', (t) => { + const getElementsByClassName = stub(); + const prevDocument = globalThis.document; + + globalThis.document = {getElementsByClassName}; + + getByClassAll('my-class'); + + globalThis.document = prevDocument; + + t.calledWith(getElementsByClassName, ['my-class'], 'should fallback to document when no element'); + t.end(); +}); + diff --git a/client/modules/config/input.spec.js b/client/modules/config/input.spec.js index 537c1632..a2ddcda2 100644 --- a/client/modules/config/input.spec.js +++ b/client/modules/config/input.spec.js @@ -1,5 +1,5 @@ -import {test} from 'supertape'; -import {convert} from './input.js'; +import {test, stub} from 'supertape'; +import {convert, getName, getValue, setValue} from './input.js'; test('cloudcmd: client: config: input: convert', (t) => { const result = convert({ @@ -13,3 +13,106 @@ test('cloudcmd: client: config: input: convert', (t) => { t.deepEqual(result, expected); t.end(); }); + +test('cloudcmd: client: config: input: convert: bool', (t) => { + const result = convert({ + auth: true, + }); + + const expected = { + auth: ' checked', + }; + + t.deepEqual(result, expected); + t.end(); +}); + +test('cloudcmd: client: config: input: convert: bool false', (t) => { + const result = convert({ + auth: false, + }); + + const expected = { + auth: '', + }; + + t.deepEqual(result, expected); + t.end(); +}); + +test('cloudcmd: client: config: input: getName', (t) => { + const getAttribute = stub().returns('js-hello'); + const element = {getAttribute}; + + const result = getName(element); + + t.equal(result, 'hello', 'should strip js- prefix'); + t.end(); +}); + +test('cloudcmd: client: config: input: getValue: checkbox', (t) => { + const querySelector = stub().returns({ + type: 'checkbox', + checked: true, + }); + const element = {querySelector}; + + const result = getValue('auth', element); + + t.ok(result, 'should return checked value'); + t.end(); +}); + +test('cloudcmd: client: config: input: getValue: number', (t) => { + const querySelector = stub().returns({ + type: 'number', + value: '42', + }); + const element = {querySelector}; + + const result = getValue('port', element); + + t.equal(result, 42, 'should return number'); + t.end(); +}); + +test('cloudcmd: client: config: input: getValue: default', (t) => { + const querySelector = stub().returns({ + type: 'text', + value: 'hello', + }); + const element = {querySelector}; + + const result = getValue('name', element); + + t.equal(result, 'hello', 'should return value as is'); + t.end(); +}); + +test('cloudcmd: client: config: input: setValue: checkbox', (t) => { + const el = { + type: 'checkbox', + checked: false, + }; + const querySelector = stub().returns(el); + const element = {querySelector}; + + setValue('auth', true, element); + + t.ok(el.checked, 'should set checked'); + t.end(); +}); + +test('cloudcmd: client: config: input: setValue: default', (t) => { + const el = { + type: 'text', + value: 'old', + }; + const querySelector = stub().returns(el); + const element = {querySelector}; + + setValue('name', 'new', element); + + t.equal(el.value, 'new', 'should set value'); + t.end(); +}); diff --git a/client/sw/register.spec.js b/client/sw/register.spec.js index 47420ba7..52699f0b 100644 --- a/client/sw/register.spec.js +++ b/client/sw/register.spec.js @@ -65,7 +65,7 @@ test('sw: register: registerSW: http', async (t, {location, navigator}) => { test('sw: register: registerSW: https self-signed', async (t, {location, navigator}) => { Object.assign(location, { - protocol: 'https', + protocol: 'https:', hostname: 'self-signed.badssl.com', }); diff --git a/server/distribute/log.spec.js b/server/distribute/log.spec.js index be50dcc3..a3b03f68 100644 --- a/server/distribute/log.spec.js +++ b/server/distribute/log.spec.js @@ -32,3 +32,34 @@ test('distribute: log: config', (t) => { }, { checkAssertionsCount: false, }); + + +test('distribute: log: stringToRGB', (t) => { + const result = log.stringToRGB('abc'); + + t.deepEqual(result, [97, 3, 294], 'should return [charCode, length, crc]'); + t.end(); +}); + +test('distribute: log: makeColor', (t) => { + const result = log.makeColor('hello'); + + t.ok(result.includes('hello'), 'should return colored string containing the input'); + t.end(); +}); + + +test('distribute: log: getDescription', (t) => { + const message = 'some error'; + const result = log.getDescription({ + message, + }); + + t.equal(result, message, 'should return message from error object'); + t.end(); +}); + +test('distribute: log: connectedStr', (t) => { + t.ok(log.connectedStr, 'should have connectedStr'); + t.end(); +}); diff --git a/server/env.spec.js b/server/env.spec.js index d8065b2c..760e7739 100644 --- a/server/env.spec.js +++ b/server/env.spec.js @@ -54,3 +54,17 @@ test('cloudcmd: server: env: bool: number: 0', (t) => { t.notOk(result); t.end(); }); + +test('cloudcmd: server: env: bool: zero uppercase', (t) => { + const {CLOUDCMD_TERMINAL} = process.env; + + process.env.CLOUDCMD_TERMINAL = '0'; + + const result = env.bool('terminal'); + + process.env.CLOUDCMD_TERMINAL = CLOUDCMD_TERMINAL; + + t.notOk(result); + t.end(); +}); + diff --git a/server/themes.spec.js b/server/themes.spec.js index 1fc95ec8..8d761a3c 100644 --- a/server/themes.spec.js +++ b/server/themes.spec.js @@ -30,3 +30,22 @@ test('themes: no args', (t) => { t.match(themes.light, css); t.end(); }); + +test('themes: production', (t) => { + const themes = getThemes({ + isDev: false, + }); + + t.ok(themes.dark, 'should have dark theme'); + t.end(); +}); + +test('themes: production: light', (t) => { + const themes = getThemes({ + isDev: false, + }); + + t.ok(themes.light, 'should have light theme'); + t.end(); +}); + From 7f28803a82e12e9721c3046dc9d71b4d825f8dd4 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 16 Jul 2026 15:31:22 +0000 Subject: [PATCH 42/42] =?UTF-8?q?chore:=20cloudcmd:=20actions:=20lint=20?= =?UTF-8?q?=E2=98=98=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/dom/dom-tree.spec.js | 29 ++++++++++++++++------- client/modules/config/input.spec.js | 36 +++++++++++++++++++++++------ server/distribute/log.spec.js | 4 +--- server/env.spec.js | 1 - server/themes.spec.js | 1 - 5 files changed, 51 insertions(+), 20 deletions(-) diff --git a/client/dom/dom-tree.spec.js b/client/dom/dom-tree.spec.js index 64a28b29..53b376c7 100644 --- a/client/dom/dom-tree.spec.js +++ b/client/dom/dom-tree.spec.js @@ -50,7 +50,9 @@ test('dom: isContainClass: contains: array', (t) => { test('dom: getByTag', (t) => { const getElementsByTagName = stub(); - const element = {getElementsByTagName}; + const element = { + getElementsByTagName, + }; getByTag('div', element); @@ -60,7 +62,9 @@ test('dom: getByTag', (t) => { test('dom: getById', (t) => { const querySelector = stub(); - const element = {querySelector}; + const element = { + querySelector, + }; getById('my-id', element); @@ -70,7 +74,9 @@ test('dom: getById', (t) => { test('dom: getByClassAll', (t) => { const getElementsByClassName = stub(); - const element = {getElementsByClassName}; + const element = { + getElementsByClassName, + }; getByClassAll('my-class', element); @@ -91,7 +97,9 @@ test('dom: getByClass: calls getByClassAll', (t) => { test('dom: getByDataName', (t) => { const querySelector = stub(); - const element = {querySelector}; + const element = { + querySelector, + }; getByDataName('hello', element); @@ -102,7 +110,9 @@ test('dom: getByDataName', (t) => { test('dom: hide', (t) => { const add = stub(); const element = { - classList: {add}, + classList: { + add, + }, }; hide(element); @@ -114,7 +124,9 @@ test('dom: hide', (t) => { test('dom: show', (t) => { const remove = stub(); const element = { - classList: {remove}, + classList: { + remove, + }, }; show(element); @@ -127,7 +139,9 @@ test('dom: getByClassAll: without element uses document', (t) => { const getElementsByClassName = stub(); const prevDocument = globalThis.document; - globalThis.document = {getElementsByClassName}; + globalThis.document = { + getElementsByClassName, + }; getByClassAll('my-class'); @@ -136,4 +150,3 @@ test('dom: getByClassAll: without element uses document', (t) => { t.calledWith(getElementsByClassName, ['my-class'], 'should fallback to document when no element'); t.end(); }); - diff --git a/client/modules/config/input.spec.js b/client/modules/config/input.spec.js index a2ddcda2..9d0866cd 100644 --- a/client/modules/config/input.spec.js +++ b/client/modules/config/input.spec.js @@ -1,5 +1,10 @@ import {test, stub} from 'supertape'; -import {convert, getName, getValue, setValue} from './input.js'; +import { + convert, + getName, + getValue, + setValue, +} from './input.js'; test('cloudcmd: client: config: input: convert', (t) => { const result = convert({ @@ -42,7 +47,9 @@ test('cloudcmd: client: config: input: convert: bool false', (t) => { test('cloudcmd: client: config: input: getName', (t) => { const getAttribute = stub().returns('js-hello'); - const element = {getAttribute}; + const element = { + getAttribute, + }; const result = getName(element); @@ -55,7 +62,10 @@ test('cloudcmd: client: config: input: getValue: checkbox', (t) => { type: 'checkbox', checked: true, }); - const element = {querySelector}; + + const element = { + querySelector, + }; const result = getValue('auth', element); @@ -68,7 +78,10 @@ test('cloudcmd: client: config: input: getValue: number', (t) => { type: 'number', value: '42', }); - const element = {querySelector}; + + const element = { + querySelector, + }; const result = getValue('port', element); @@ -81,7 +94,10 @@ test('cloudcmd: client: config: input: getValue: default', (t) => { type: 'text', value: 'hello', }); - const element = {querySelector}; + + const element = { + querySelector, + }; const result = getValue('name', element); @@ -94,8 +110,11 @@ test('cloudcmd: client: config: input: setValue: checkbox', (t) => { type: 'checkbox', checked: false, }; + const querySelector = stub().returns(el); - const element = {querySelector}; + const element = { + querySelector, + }; setValue('auth', true, element); @@ -108,8 +127,11 @@ test('cloudcmd: client: config: input: setValue: default', (t) => { type: 'text', value: 'old', }; + const querySelector = stub().returns(el); - const element = {querySelector}; + const element = { + querySelector, + }; setValue('name', 'new', element); diff --git a/server/distribute/log.spec.js b/server/distribute/log.spec.js index a3b03f68..1c6d1aa3 100644 --- a/server/distribute/log.spec.js +++ b/server/distribute/log.spec.js @@ -33,7 +33,6 @@ test('distribute: log: config', (t) => { checkAssertionsCount: false, }); - test('distribute: log: stringToRGB', (t) => { const result = log.stringToRGB('abc'); @@ -44,11 +43,10 @@ test('distribute: log: stringToRGB', (t) => { test('distribute: log: makeColor', (t) => { const result = log.makeColor('hello'); - t.ok(result.includes('hello'), 'should return colored string containing the input'); + t.match(result, 'hello', 'should return colored string containing the input'); t.end(); }); - test('distribute: log: getDescription', (t) => { const message = 'some error'; const result = log.getDescription({ diff --git a/server/env.spec.js b/server/env.spec.js index 760e7739..5a8765f4 100644 --- a/server/env.spec.js +++ b/server/env.spec.js @@ -67,4 +67,3 @@ test('cloudcmd: server: env: bool: zero uppercase', (t) => { t.notOk(result); t.end(); }); - diff --git a/server/themes.spec.js b/server/themes.spec.js index 8d761a3c..709896e8 100644 --- a/server/themes.spec.js +++ b/server/themes.spec.js @@ -48,4 +48,3 @@ test('themes: production: light', (t) => { t.ok(themes.light, 'should have light theme'); t.end(); }); -