diff --git a/.github/workflows/docker-io.yml b/.github/workflows/docker-io.yml
index 8cbeb042..beff4589 100644
--- a/.github/workflows/docker-io.yml
+++ b/.github/workflows/docker-io.yml
@@ -1,6 +1,4 @@
name: Docker IO
-permissions:
- contents: write
on:
- push
jobs:
@@ -51,7 +49,7 @@ jobs:
with:
context: .
file: docker/Dockerfile.io
- platforms: linux/amd64,linux/arm64
+ platforms: linux/amd64
push: true
tags: |
coderaiser/cloudcmd:io
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 5c81d368..1a85b29e 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -1,6 +1,4 @@
name: Docker CI
-permissions:
- contents: write
on:
push:
tags:
diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index 429e5568..b9ed9cb5 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -1,8 +1,6 @@
name: Node CI
on:
- push
-permissions:
- contents: write
jobs:
build:
runs-on: ubuntu-latest
@@ -24,17 +22,24 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- name: Install Redrun
- run: bun i redrun madrun -g --no-save
+ run: bun i redrun -g --no-save
- name: Install
run: bun i --no-save
- - name: Init Madrun
- run: madrun --init
- name: Lint
run: redrun fix:lint
- - name: Typos
- uses: coderaiser/typos.ai@v1.1.8
+ - uses: actions/cache@v5
with:
- key: ${{ secrets.TYPOS_AI_KEY }}
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ - name: Typos Install
+ run: cargo install typos-cli || echo 'already installed'
+ - name: Typos
+ run: typos --write-changes
- name: Commit fixes
uses: EndBug/add-and-commit@v10
continue-on-error: true
diff --git a/.madrun.js b/.madrun.js
index efa8ce98..9bd30e3d 100644
--- a/.madrun.js
+++ b/.madrun.js
@@ -21,15 +21,14 @@ export default {
'start:dev',
]),
'lint:all': () => run('lint:progress'),
- 'lint': () => 'redlint fix; putout .',
+ 'lint': () => 'redlint fix; putout . --rulesdir rules',
'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'),
'lint:fresh': () => run('lint', '--fresh'),
- 'fix:lint': async () => `putout --fix . && redlint fix`,
+ 'fix:lint': async () => `putout --rulesdir rules --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'`,
+ 'test': () => [testEnv, `tape 'test/**/*.js' '{bin,client,static,common,server}/**/*.spec.js' -f fail`],
'test:client': () => `tape 'test/client/**/*.js'`,
'test:server': () => `tape 'test/**/*.js' 'server/**/*.spec.js' 'common/**/*.spec.js'`,
'wisdom': async () => await run(['lint:all', 'build', 'test'], null, {
@@ -39,7 +38,7 @@ export default {
'coverage': async () => [testEnv, `c8 ${await cutEnv('test')}`],
'coverage:report': () => 'c8 report --reporter=lcov',
'report': () => 'c8 report --reporter=lcov',
- '6to5': () => [buildEnv, 'rspack build --config rspack.config.js'],
+ '6to5': () => [buildEnv, 'webpack --progress'],
'6to5:client': () => run('6to5', '--mode production'),
'6to5:client:dev': async () => await run('6to5', '--mode development', {
NODE_ENV: 'development',
diff --git a/.npmignore b/.npmignore
index b83b5923..04da926f 100644
--- a/.npmignore
+++ b/.npmignore
@@ -20,7 +20,7 @@ img/logo/cloudcmd-hq.png
webpack.config.js
docker
-test*
+test
fixture
fixture-*
coverage
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 00000000..1efe0ac6
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+v20.15.1
diff --git a/.rspack/css.js b/.webpack/css.js
similarity index 53%
rename from .rspack/css.js
rename to .webpack/css.js
index 10d28250..311290cc 100644
--- a/.rspack/css.js
+++ b/.webpack/css.js
@@ -1,18 +1,19 @@
import {env} from 'node:process';
-import {rspack} from '@rspack/core';
+import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
+import MiniCssExtractPlugin from 'mini-css-extract-plugin';
-const {CssExtractRspackPlugin} = rspack;
const isDev = env.NODE_ENV === 'development';
+const clean = (a) => a.filter(Boolean);
-const plugins = [
- new CssExtractRspackPlugin({
+const plugins = clean([
+ new MiniCssExtractPlugin({
filename: '[name].css',
}),
-];
+]);
const rules = [{
test: /\.css$/i,
- use: [CssExtractRspackPlugin.loader, {
+ use: [MiniCssExtractPlugin.loader, {
loader: 'css-loader',
options: {
url: true,
@@ -32,7 +33,13 @@ export default {
optimization: {
minimize: !isDev,
minimizer: [
- new rspack.LightningCssMinimizerRspackPlugin(),
+ new CssMinimizerPlugin({
+ minimizerOptions: {
+ preset: ['default', {
+ svgo: false,
+ }],
+ },
+ }),
],
},
};
diff --git a/.rspack/html.js b/.webpack/html.js
similarity index 100%
rename from .rspack/html.js
rename to .webpack/html.js
diff --git a/.rspack/js.js b/.webpack/js.js
similarity index 81%
rename from .rspack/js.js
rename to .webpack/js.js
index 251d85f6..fdc88556 100644
--- a/.rspack/js.js
+++ b/.webpack/js.js
@@ -1,15 +1,12 @@
import {resolve, sep} from 'node:path';
-import {fileURLToPath} from 'node:url';
import {env} from 'node:process';
-import {rspack} from '@rspack/core';
-
-const resolveModule = (a) => fileURLToPath(import.meta.resolve(a));
+import webpack from 'webpack';
+import WebpackBar from 'webpackbar';
const {
EnvironmentPlugin,
NormalModuleReplacementPlugin,
- ProvidePlugin,
-} = rspack;
+} = webpack;
const modules = './modules';
const dirModules = './client/modules';
@@ -25,31 +22,27 @@ const dist = resolve(rootDir, 'dist');
const distDev = resolve(rootDir, 'dist-dev');
const devtool = isDev ? 'eval' : 'source-map';
-const noParse = (a) => a.endsWith('.spec.js');
+const notEmpty = (a) => a;
+const clean = (array) => array.filter(notEmpty);
-// 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',
-}, {
- test: /\.[mc]?js$/,
- exclude: [/node_modules/, /sw\/sw\.js$/],
- loader: 'builtin:swc-loader',
- options: {
- jsc: {
- parser: {
- syntax: 'ecmascript',
- },
- },
- env: {
- targets: 'defaults',
- },
+const noParse = (a) => a.endsWith('.spec.js');
+const options = {
+ babelrc: true,
+};
+
+const rules = clean([
+ !isDev && {
+ test: /\.[mc]?js$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader',
},
-}];
+ isDev && {
+ test: /\.[mc]?js$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader',
+ options,
+ },
+]);
const plugins = [
new NormalModuleReplacementPlugin(/^node:/, (resource) => {
@@ -59,7 +52,8 @@ const plugins = [
new EnvironmentPlugin({
NODE_ENV,
}),
- new ProvidePlugin({
+ new WebpackBar(),
+ new webpack.ProvidePlugin({
process: 'process/browser',
}),
];
@@ -105,9 +99,9 @@ export default {
'node:path': 'path',
},
fallback: {
- path: resolveModule('path-browserify'),
- process: resolveModule('process/browser'),
- util: resolveModule('util'),
+ path: import.meta.resolve('path-browserify'),
+ process: import.meta.resolve('process/browser'),
+ util: import.meta.resolve('util'),
},
},
devtool,
@@ -162,7 +156,7 @@ export default {
},
plugins,
performance: {
- maxEntrypointSize: 800_000,
+ maxEntrypointSize: 600_000,
maxAssetSize: 600_000,
},
};
diff --git a/ChangeLog b/ChangeLog
index c5e0fbca..1132ea96 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,30 +1,3 @@
-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:
-- 4ee38db6 client: view: flac: add support
-
-2026.05.26, v19.18.1
-
-feature:
-- 40ecef5e cloudcmd: ratelimit: X-Forwarded-For (#437)
-
-2026.05.26, v19.18.0
-
-fix:
-- 161bede8 client: menu: @putout/bundle@5.5.
-- 447d990f cloudcmd: server: rest: sendError
-
-feature:
-- 5d9628ce cloudcmd: rate limit: add support (#437)
-- 2be395e6 cloudcmd: get rid of manifest: 401 basic auth
-
2026.05.17, v19.17.0
fix:
diff --git a/HELP.md b/HELP.md
index 2967f0d0..783eb088 100644
--- a/HELP.md
+++ b/HELP.md
@@ -1,6 +1,6 @@
-# Cloud Commander v19.19.1
+# Cloud Commander v19.17.0
-### [Main][MainURL] [Blog][BlogURL] [Demo][DemoURL] [Deploy](#deploy)
+### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL]
[MainURL]: https://cloudcmd.io "Main"
[BlogURL]: https://blog.cloudcmd.io "Blog"
@@ -14,9 +14,6 @@
[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.

@@ -190,12 +187,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
@@ -933,9 +930,9 @@ ln -s ./sites-available/io.cloudcmd.io ./sites-enabled
## Deploy
-`Cloud Commander` can be easily deployed to [InstaPods][DeployInstaPodsURL]. After deploy you receive email with credentials.
+`Cloud Commander` can be easily deployed to [Heroku](https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd "Deploy to Heroku").
-[](https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd)
+[](https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd)
## Docker
@@ -1122,10 +1119,6 @@ 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)**
- *2026.05.17*, **[v19.17.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.17.0)**
- *2026.05.03*, **[v19.16.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.16.0)**
- *2026.04.28*, **[v19.15.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.15.0)**
diff --git a/README.md b/README.md
index dfe8d8c6..806e734d 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Cloud Commander v19.19.1 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deploy on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL]
+# Cloud Commander v19.17.0 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL]
### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL]
@@ -15,8 +15,6 @@
[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.
diff --git a/bin/cloudcmd.js b/bin/cloudcmd.js
index 6ca6716c..bdf88379 100755
--- a/bin/cloudcmd.js
+++ b/bin/cloudcmd.js
@@ -115,7 +115,7 @@ const yargsOptions = {
'columns': env.parse('columns') || config('columns') || '',
'theme': env.parse('theme') || config('theme') || '',
'vim': choose(env.bool('vim'), config('vim')),
- 'log': choose(env.bool('log'), config('log')),
+ 'log': config('log'),
'import-url': env.parse('import_url') || config('importUrl'),
'import-listen': choose(env.bool('import_listen'), config('importListen')),
@@ -264,8 +264,7 @@ function validateRoot(root, config) {
if (root === '/')
return;
- if (config('log'))
- console.log(`root: ${root}`);
+ console.log(`root: ${root}`);
}
async function getPassword(password) {
diff --git a/client/cloudcmd.js b/client/cloudcmd.js
index f2416fd4..688d97b7 100644
--- a/client/cloudcmd.js
+++ b/client/cloudcmd.js
@@ -36,7 +36,7 @@ async function init(config) {
if (globalThis.CloudCmd.config('menu') === 'aleman')
setTimeout(() => {
import('https://esm.sh/@putout/processor-html');
- import('https://esm.sh/@putout/bundle@5.5');
+ import('https://esm.sh/@putout/bundle');
}, 100);
}
diff --git a/client/dom/current-file.spec.js b/client/dom/current-file.spec.js
index 85b18a29..6bf0509b 100644
--- a/client/dom/current-file.spec.js
+++ b/client/dom/current-file.spec.js
@@ -19,13 +19,14 @@ 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();
});
@@ -40,13 +41,14 @@ 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();
});
@@ -74,10 +76,11 @@ 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();
});
@@ -93,12 +96,14 @@ 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/dom-tree.spec.js b/client/dom/dom-tree.spec.js
index 53b376c7..13f56f6d 100644
--- a/client/dom/dom-tree.spec.js
+++ b/client/dom/dom-tree.spec.js
@@ -1,16 +1,7 @@
-import {test, stub} from 'supertape';
+import test from 'supertape';
import {create} from 'auto-globals';
import {tryCatch} from 'try-catch';
-import {
- isContainClass,
- getByTag,
- getById,
- getByClass,
- getByDataName,
- getByClassAll,
- hide,
- show,
-} from './dom-tree.js';
+import {isContainClass} from './dom-tree.js';
test('dom: isContainClass: no element', (t) => {
const [e] = tryCatch(isContainClass);
@@ -47,106 +38,3 @@ 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/dom/index.js b/client/dom/index.js
index a82b6f72..f6ce6bc7 100644
--- a/client/dom/index.js
+++ b/client/dom/index.js
@@ -442,7 +442,11 @@ export function getFilenames(files) {
if (name === '..')
allFiles.shift();
- return allFiles.map(DOM.getCurrentName);
+ const names = allFiles.map((current) => {
+ return DOM.getCurrentName(current);
+ });
+
+ return names;
}
/**
diff --git a/client/dom/load-remote.js b/client/dom/load-remote.js
index 5be8e327..43af0cf5 100644
--- a/client/dom/load-remote.js
+++ b/client/dom/load-remote.js
@@ -33,19 +33,13 @@ export const loadRemote = callbackify(async (name, options) => {
local = [module.local];
}
- const localURL = [];
+ const localURL = local.map((url) => prefix + url);
- for (const url of local) {
- localURL.push(prefix + url);
- }
-
- const remoteURL = [];
-
- for (const tmpl of remoteTmpls) {
- remoteURL.push(rendy(tmpl, {
+ const remoteURL = remoteTmpls.map((tmpl) => {
+ return rendy(tmpl, {
version,
- }));
- }
+ });
+ });
if (online) {
const [e] = await tryToCatch(load.parallel, remoteURL);
diff --git a/client/key/binder.spec.js b/client/key/binder.spec.js
deleted file mode 100644
index f9d54fdd..00000000
--- a/client/key/binder.spec.js
+++ /dev/null
@@ -1,28 +0,0 @@
-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/index.js b/client/key/index.js
index 283a7f11..6f55746c 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 && shift) {
+ if (ctrlMeta) {
CloudCmd.logOut();
event.preventDefault();
}
diff --git a/client/key/vim/find.spec.js b/client/key/vim/find.spec.js
index 191a3652..59c9a249 100644
--- a/client/key/vim/find.spec.js
+++ b/client/key/vim/find.spec.js
@@ -1,12 +1,6 @@
import test from 'supertape';
import {getDOM} from './globals.fixture.js';
-import {
- _next,
- _previous,
- find,
- findNext,
- findPrevious,
-} from './find.js';
+import {_next, _previous} from './find.js';
globalThis.DOM = getDOM();
@@ -17,39 +11,9 @@ 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/client/key/vim/index.spec.js b/client/key/vim/index.spec.js
index 231c6577..e1ca4eee 100644
--- a/client/key/vim/index.spec.js
+++ b/client/key/vim/index.spec.js
@@ -1,7 +1,6 @@
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();
@@ -676,81 +675,3 @@ 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/client/modules/config/input.spec.js b/client/modules/config/input.spec.js
index 9d0866cd..537c1632 100644
--- a/client/modules/config/input.spec.js
+++ b/client/modules/config/input.spec.js
@@ -1,10 +1,5 @@
-import {test, stub} from 'supertape';
-import {
- convert,
- getName,
- getValue,
- setValue,
-} from './input.js';
+import {test} from 'supertape';
+import {convert} from './input.js';
test('cloudcmd: client: config: input: convert', (t) => {
const result = convert({
@@ -18,123 +13,3 @@ 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/modules/menu/index.js b/client/modules/menu/index.js
index 7235591e..249e755c 100644
--- a/client/modules/menu/index.js
+++ b/client/modules/menu/index.js
@@ -1,3 +1,4 @@
+/* global CloudCmd, DOM */
import exec from 'execon';
import wrap from 'wraptile';
import createElement from '@cloudcmd/create-element';
@@ -5,7 +6,6 @@ import {getIdBySrc} from '#dom/load';
import * as RESTful from '#dom/rest';
import {FS} from '#common/cloudfunc';
-const {CloudCmd, DOM} = globalThis;
const {config, Key} = CloudCmd;
const {
diff --git a/client/modules/view/index.spec.js b/client/modules/view/index.spec.js
index abfbe3f4..8d184548 100644
--- a/client/modules/view/index.spec.js
+++ b/client/modules/view/index.spec.js
@@ -31,10 +31,8 @@ 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(result, expected);
+ t.equal(typeof config, 'object');
t.end();
});
diff --git a/client/modules/view/types.js b/client/modules/view/types.js
index 320a4bc8..2e001fe1 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|flac)$/i.test(name);
+export const isAudio = (name) => /\.(mp3|ogg|m4a)$/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 f0542c18..7ed159f4 100644
--- a/client/modules/view/types.spec.js
+++ b/client/modules/view/types.spec.js
@@ -8,13 +8,6 @@ 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');
diff --git a/client/sw/register.spec.js b/client/sw/register.spec.js
index 52699f0b..47420ba7 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/common/cloudfunc.spec.js b/common/cloudfunc.spec.js
index b679feeb..dca24b5a 100644
--- a/common/cloudfunc.spec.js
+++ b/common/cloudfunc.spec.js
@@ -2,17 +2,12 @@ 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;
@@ -226,97 +221,6 @@ 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.match(result, '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/',
diff --git a/common/util.spec.js b/common/util.spec.js
index 5a19a8be..6a32848c 100644
--- a/common/util.spec.js
+++ b/common/util.spec.js
@@ -5,8 +5,6 @@ import {
getRegExp,
escapeRegExp,
getExt,
- time,
- timeEnd,
} from '#common/util';
test('getExt: no extension', (t) => {
@@ -108,31 +106,11 @@ test('util: getRegExp: no', (t) => {
});
test('util: escapeRegExp: no str', (t) => {
- const result = escapeRegExp(1);
- const expected = 1;
-
- t.equal(result, expected);
+ t.equal(escapeRegExp(1), 1);
t.end();
});
test('util: escapeRegExp', (t) => {
- const result = escapeRegExp('#hello');
- const expected = '\\#hello';
-
- 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.equal(escapeRegExp('#hello'), '\\#hello');
t.end();
});
diff --git a/docker/Dockerfile.io b/docker/Dockerfile.io
index d5a24459..7b3a9304 100644
--- a/docker/Dockerfile.io
+++ b/docker/Dockerfile.io
@@ -11,20 +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 \
- NPM_CONFIG_PACKAGE_LOCK=false \
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 gh"
+ARG PALABRA_DEPS="nvm node rust go deno fasm nvchad rizin yara gdu f4"
ARG BUN_DEPS="palabra wisdom nupdate version-io redrun superc8 supertape madrun redlint putout renamify-cli runny redfork cline"
RUN apt-get update && \
@@ -32,7 +31,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 && \
diff --git a/html/index.html b/html/index.html
index d366ed1c..76043d9c 100644
--- a/html/index.html
+++ b/html/index.html
@@ -9,6 +9,7 @@
+
@@ -44,7 +45,7 @@