From 9c50fe53e797a6bcd48adbde51708c1db90dafe3 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Mon, 29 Jun 2026 17:48:04 +0300 Subject: [PATCH 01/21] 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 02/21] 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 03/21] 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 04/21] 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 05/21] 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 06/21] 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 07/21] 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 08/21] 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 09/21] 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 10/21] 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 11/21] 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 12/21] 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 13/21] 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 14/21] =?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 15/21] 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 16/21] =?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 17/21] 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 18/21] 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 19/21] =?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 20/21] 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 21/21] =?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(); }); -