Compare commits

...

342 commits

Author SHA1 Message Date
coderaiser
7f28803a82 chore: cloudcmd: actions: lint ☘️ 2026-07-16 15:31:22 +00:00
coderaiser
943bdecd3e test: coverage 2026-07-16 15:29:08 +00:00
coderaiser
e4ff4a78cc chore: cloudcmd: actions: lint ☘️ 2026-07-16 12:27:35 +00:00
coderaiser
fec5538f01 test: common: util: coverage 2026-07-16 12:26:33 +00:00
coderaiser
b7bb363aa8 test: cloudfunc: coverage 2026-07-16 12:23:46 +00:00
coderaiser
cd4a2cec76 chore: cloudcmd: actions: lint ☘️ 2026-07-16 09:28:42 +00:00
coderaiser
0638ca32cb feature: migrate from webpack to rspack 2026-07-16 12:27:20 +03:00
coderaiser
fe185cc93e chore: cloudcmd: actions: lint ☘️ 2026-07-15 20:27:25 +00:00
coderiaser
7edea50296 chore: lint 2026-07-15 23:26:28 +03:00
coderaiser
8258cdb651
chore: rm rulesdir 2026-07-15 21:18:06 +02:00
coderaiser
a9e29eee85 test: cloudcmd: coverage 2026-07-15 10:31:07 +00:00
coderaiser
a52a6aae62 test: coverage 2026-07-15 09:36:03 +00:00
coderaiser
92c79830f5 chore: madrun: test: rm test-e2e 2026-07-15 08:21:03 +00:00
coderiaser
8629f67972 feature: cloudcmd: docker: io: arm 2026-06-29 18:19:08 +03:00
coderiaser
c04d363c46 test: e2e: spawn 2026-06-29 18:16:34 +03:00
coderiaser
046a4ef22e chore: lint 2026-06-29 17:54:20 +03:00
coderiaser
579c9e41d7 feature: cloudcmd: serve-once v4.0.1 2026-06-29 17:48:06 +03:00
coderiaser
6014898610 feature: cloudcmd: @babel/preset-env v8.0.2 2026-06-29 17:48:06 +03:00
coderiaser
ad3bf95e5f feature: cloudcmd: @babel/plugin-transform-optional-chaining v8.0.1 2026-06-29 17:48:05 +03:00
coderiaser
4e22a4edde feature: cloudcmd: @babel/core v8.0.1 2026-06-29 17:48:04 +03:00
coderiaser
9c50fe53e7 feature: cloudcmd: @babel/code-frame v8.0.0 2026-06-29 17:48:04 +03:00
coderiaser
a2a2d51ec1 chore: cloudcmd: v19.19.1 2026-06-15 14:50:37 +03:00
coderiaser
c36c32cb08 chore: lint 2026-06-15 14:49:57 +03:00
coderiaser
78b206e06b feature: client: key: Ctrl + L -> Ctrl + Shift + L: Log out (#466) 2026-06-15 14:49:47 +03:00
coderiaser
ef83a61a0c chore: actions: typos 2026-06-04 17:55:58 +03:00
coderaiser
f5c0aa5c4c Update nodejs.yml 2026-06-04 17:55:44 +03:00
coderaiser
e59167b1fa chore: actions: permissions 2026-06-04 16:05:15 +03:00
coderiaser
ea584aceea chore: actions: typos.ai 2026-06-04 15:42:02 +03:00
coderiaser
4d7116b467 chore: docker: io: git 2026-06-03 23:47:50 +03:00
coderiaser
b149288545 feature: docker: io: NPM_CONFIG_PACKAGE_LOCK=false 2026-06-03 23:40:16 +03:00
coderiaser
9d06279ba8 featur: docker: io: add gh 2026-06-03 23:39:27 +03:00
coderiaser
21e1dcf0e5 chore: actions: typos 2026-06-03 18:46:19 +03:00
coderaiser
b3b0c1d159 chore: package: rm unused optimize-css-assets-webpack-plugin 2026-06-03 13:02:04 +00:00
coderaiser
76d7bf4d73
feature: docker: typos, shellcheck: add 2026-06-02 10:03:23 +02:00
coderaiser
f1d80282eb chore: cloudcmd: actions: lint ☘️ 2026-06-01 20:39:12 +00:00
coderiaser
220cc5557b chore: actions: API_KEY 2026-06-01 23:32:27 +03:00
coderiaser
d2176c93fb chore: actions: typo 2026-06-01 22:56:08 +03:00
coderaiser
c558e44a1c
docs: readme: InstaPods 2026-06-01 13:29:58 +03:00
coderaiser
45ec062bce
docs: readme: deploy on InstaPods 2026-06-01 13:24:07 +03:00
coderaiser
f8edddd442
chore: .nvmrc 2026-05-30 13:12:09 +02:00
coderiaser
fc3d5ac700 chore: cloudcmd: v19.19.0 2026-05-26 23:17:05 +03:00
coderaiser
4ee38db68c feature: client: view: flac: add support 2026-05-26 23:16:40 +03:00
coderiaser
1dfa7ca355 chore: cloudcmd: v19.18.1 2026-05-26 22:09:29 +03:00
coderiaser
40ecef5ec4 feature: cloudcmd: ratelimit: X-Forwarded-For (#437) 2026-05-26 22:08:16 +03:00
coderiaser
cad5e0f44e chore: lint 2026-05-26 21:45:25 +03:00
coderiaser
d139011cc3 chore: cloudcmd: v19.18.0 2026-05-26 00:21:45 +03:00
coderiaser
5d9628ce70 feature: cloudcmd: rate limit: add support (#437) 2026-05-26 00:21:24 +03:00
coderiaser
161bede8db fix: client: menu: @putout/bundle@5.5. 2026-05-24 22:24:54 +03:00
coderiaser
2be395e640 feature: cloudcmd: get rid of manifest: 401 basic auth 2026-05-24 21:40:26 +03:00
coderiaser
447d990f43 fix: cloudcmd: server: rest: sendError 2026-05-21 00:05:43 +03:00
coderaiser
4f4725ac3c chore: cloudcmd: actions: lint ☘️ 2026-05-17 11:25:50 +00:00
coderiaser
eff03299b6 chore: cloudcmd: v19.17.0 2026-05-17 14:24:10 +03:00
coderiaser
a6adf39db2 chore: lint 2026-05-17 14:24:10 +03:00
coderiaser
70ff6fb29f feature: cloudcmd: cssnano-preset-default v8.0.1 2026-05-17 14:24:10 +03:00
coderiaser
eb32fff688 feature: style: owner, mode: improve 2026-05-17 14:24:10 +03:00
coderiaser
6071b66118 fix: css: query: hide time on mobile 2026-05-17 14:24:10 +03:00
coderiaser
83c0b8d3f1 feature: cloudcmd: superc8 v12.6.0 2026-05-17 14:24:10 +03:00
coderiaser
79da2319f8 feature: docker: io: cline: add 2026-05-17 14:24:10 +03:00
coderiaser
68eacb916a feature: qword: add 2026-05-13 00:22:57 +03:00
coderiaser
ccf608a52e chore: lint 2026-05-10 22:51:18 +03:00
coderiaser
4acd294bf6 feature: docker: io: tmux 2026-05-10 22:44:33 +03:00
coderiaser
05c800431d feature: cloudcmd: @supertape/loader-css v1.0.0 2026-05-06 22:21:16 +03:00
coderiaser
6ff1db2710 chore: lint 2026-05-05 23:51:29 +03:00
coderiaser
800a6545a1 feature: cloudcmd: eslint-plugin-n v18.0.1 2026-05-05 23:51:23 +03:00
coderiaser
a985bb36ca feature: cloudcmd: supertape v13.0.0 2026-05-05 23:50:02 +03:00
coderiaser
e7c55e05b8 fix: docker: io: XDG_CONFIG_HOME 2026-05-03 23:57:16 +03:00
coderiaser
4315ec6179 feature: docker: io XDG_CONFIG_HOME: /etc -> /usr/local/etc 2026-05-03 16:08:15 +03:00
coderiaser
628d12fa2f chore: cloudcmd: v19.16.0 2026-05-03 16:02:16 +03:00
coderiaser
d82d0335b2 feature: client: vim: rr for rename file 2026-05-03 16:01:46 +03:00
coderiaser
acfa27cf74 feature: docker: io: nvchad: add 2026-05-03 00:42:54 +03:00
coderiaser
14f009f832 feature: docker: io: bash-completion: add 2026-05-03 00:16:16 +03:00
coderiaser
d1f6476604 chore: docker: io: no git 2026-05-01 21:01:24 +03:00
coderiaser
c6c6014691 feature: docker: io: f4 2026-05-01 20:21:49 +03:00
coderiaser
9a6b89356e feature: docker: io: GOPATH 2026-05-01 19:05:41 +03:00
coderiaser
882fe83623 chore: cloudcmd: v19.15.0 2026-04-29 12:36:10 +03:00
coderiaser
a9bc22ce29 feature: cloudcmd: operation: rm useless checks 2026-04-29 12:36:10 +03:00
coderiaser
36bacfe915 feature: cloudcmd: client: key: vim: cc, mm 2026-04-29 12:36:10 +03:00
coderiaser
bcbef1a3b0 chore: cloudcmd: v19.14.0 2026-04-29 12:36:10 +03:00
coderiaser
591da25cfe feature: cloudcmd: ponse v8.0.0 2026-04-29 12:36:10 +03:00
coderiaser
1e1b073c8c feature: docker: io: /usr/src/cloudcmd -> /usr/local/share/cloudcmd 2026-04-29 12:35:51 +03:00
coderiaser
ea7b828cbb feature: docker: io: ubuntu: resolute 2026-04-26 18:38:39 +03:00
coderiaser
cd6c11ace2 feature: docker: io: far2l 2026-04-26 18:29:45 +03:00
coderiaser
c4beeec63b feature: docker: io: gdu: add 2026-04-26 12:06:57 +03:00
coderiaser
87c13e7cce chore: cloudcmd: v19.13.1 2026-04-21 23:31:22 +03:00
coderiaser
d5cd11e812 feature: cloudcmd: montag v2.0.1 2026-04-21 23:30:41 +03:00
coderiaser
dc94d2db7a feature: docker: io: add latest git version 2026-04-21 22:47:17 +03:00
coderiaser
1637beee78 feature: docker: io: /usr/local/src -> /usr/local/share 2026-04-20 23:46:29 +03:00
coderaiser
0541b7e351 chore: cloudcmd: actions: lint ☘️ 2026-04-19 16:35:25 +00:00
coderiaser
b1bc4e731f feature: Docker: io: pv 2026-04-19 19:34:22 +03:00
coderiaser
7c0dca6038 feature: docker: io: git: master 2026-04-15 16:22:31 +03:00
coderiaser
24fd0a8aa7 chore: cloudcmd: v19.13.0 2026-04-15 02:39:12 +03:00
coderiaser
2b3dc1573d feature: cloudcmd: Ctrl + L: logout 2026-04-15 02:39:12 +03:00
coderiaser
48693d9e24 fix: docker: io: XDG_CACHE_HOME 2026-04-14 00:09:31 +03:00
coderiaser
977a8aaaf4 feature: docker: io: strace: add 2026-04-13 22:40:14 +03:00
coderiaser
15537ec518 chore: docker: io: remove haskell
This reverts commit b0687b54c7.
2026-04-13 22:36:54 +03:00
coderiaser
7d0098fd5c feature: docker: io: XDG_CACHE_HOME=/tmp/cache 2026-04-13 22:36:43 +03:00
coderiaser
da0c4c4704 chore: actions: docker: io: rm cache 2026-04-13 22:36:43 +03:00
coderiaser
b0687b54c7 chore: docker: io: get back haskell 2026-04-13 22:36:43 +03:00
coderiaser
a89e901b2b feature: actions: docker: io: add 2026-04-12 19:00:58 +03:00
coderiaser
ef06a0c310 chore: cloudcmd: v19.12.5 2026-04-12 18:37:50 +03:00
coderiaser
e7242900a4 chore: docker: io: get rid of haskell 2026-04-12 18:37:50 +03:00
coderiaser
a6b01dce49 chore: actions: cache 2026-04-12 18:31:55 +03:00
coderiaser
1cfc1a6f5d feature: docker: io: only amd64 2026-04-12 18:03:46 +03:00
coderiaser
9915757dce chore: cloudcmd: v19.12.4 2026-04-12 14:13:49 +03:00
coderiaser
673fba2e21 feature: docker: io: haskell 2026-04-12 14:13:49 +03:00
coderiaser
d37f8cd36e feature: docker: io: palabra: node 2026-04-12 13:56:16 +03:00
coderiaser
5e509a4d7b chore: cloudcmd: v19.12.3 2026-04-12 13:04:53 +03:00
coderiaser
cb6cabd4c9 feature: docker: io: PALABRA_DIR 2026-04-12 12:54:58 +03:00
coderiaser
f61cc46f41 chore: cloudcmd: v19.12.2 2026-04-11 22:29:12 +03:00
coderiaser
32a8f169d4 feature: docker: io: get rid of haskell: to slow install 2026-04-11 22:29:12 +03:00
coderiaser
aa74123228 feature: docker: io: rizin, yara 2026-04-11 16:47:53 +03:00
coderiaser
27f20d6af8 chore: cloudcmd: v19.12.1 2026-04-11 15:54:19 +03:00
coderiaser
4695480ead feature: docker: io: add PREFIX 2026-04-11 15:54:19 +03:00
coderiaser
bec3f5a799 chore: cloudcmd: v19.12.0 2026-04-09 19:14:43 +03:00
coderiaser
13b15b7bea feature: docker: io: palabra 2026-04-09 19:13:55 +03:00
coderiaser
969bc770e3 chore: cloudcmd: v19.11.14 2026-04-07 20:55:31 +03:00
coderiaser
5ecc48e348 feature: docker: io: rustup env 2026-04-07 20:55:31 +03:00
coderiaser
9efefc23e0 chore: cloudcmd: v19.11.13 2026-04-07 12:34:02 +03:00
coderiaser
6a08479a1b feature: docker: io: BUN_INSTALL 2026-04-07 12:33:26 +03:00
coderiaser
c2563ed6b3 chore: cloudcmd: v19.11.12 2026-04-06 21:22:52 +03:00
coderiaser
46c655546e feature: docker: io: npm_config_cache 2026-04-06 21:22:23 +03:00
coderiaser
4fa0786a39 chore: cloudcmd: v19.11.11 2026-04-06 20:59:36 +03:00
coderiaser
ae2ce3882c feature: docker: io: $DENO_DIR 2026-04-06 20:58:51 +03:00
coderiaser
3c9389c4f0 chore: cloudcmd: v19.11.10 2026-04-06 19:56:41 +03:00
coderiaser
bb6be7abbb feature: docker: io: nvm: node 2026-04-06 19:56:41 +03:00
coderiaser
3aac4e68b4 chore: cloudcmd: v19.11.9 2026-04-06 13:24:03 +03:00
coderiaser
ea96d13fab feature: docker: io: net-tools 2026-04-06 13:23:34 +03:00
coderiaser
f9c5804fb8 chore: cloudcmd: v19.11.8 2026-04-06 12:56:31 +03:00
coderiaser
84f96fa185 feature: docker: io: debian -> ubuntu 2026-04-06 12:56:31 +03:00
coderiaser
8238bd4011 chore: cloudcmd: v19.11.7 2026-04-05 21:38:55 +03:00
coderiaser
36cdef3717 feature: docker: io: bookworm 2026-04-05 21:38:26 +03:00
coderiaser
f8035d6c49 chore: cloudcmd: v19.11.6 2026-04-05 20:53:01 +03:00
coderiaser
845f9bd127 feature: cloudcmd: gritty v10.2.0 2026-04-05 20:52:26 +03:00
coderiaser
b5ec9ce210 chore: cloudcmd: v19.11.5 2026-04-05 00:33:37 +03:00
coderiaser
71a1d78c39 feature: docker: io: hexyl: add 2026-04-05 00:33:37 +03:00
coderiaser
6bbfeb9024 chore: cloudcmd: v19.11.4 2026-04-05 00:33:37 +03:00
coderiaser
a038470816 feature: docker: io: neovim: apt -> github 2026-04-05 00:33:37 +03:00
coderiaser
45e0ff41f4 chore: cloudcmd: v19.11.3 2026-04-04 18:39:59 +03:00
coderiaser
9970ff7629 feature: docker: io: btop 2026-04-04 18:34:13 +03:00
coderiaser
4395a47178 feature: docker: io: $PATH: add $HOME/.local/bin 2026-04-04 18:30:01 +03:00
coderiaser
77daf5028e chore: cloudcmd: v19.11.2 2026-04-04 18:23:34 +03:00
coderiaser
c40ae8e405 feature: docker: io: htop 2026-04-04 18:23:04 +03:00
coderiaser
f463c5c5a7 feature: docker: io: aptitude: add 2026-04-04 17:20:05 +03:00
coderiaser
d1032f09ab feature: docker: io: remove unused 2026-04-04 17:19:09 +03:00
coderiaser
b3e682c0a5 chore: cloudcmd: v19.11.1 2026-04-04 17:11:39 +03:00
coderiaser
ffabdc5276 feature: docker: io: ncdu 2026-04-04 17:11:39 +03:00
coderiaser
7787bfc22e feature: cloudcmd: user-menu: runFromCDN 2026-04-04 17:04:55 +03:00
coderiaser
406429aec8 chore: cloudcmd: v19.11.0 2026-04-04 02:05:28 +03:00
coderiaser
32f89d3874 feature: cloudcmd: user-menu: root 2026-04-04 02:04:40 +03:00
coderiaser
1c0021dbb6 chore: cloudcmd: v19.10.2 2026-04-04 00:06:30 +03:00
coderiaser
df4fb51716 feature: cloudcmd: aleman v2.0.1 2026-04-04 00:05:55 +03:00
coderiaser
314cceb462 chore: cloudcmd: v19.10.1 2026-04-03 23:12:27 +03:00
coderiaser
ceb7ef4ff7 feature: docker: io: keep /var/lib/apt/lists 2026-04-03 23:12:00 +03:00
coderiaser
34fad6ea73 chore: cloudcmd: v19.10.0 2026-04-02 18:19:15 +03:00
chaoliang yan
d12e7bd0c5 fix: distribute: fix event listener leak on socket disconnect (#462) 2026-04-02 18:18:47 +03:00
coderiaser
b8a621ca1b chore: cloudcmd: v19.9.24 2026-04-02 18:13:16 +03:00
coderiaser
1fc1e3734c feature: docker: io: apt-get upgrade 2026-04-02 18:13:16 +03:00
coderiaser
582b7423dc chore: cloudcmd: v19.9.23 2026-04-01 23:25:43 +03:00
coderiaser
e4d7d441fc feature: docker: io: nix 2026-04-01 23:25:43 +03:00
coderiaser
e53aa06cdb chore: cloudcmd: v19.9.22 2026-03-31 00:59:44 +03:00
coderiaser
b4345ed40d feature: docker: io: DENO_DIR 2026-03-31 00:59:02 +03:00
coderiaser
ddbf4d69a4 chore: cloudcmd: v19.9.21 2026-03-31 00:55:49 +03:00
coderiaser
e2855641ca feature: docker: io: rustup 2026-03-31 00:55:49 +03:00
coderiaser
5c6a9a958e fix: css: columns: name: 40% -> 35% 2026-03-31 00:50:35 +03:00
coderiaser
d2a7f6343a chore: cloudcmd: v19.9.20 2026-03-31 00:28:13 +03:00
coderiaser
2b29448062 feature: iocmd: io: nvm 2026-03-31 00:28:13 +03:00
coderiaser
09a8b2bd1c chore: cloudcmd: v19.9.19 2026-03-30 20:54:27 +03:00
coderiaser
6a52b11e46 feature: docker: io: go, rust 2026-03-30 20:53:59 +03:00
coderiaser
9a053ec374 chore: cloudcmd: v19.9.18 2026-03-30 20:36:44 +03:00
coderaiser
6aa496c995 chore: cloudcmd: actions: lint ☘️ 2026-03-30 20:36:43 +03:00
coderiaser
738059f27f feature: docker: io: fzf 2026-03-30 19:19:04 +03:00
coderiaser
3fc8932f4c feature: docker: io: less, el_GR 2026-03-30 19:10:51 +03:00
coderiaser
2db458bc7b chore: cloudcmd: v19.9.17 2026-03-30 00:11:13 +03:00
coderiaser
cf424d6c05 feature: docker: io: command-not-found update 2026-03-30 00:10:48 +03:00
coderiaser
8b9004b4dd chore: cloudcmd: v19.9.16 2026-03-29 23:22:14 +03:00
coderiaser
efb24e229f feature: docker: io: get back debian 2026-03-29 23:22:14 +03:00
coderiaser
19347a2b4c feature: docker: io: add command-not-found 2026-03-29 23:14:51 +03:00
coderiaser
a80f109d19 chore: cloudcmd: v19.9.15 2026-03-29 22:42:08 +03:00
coderiaser
74e008198f feature: docker: io: BUN_INSTALL_CACHE_DIR 2026-03-29 22:42:08 +03:00
coderiaser
d25a08d2bd feature: docker: io: ubuntu 2026-03-29 22:42:08 +03:00
coderiaser
e04c459489 feature: docker: io: net-tools: add 2026-03-29 20:12:12 +03:00
coderiaser
3d2944bd13 chore: cloudcmd: v19.9.14 2026-03-29 19:58:06 +03:00
coderaiser
52a9076f14 chore: cloudcmd: actions: lint ☘️ 2026-03-29 14:01:56 +00:00
coderiaser
7e2d2ede98 test: bin: coverage 2026-03-29 17:00:48 +03:00
coderiaser
6c7709be9c fix: docker: io: PS1 2026-03-29 17:00:48 +03:00
coderiaser
340a639f1b test: bin: cloudcmd: -h 2026-03-29 13:44:18 +03:00
coderiaser
aa5a772f00 chore: cloudcmd: v19.9.13 2026-03-29 13:41:12 +03:00
coderiaser
439b3710cc fix: bin: currify 2026-03-29 13:40:39 +03:00
coderiaser
e41828410c fix: docker: io: xterm-256color 2026-03-29 13:39:54 +03:00
coderiaser
799d86bda4 chore: cloudcmd: v19.9.12 2026-03-29 12:58:45 +03:00
coderiaser
a423739cb8 feature: cloudcmd: bin: get rid of require 2026-03-29 12:58:45 +03:00
coderiaser
47acb8fdf7 chore: cloudcmd: v19.9.11 2026-03-29 12:30:45 +03:00
coderiaser
1f95f18898 feature: cloudcmd: get rid of simport 2026-03-29 12:29:39 +03:00
coderiaser
2aaf0a5385 chore: cloudcmd: v19.9.10 2026-03-29 12:21:37 +03:00
coderiaser
1a44ab0eac fix: docker: io: use node
This reverts commit cc7f9dc75b.
2026-03-29 12:21:37 +03:00
coderiaser
fe3e201143 docs: help: readme from cloudcmd.io 2026-03-29 12:21:37 +03:00
coderiaser
f0deb32362 feature: docker: io: add ja_JP.UTF-8 2026-03-29 00:59:53 +02:00
coderiaser
ba6f66e73e chore: cloudcmd: v19.9.9 2026-03-29 00:56:35 +02:00
coderiaser
cc7f9dc75b feature: docker: io: use ubuntu 2026-03-29 00:55:53 +02:00
coderaiser
f671f79814
fix: docker: io: PS1 environment variable in Dockerfile 2026-03-28 23:42:07 +01:00
coderaiser
fa8875d53c chore: cloudcmd: actions: lint ☘️ 2026-03-28 20:44:34 +00:00
coderiaser
2dd62241f7 chore: cloudcmd: v19.9.8 2026-03-28 22:43:32 +02:00
coderiaser
4d1cd8cdc6 feature: docker: io: ffmpeg 2026-03-28 22:42:38 +02:00
coderiaser
54b56fdc38 feature: cloudcmd: vim: ESC: use only to enable, do not use to disable 2026-03-28 22:40:46 +02:00
coderiaser
785c32e2e3 chore: docker: io: echo 2026-03-27 19:50:01 +02:00
coderiaser
c3f2b7c32f chore: cloudcmd: v19.9.7 2026-03-27 19:46:11 +02:00
coderiaser
110908e221 feature: docker: io: apt-get 2026-03-27 19:45:32 +02:00
coderiaser
8be917da4a chore: cloudcmd: v19.9.6 2026-03-27 18:31:34 +02:00
coderiaser
8db81f30dc feature: docker: io: add UTF-8 2026-03-27 18:31:34 +02:00
coderiaser
56439d5ec2 chore: cloudcmd: v19.9.5 2026-03-26 13:17:11 +02:00
coderiaser
e761cacbd0 fix: columns: name-size-date-time: 20% -> 19% 2026-03-26 13:16:40 +02:00
coderiaser
0bb706995a chore: cloudcmd: v19.9.4 2026-03-26 13:15:02 +02:00
coderiaser
69498ed6ab feature: docker: io: pull.rebase by default 2026-03-26 13:13:44 +02:00
coderiaser
82f8d846a9 chore: docker: get rid of healcheck 2026-03-24 16:41:10 +02:00
coderiaser
1ea4aa67ca chore: cloudcmd: v19.9.3 2026-03-24 16:33:55 +02:00
coderiaser
8763788bad feature: docker: io: healthcheck 2026-03-24 16:33:31 +02:00
coderiaser
c1c67c18af chore: cloudcmd: v19.9.2 2026-03-23 23:55:21 +02:00
coderiaser
09a02074e1 feature: docker: io: git config: add 2026-03-23 23:54:48 +02:00
coderiaser
c448eaa48b feature: docker: io: buni 2026-03-23 23:54:48 +02:00
coderaiser
05ad2fd635 chore: cloudcmd: actions: lint ☘️ 2026-03-23 21:50:02 +00:00
coderiaser
b949a149ec chore: cloudcmd: v19.9.1 2026-03-23 23:49:01 +02:00
coderiaser
6e5318faa7 fix: client: modules: config: input: quote 2026-03-23 23:48:34 +02:00
coderiaser
ec66ba78a7 chore: cloudcmd: v19.9.0 2026-03-23 23:04:38 +02:00
coderiaser
a1216cddeb feature: cloudcmd: add ability to hide port configuration 2026-03-23 23:03:47 +02:00
coderiaser
2f0affd3a3 chore: cloudcmd: v19.8.15 2026-03-23 22:51:10 +02:00
coderiaser
68b2aa780b feature: docker: io: cloudcmd_vim 2026-03-23 22:50:34 +02:00
coderiaser
56e21b4011 chore: cloudcmd: v19.8.14 2026-03-23 22:12:37 +02:00
coderiaser
665ed9c2e9 fix: docker: io: get back port 2026-03-23 22:12:03 +02:00
coderiaser
a44a28f496 chore: cloudcmd: v19.8.13 2026-03-23 19:42:52 +02:00
coderaiser
f51229fc6f feature: docker: io: port 2026-03-23 17:42:02 +00:00
coderiaser
0c9f3922f8 chore: actions: lint 2026-03-23 19:19:04 +02:00
coderiaser
4410cd2665 chore: cloudcmd: v19.8.12 2026-03-23 17:07:19 +00:00
coderiaser
618a561514 feature: docker: io: PS1 2026-03-23 17:07:19 +00:00
coderiaser
682729ac3e chore: cloudcmd: v19.8.11 2026-03-23 18:53:19 +02:00
coderiaser
67f27d8425 feature: docker: io: bun, deno 2026-03-23 18:52:54 +02:00
coderiaser
97672ef5d5 fix: docker: io: apt-get install -> apt-get 2026-03-23 18:41:51 +02:00
coderiaser
e6684df59e chore: cloudcmd: v19.8.10 2026-03-23 18:32:52 +02:00
coderiaser
9493e4de7e featur: docker: io: ls --color 2026-03-23 18:18:10 +02:00
coderiaser
025b005e12 feature: docker: io: add PS1 2026-03-23 18:16:38 +02:00
coderiaser
2d524a4974 chore: cloudcmd: v19.8.9 2026-03-23 15:46:53 +02:00
coderiaser
5e0b681d8b chore: docker: lint 2026-03-23 15:46:11 +02:00
coderiaser
b052cf220d fix: cloudcmd: no time available: --.--.---- -> --:--:-- (#461) 2026-03-23 15:46:00 +02:00
coderiaser
4fa92f9394 chore: cloudcmd: v19.8.8 2026-03-23 12:23:24 +02:00
coderiaser
02dbe56d79 feature: server: user-menu: when error send it 2026-03-23 12:20:31 +02:00
coderiaser
e616cc0bc9 chore: cloudcmd: v19.8.7 2026-03-23 12:13:18 +02:00
coderiaser
ecc76e8b1e feature: docker: io: renamify-cli, runny, redfork 2026-03-23 12:12:28 +02:00
coderiaser
0fbfa0f0cf chore: cloudcmd: v19.8.6 2026-03-23 01:49:08 +02:00
coderiaser
53c072abe4 fix: @putout/plugin-cloudcmd: devDependencies -> dependnecies 2026-03-23 01:48:46 +02:00
coderiaser
4b9922bf92 feature: docker: /usr/src: app -> cloudcmd 2026-03-23 01:43:05 +02:00
coderiaser
1bb77992b0 chore: cloudcmd: v19.8.5 2026-03-23 01:39:37 +02:00
coderiaser
bec991d941 chore: lint 2026-03-23 01:39:15 +02:00
coderiaser
56fc8b838a fix: docker: gritty 2026-03-23 01:31:58 +02:00
coderiaser
df21e81d5a chore: docker: lint 2026-03-23 00:52:54 +02:00
coderiaser
8b83277ec0 chore: cloudcmd: v19.8.4 2026-03-23 00:30:04 +02:00
coderiaser
660f57e914 chore: actions: docker: set-output -> GITHUB_OUTPUT (https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/) 2026-03-23 00:30:02 +02:00
coderiaser
151c56f9a3 feature: Dockerfile: simplify 2026-03-23 00:19:55 +02:00
coderiaser
9e529605e1 feature: docker: vim nvim 2026-03-22 23:22:01 +02:00
coderiaser
ca97c21e9c chore: cloudcmd: v19.8.3 2026-03-22 22:25:32 +02:00
coderiaser
01677e6a09 fix: docker: io: slim -> io 2026-03-22 22:24:44 +02:00
coderiaser
7e35c60677 feature: docker: io: curl wget 2026-03-22 22:19:21 +02:00
coderiaser
a20640ed75 chore: cloudcmd: v19.8.2 2026-03-22 16:02:19 +02:00
coderaiser
e5b221f7ac feature: docker: io: add 2026-03-22 13:59:47 +00:00
coderiaser
2396000a6a chore: cloudcmd: v19.8.1 2026-03-22 02:04:27 +02:00
coderiaser
7661f69f1a test: cloudcmd: coverage 2026-03-22 02:04:27 +02:00
coderaiser
708a4c6b99 feature: docker: slim: add 2026-03-21 23:38:56 +00:00
coderaiser
84f3a3dc79 chore: cloudcmd: actions: lint ☘️ 2026-03-21 22:16:01 +00:00
coderaiser
80613f46fa feature: docker: slim: add 2026-03-21 22:14:53 +00:00
coderaiser
e9a8910c9c chore: cloudcmd: actions: lint ☘️ 2026-03-20 15:56:30 +00:00
coderiaser
e78f5d943f chore: cloudcmd: v19.8.0 2026-03-20 17:55:27 +02:00
coderiaser
10934b3ad9 feature: cloudcmd: add ability to show modification time (#230) 2026-03-20 17:54:58 +02:00
coderaiser
ddf5a59f70 chore: cloudcmd: actions: lint ☘️ 2026-03-20 14:55:06 +00:00
coderiaser
59037f2c18 fix: cloudcmd: bin: --show-config 2026-03-20 16:53:57 +02:00
coderiaser
1444550865 chore: cloudcmd: v19.7.1 2026-03-18 12:46:21 +02:00
coderiaser
b0c1d36c81 feature: cloudcmd: @cloudcmd/fileop v9.0.7 (#460) 2026-03-18 12:45:30 +02:00
coderaiser
10b225a337 chore: cloudcmd: actions: lint ☘️ 2026-03-17 19:12:28 +00:00
coderiaser
289f67e0d3 chore: cloudcmd: v19.7.0 2026-03-17 21:11:22 +02:00
coderiaser
daf8387516 feature: cloudfunc: override date format (#459) 2026-03-17 21:10:56 +02:00
coderaiser
170b4da211 chore: cloudcmd: actions: lint ☘️ 2026-03-17 18:45:13 +00:00
coderiaser
c9b27bb514 chore: cloudcmd: v19.6.9 2026-03-17 20:44:05 +02:00
coderiaser
5c19ddba61 chore: lint 2026-03-17 20:43:43 +02:00
coderiaser
b28a070a28 feature: cloudcmd: redzip v4.6.1 2026-03-17 20:41:28 +02:00
coderiaser
43c5a01102 feature: cloudcmd: css-minimizer-webpack-plugin v8.0.0 2026-03-17 20:41:21 +02:00
coderiaser
15dcae5c5e feature: cloudcmd: webpack-cli v7.0.2 2026-03-17 20:41:15 +02:00
coderiaser
5976da81df feature: cloudcmd: @cloudcmd/fileop v9.0.5 2026-02-28 15:50:32 +02:00
coderiaser
37cb83f2cf feature: cloudcmd: redzip v4.6.0 2026-02-28 15:50:27 +02:00
coderiaser
dd5f392293 chore: cloudcmd: v19.6.8 2026-02-27 01:14:17 +02:00
coderiaser
15fab5142f feature: cloudcmd: copymitter v10.3.0 (#458) 2026-02-27 01:13:19 +02:00
coderiaser
d2801ca896 chore: cloudcmd: v19.6.7 2026-02-26 20:18:27 +02:00
coderiaser
68c7d0be3b feature: cloudcmd: onezip v7.0.0 2026-02-26 20:17:28 +02:00
coderiaser
bfbaae9ec0 chore: cloudcmd: v19.6.6 2026-02-26 19:24:05 +02:00
coderiaser
3987cc82c1 feature: cloudcmd: redzip v4.5.1 (#457) 2026-02-26 19:23:14 +02:00
coderiaser
559b7424b6 chore: cloudcmd: v19.6.5 2026-02-26 16:06:55 +02:00
coderiaser
964ae989f2 feature: cloudcmd: redzip v4.5.0 (#457) 2026-02-26 16:06:21 +02:00
coderaiser
ff4e02c210 chore: cloudcmd: actions: lint ☘️ 2026-02-25 15:04:30 +00:00
coderiaser
9ead35242a chore: lint: scan -> fix 2026-02-25 17:03:22 +02:00
coderiaser
061ab38b24 chore: madrun: simplify 2026-02-25 17:02:22 +02:00
coderiaser
07bc9e2782 chore: cloudcmd: v19.6.4 2026-02-25 17:00:54 +02:00
coderiaser
a66eeda37e feature: cloudcmd: copymitter v10.2.0 (coderaiser/cloudcmd#457) 2026-02-25 16:59:19 +02:00
coderiaser
4340533acf feature: cloudcmd: c8 v11.0.0 2026-02-25 16:59:12 +02:00
coderiaser
394939e0d5 chore: cloudcmd: v19.6.3 2026-02-24 23:21:34 +02:00
coderiaser
0857711f61 feature: cloudcmd: redzip v4.2.0 (#457) 2026-02-24 23:21:27 +02:00
coderiaser
5aba36cca0 chore: cloudcmd: v19.6.2 2026-02-24 21:26:52 +02:00
coderiaser
3d2e5feda2 chore: lint 2026-02-24 21:24:15 +02:00
coderiaser
321a54dd59 feature: cloudcmd: @cloudcmd/fileop v9.0.2 (#457) 2026-02-24 21:23:33 +02:00
coderiaser
feb6eb2468 chore: cloudcmd: v19.6.1 2026-02-24 15:12:26 +02:00
coderiaser
7c5ac408e5 feature: cloudcmd: @cloudcmd/fileop v9.0.1 (#457) 2026-02-24 15:11:44 +02:00
coderiaser
37ecb3d7ac docs: readme: import: default -> named 2026-02-21 17:36:59 +02:00
coderiaser
52023c82cb chore: cloudcmd: v19.6.0 2026-02-21 17:34:43 +02:00
coderiaser
6d19bf2e14 feature: common: object.omit -> omit 2026-02-21 17:33:20 +02:00
coderiaser
d7214aba70 chore: cloudcmd: v19.5.1 2026-02-18 19:57:27 +02:00
coderiaser
6e1cf4ef52 feature: cloudcmd: supermenu v5.0.0 2026-02-18 19:56:56 +02:00
coderiaser
3aed798ff1 chore: cloudcmd: v19.5.0 2026-02-18 19:46:35 +02:00
coderaiser
957fbc6533 chore: cloudcmd: actions: lint ☘️ 2026-02-18 17:41:57 +00:00
coderiaser
b20539ef2b feature: common: entity: encode {,} 2026-02-18 19:40:55 +02:00
coderiaser
7ef134f46f feature: cloudcmd: rendy v5.0.0 2026-02-18 19:21:47 +02:00
coderiaser
cdeb40d191 chore: cloudcmd: v19.4.1 2026-02-18 14:55:46 +02:00
coderiaser
1e18d51342 feature: cloudcmd: @cloudcmd/fileop v9.0.0 2026-02-18 14:54:54 +02:00
coderiaser
8836d816d5 chore: cloudcmd: v19.4.0 2026-02-18 14:16:01 +02:00
coderiaser
ff9fcb1189 chore: lint 2026-02-18 14:15:22 +02:00
coderiaser
45cf9bafcd fix: menu: prefix (#456) 2026-02-18 14:09:30 +02:00
coderiaser
3e647290fd feature: cloudcmd: redlint v6.0.0 2026-02-18 14:04:19 +02:00
coderiaser
800ed012ba feature: cloudcmd: putout v42.0.5 2026-02-18 14:04:18 +02:00
coderiaser
525c17d4bd feature: cloudcmd: madrun v13.0.0 2026-02-18 14:04:17 +02:00
coderiaser
4424749978 feature: cloudcmd: eslint-plugin-putout v31.0.0 2026-02-18 14:04:17 +02:00
coderiaser
9fc3c389bf chore: cloudcmd: v19.3.9 2026-02-15 23:46:59 +02:00
coderiaser
9ffe3ef137 feature: cloudcmd: copymitter v10.0.0 (#457) 2026-02-15 23:44:18 +02:00
coderiaser
a9105e6797 chore: cloudcmd: v19.3.8 2026-02-15 22:35:16 +02:00
coderiaser
3438a0c6a1 chore: webpack: add css minification 2026-02-15 22:34:39 +02:00
coderaiser
e969b3c2b3 chore: cloudcmd: actions: lint ☘️ 2026-02-15 20:25:32 +00:00
coderiaser
d274a2b3db fix: spinner (#456) 2026-02-15 22:24:34 +02:00
coderiaser
5c9ae0a0de chore: cloudcmd: v19.3.7 2026-02-13 16:25:48 +02:00
coderiaser
8fd79a2792 feature: cloudcmd: win32 v8.0.0 2026-02-13 16:24:43 +02:00
coderiaser
9267f7a28d chore: cloudcmd: v19.3.6 2026-02-12 14:07:31 +02:00
coderiaser
3479c3b3f2 test: server: route: console-io -> gritty 2026-02-12 14:06:46 +02:00
coderiaser
ac94eccd90 feature: cloudcmd: konsole: named 2026-02-12 14:00:34 +02:00
coderiaser
144e4a3468 feature: cloudcmd: gritty v10.0.0 2026-02-12 14:00:34 +02:00
coderiaser
938f9e7674 feature: cloudcmd: console-io v15.0.1 2026-02-10 20:01:56 +02:00
coderiaser
c229160605 chore: cloudcmd: v19.3.5 2026-02-08 20:41:09 +02:00
coderiaser
fb40bd9cec feature: rm cssnano: has no sense for spinner, option disabled by default (https://svgo.dev/docs/plugins/convertPathData/) 2026-02-08 20:39:03 +02:00
coderiaser
76125be9f5 feature: cloudcmd: eslint v10.0.0 2026-02-07 16:51:36 +02:00
coderiaser
d0730e12d9 chore: cloudcmd: v19.3.4 2026-02-06 22:00:21 +02:00
coderiaser
66a08c7f71 feature: cloudcmd: deepword v11.0.0 2026-02-06 21:59:42 +02:00
119 changed files with 2697 additions and 531 deletions

View file

@ -1,43 +1,31 @@
export default {
'F2 - Rename file': async ({DOM}) => {
await DOM.renameCurrent();
},
'L - Lint': async ({CloudCmd}) => {
const {TerminalRun} = CloudCmd;
await run(TerminalRun, 'npm run lint');
},
'F - Fix Lint': async ({CloudCmd}) => {
const {TerminalRun} = CloudCmd;
await run(TerminalRun, 'npm run fix:lint');
},
'T - Test': async ({CloudCmd}) => {
const {TerminalRun} = CloudCmd;
await run(TerminalRun, 'npm run test');
},
'C - Coverage': async ({CloudCmd}) => {
const {TerminalRun} = CloudCmd;
await run(TerminalRun, 'npm run coverage');
},
'D - Build Dev': async ({CloudCmd}) => {
const {TerminalRun} = CloudCmd;
await run(TerminalRun, 'npm run build:client:dev');
CloudCmd.refresh();
},
'P - Build Prod': async ({CloudCmd}) => {
const {TerminalRun} = CloudCmd;
await run(TerminalRun, 'npm run build:client');
CloudCmd.refresh();
},
'F2 - Rename File': renameCurrent,
'L - Lint': run('npm run lint'),
'F - Fix Lint': run('npm run fix:lint'),
'T - Test': run('npm run test'),
'C - Coverage': run('npm run coverage'),
'D - Build Dev': run('npm run build:client:dev'),
'P - Build Prod': run('npm run build:client'),
};
async function run(TerminalRun, command) {
await TerminalRun.show({
command,
closeMessage: 'Press any key to close Terminal',
autoClose: false,
});
async function renameCurrent(DOM) {
await DOM.renameCurrent();
}
function run(command) {
return async ({CloudCmd, DOM}) => {
const {TerminalRun, config} = CloudCmd;
const {CurrentInfo} = DOM;
const {dirPath} = CurrentInfo;
const cwd = config('root') + dirPath;
return await TerminalRun.show({
cwd,
command,
closeMessage: 'Press any key to close Terminal',
autoClose: false,
});
};
}

60
.github/workflows/docker-io.yml vendored Normal file
View file

@ -0,0 +1,60 @@
name: Docker IO
permissions:
contents: write
on:
- push
jobs:
buildx:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Use Node.js 24.x
uses: actions/setup-node@v6
with:
node-version: 24.x
- name: Install Redrun
run: bun i redrun -g --no-save
- name: NPM Install
run: bun i --no-save
- name: Lint
run: redrun lint
- name: Build
id: build
run: |
redrun build
VERSION=$(grep '"version":' package.json -m1 | cut -d\" -f4)
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to DockerHub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push io-image
uses: docker/build-push-action@v7
with:
context: .
file: docker/Dockerfile.io
platforms: linux/amd64,linux/arm64
push: true
tags: |
coderaiser/cloudcmd:io
coderaiser/cloudcmd:${{ steps.build.outputs.version }}-io
ghcr.io/${{ github.repository }}-io
ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}-io

View file

@ -1,4 +1,6 @@
name: Docker CI
permissions:
contents: write
on:
push:
tags:
@ -29,24 +31,25 @@ jobs:
id: build
run: |
redrun build
echo "::set-output name=version::$(grep '"version":' package.json -m1 | cut -d\" -f4)"
VERSION=$(grep '"version":' package.json -m1 | cut -d\" -f4)
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Login to DockerHub
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push base-image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v7
with:
context: .
file: docker/Dockerfile
@ -58,14 +61,28 @@ jobs:
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}
- name: Build and push alpine-image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v7
with:
context: .
file: docker/Dockerfile.alpine
platforms: linux/amd64,linux/arm64
push: true
tags: |
coderaiser/cloudcmd:alpine
coderaiser/cloudcmd:latest-alpine
coderaiser/cloudcmd:${{ steps.build.outputs.version }}-alpine
ghcr.io/${{ github.repository }}:latest-alpine
ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}-alpine
- name: Build and push slim-image
uses: docker/build-push-action@v7
with:
context: .
file: docker/Dockerfile.slim
platforms: linux/amd64,linux/arm64
push: true
tags: |
coderaiser/cloudcmd:slim
coderaiser/cloudcmd:latest-slim
coderaiser/cloudcmd:${{ steps.build.outputs.version }}-slim
ghcr.io/${{ github.repository }}-slim
ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}-slim

View file

@ -1,6 +1,8 @@
name: Node CI
on:
- push
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
@ -11,7 +13,7 @@ jobs:
node-version:
- 22.x
- 24.x
- 25.x
- 26.x
steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
@ -22,26 +24,19 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- name: Install Redrun
run: bun i redrun -g --no-save
run: bun i redrun madrun -g --no-save
- name: Install
run: bun i --no-save
- name: Init Madrun
run: madrun --init
- name: Lint
run: redrun fix:lint
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Typos Install
run: cargo install typos-cli || echo 'already installed'
- name: Typos
run: typos --write-changes
uses: coderaiser/typos.ai@v1.1.8
with:
key: ${{ secrets.TYPOS_AI_KEY }}
- name: Commit fixes
uses: EndBug/add-and-commit@v9
uses: EndBug/add-and-commit@v10
continue-on-error: true
with:
message: "chore: ${{ env.NAME }}: actions: lint ☘️"

24
.gitignore vendored
View file

@ -1,19 +1,17 @@
package-lock.json
yarn.lock
yarn-error.log
node_modules
npm-debug.log*
coverage
modules/execon
modules/emitify
*.swp
*.log
*.lock
.nyc_output
*.swp
.DS_Store
.idea
package-lock.json
npm-debug.log*
node_modules
coverage
modules/execon
modules/emitify
dist
dist-dev
.idea

View file

@ -1,4 +1,3 @@
import process from 'node:process';
import {run, cutEnv} from 'madrun';
import {defineEnv} from 'supertape/env';
@ -7,16 +6,7 @@ const testEnv = defineEnv({
css: true,
});
const is17 = /^v1[789]/.test(process.version);
const is20 = process.version.startsWith('v2');
// fix for ERR_OSSL_EVP_UNSUPPORTED on node v17
// flag '--openssl-legacy-provider' not supported
// on earlier version of node.js
//
// https://stackoverflow.com/a/69746937/4536327
const buildEnv = (is17 || is20) && {
NODE_OPTIONS: '--openssl-legacy-provider',
const buildEnv = {
NODE_ENV: 'production',
};
@ -26,16 +16,20 @@ export default {
NODE_ENV: 'development',
}),
'build:start': () => run(['build:client', 'start']),
'build:start:dev': () => run(['build:client:dev', 'start:dev']),
'build:start:dev': () => run([
'build:client:dev',
'start:dev',
]),
'lint:all': () => run('lint:progress'),
'lint': () => 'redlint scan; 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'),
'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,mjs}' '{client,static,common,server}/**/*.spec.{js,mjs}' -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'`,
'wisdom': async () => await run(['lint:all', 'build', 'test'], null, {
@ -45,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',

View file

@ -1,34 +1,29 @@
.*
*.spec.*
*.config.*
*.fixture.js*
*.ai
*.cdr
*.eps
*.log
*.lock
.*
manifest.yml
docker
docker-compose.yml
test
now.json
app.json
bower.json
deno.json
bin/release.*
img/logo/cloudcmd-hq.png
webpack.config.js
docker
test*
fixture
fixture-*
coverage
css
html
yarn-error.log
yarn.lock
now.json
app.json
bower.json
manifest.yml
deno.json
bin/release.*
client
img/logo/cloudcmd-hq.png
webpack.config.js
*.ai
*.cdr
*.eps
*.config.*

1
.nvmrc
View file

@ -1 +0,0 @@
v20.15.1

View file

@ -1,11 +1,11 @@
{
"check-coverage": false,
"checkCoverage": false,
"all": false,
"exclude": [
"**/*.spec.js",
"**/fixture",
"**/*.*.js",
"**/*.config.*",
"**/fixture",
"**/test/**"
],
"branches": 100,

View file

@ -1,15 +1,14 @@
{
"plugins": ["cloudcmd"],
"ignore": [
"html",
"fixture*",
"*.md",
"app.json",
"fontello.json",
"*.md"
"html",
"fixture*"
],
"rules": {
"package-json/add-type": "off",
"esm/add-index-to-import": "off"
"package-json/add-type": "off"
},
"match": {
".filesystem.json": {

View file

@ -1,21 +1,18 @@
import {env} from 'node:process';
import OptimizeCssAssetsPlugin from 'optimize-css-assets-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',
}),
!isDev && new OptimizeCssAssetsPlugin(),
]);
];
const rules = [{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, {
use: [CssExtractRspackPlugin.loader, {
loader: 'css-loader',
options: {
url: true,
@ -27,8 +24,15 @@ const rules = [{
}];
export default {
mode: isDev ? 'development' : 'production',
plugins,
module: {
rules,
},
optimization: {
minimize: !isDev,
minimizer: [
new rspack.LightningCssMinimizerRspackPlugin(),
],
},
};

View file

@ -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,31 @@ 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$/,
exclude: /node_modules/,
loader: 'babel-loader',
// 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',
},
},
isDev && {
test: /\.[mc]?js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options,
},
]);
}];
const plugins = [
new NormalModuleReplacementPlugin(/^node:/, (resource) => {
@ -52,8 +59,7 @@ const plugins = [
new EnvironmentPlugin({
NODE_ENV,
}),
new WebpackBar(),
new webpack.ProvidePlugin({
new ProvidePlugin({
process: 'process/browser',
}),
];
@ -77,6 +83,8 @@ const splitChunks = {
'themes/light',
'columns/name-size',
'columns/name-size-date',
'columns/name-size-time',
'columns/name-size-date-time',
];
return !lazyChunks.includes(chunk.name);
@ -97,9 +105,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,
@ -111,6 +119,7 @@ export default {
'themes/light': `${dirThemes}/light.css`,
'columns/name-size': `${dirColumns}/name-size.css`,
'columns/name-size-date': `${dirColumns}/name-size-date.css`,
'columns/name-size-date-time': `${dirColumns}/name-size-date-time.css`,
'nojs': `${dirCss}/nojs.css`,
'help': `${dirCss}/help.css`,
'view': `${dirCss}/view.css`,
@ -153,7 +162,7 @@ export default {
},
plugins,
performance: {
maxEntrypointSize: 600_000,
maxEntrypointSize: 800_000,
maxAssetSize: 600_000,
},
};

572
ChangeLog
View file

@ -1,3 +1,575 @@
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:
- d96f6c46 css: query: hide time on mobile
- e7c55e05 docker: io: XDG_CONFIG_HOME
feature:
- ae1ca0f8 cloudcmd: cssnano-preset-default v8.0.1
- 94d5096a style: owner, mode: improve
- 807f8346 cloudcmd: superc8 v12.6.0
- 67a95722 docker: io: cline: add
- 68eacb91 qword: add
- 4acd294b docker: io: tmux
- 05c80043 cloudcmd: @supertape/loader-css v1.0.0
- 800a6545 cloudcmd: eslint-plugin-n v18.0.1
- a985bb36 cloudcmd: supertape v13.0.0
- 4315ec61 docker: io XDG_CONFIG_HOME: /etc -> /usr/local/etc
2026.05.03, v19.16.0
feature:
- d82d0335 client: vim: rr for rename file
- acfa27cf docker: io: nvchad: add
- 14f009f8 docker: io: bash-completion: add
- c6c60146 docker: io: f4
- 9a6b8935 docker: io: GOPATH
- a9bc22ce cloudcmd: operation: rm useless checks
- 36bacfe9 cloudcmd: client: key: vim: cc, mm
- 591da25c cloudcmd: ponse v8.0.0
- 1e1b073c docker: io: /usr/src/cloudcmd -> /usr/local/share/cloudcmd
2026.04.28, v19.15.0
feature:
- 9d97343b cloudcmd: operation: rm useless checks
- 46a88cfa cloudcmd: client: key: vim: cc, mm
2026.04.28, v19.14.0
feature:
- 36a8b641 cloudcmd: ponse v8.0.0
- 1c263c18 docker: io: /usr/src/cloudcmd -> /usr/local/share/cloudcmd
- ea7b828c docker: io: ubuntu: resolute
- cd6c11ac docker: io: far2l
- c4beeec6 docker: io: gdu: add
2026.04.21, v19.13.1
feature:
- d5cd11e8 cloudcmd: montag v2.0.1
- dc94d2db docker: io: add latest git version
- 1637beee docker: io: /usr/local/src -> /usr/local/share
- b1bc4e73 Docker: io: pv
- 7c0dca60 docker: io: git: master
2026.04.15, v19.13.0
fix:
- 48693d9e docker: io: XDG_CACHE_HOME
feature:
- 4b2395c5 cloudcmd: Ctrl + L: logout
- 977a8aaa docker: io: strace: add
- 7d0098fd docker: io: XDG_CACHE_HOME=/tmp/cache
- a89e901b actions: docker: io: add
2026.04.12, v19.12.5
feature:
- 1cfc1a6f docker: io: only amd64
2026.04.12, v19.12.4
feature:
- 3c2b5658 docker: io: haskell
- d37f8cd3 docker: io: palabra: node
2026.04.12, v19.12.3
feature:
- cb6cabd4 docker: io: PALABRA_DIR
2026.04.11, v19.12.2
feature:
- 2ce11fa2 docker: io: get rid of haskell: to slow install
- aa741232 docker: io: rizin, yara
2026.04.09, v19.12.1
feature:
- 5bd03215 docker: io: add PREFIX
2026.04.09, v19.12.0
feature:
- 13b15b7b docker: io: palabra
2026.04.07, v19.11.14
feature:
- a31beab0 docker: io: rustup env
2026.04.07, v19.11.13
feature:
- 6a08479a docker: io: BUN_INSTALL
2026.04.06, v19.11.12
feature:
- 46c65554 docker: io: npm_config_cache
2026.04.06, v19.11.11
feature:
- ae2ce388 docker: io: $DENO_DIR
2026.04.06, v19.11.10
feature:
- 12ea14ac docker: io: nvm: node
2026.04.06, v19.11.9
feature:
- ea96d13f docker: io: net-tools
2026.04.06, v19.11.8
feature:
- 5c08565f docker: io: debian -> ubuntu
2026.04.05, v19.11.7
feature:
- 36cdef37 docker: io: bookworm
2026.04.05, v19.11.6
feature:
- 845f9bd1 cloudcmd: gritty v10.2.0
2026.04.05, v19.11.5
feature:
- f9c513cc docker: io: hexyl: add
2026.04.04, v19.11.4
feature:
- e7347d25 docker: io: neovim: apt -> github
2026.04.04, v19.11.3
feature:
- 9970ff76 docker: io: btop
- 4395a471 docker: io: $PATH: add $HOME/.local/bin
2026.04.04, v19.11.2
feature:
- c40ae8e4 docker: io: htop
- f463c5c5 docker: io: aptitude: add
- d1032f09 docker: io: remove unused
2026.04.04, v19.11.1
feature:
- 7787bfc2 cloudcmd: user-menu: runFromCDN
2026.04.04, v19.11.0
feature:
- 32f89d38 cloudcmd: user-menu: root
2026.04.04, v19.10.2
feature:
- df4fb517 cloudcmd: aleman v2.0.1
2026.04.03, v19.10.1
feature:
- ceb7ef4f docker: io: keep /var/lib/apt/lists
2026.04.02, v19.10.0
fix:
- d12e7bd0 distribute: fix event listener leak on socket disconnect (#462)
2026.04.02, v19.9.24
feature:
- c4d26c6a docker: io: apt-get upgrade
- 9ddb8c29 docker: io: get rid of nix
- e4d7d441 docker: io: nix
2026.04.01, v19.9.23
feature:
- 5a3413ce docker: io: nix
2026.03.31, v19.9.22
feature:
- b4345ed4 docker: io: DENO_DIR
2026.03.31, v19.9.21
fix:
- 5c6a9a95 css: columns: name: 40% -> 35%
2026.03.30, v19.9.20
feature:
- cd0b5554 iocmd: io: nvm
2026.03.30, v19.9.19
feature:
- 6a52b11e docker: io: go, rust
2026.03.30, v19.9.18
feature:
- 738059f2 docker: io: fzf
- 3fc8932f docker: io: less, el_GR
2026.03.30, v19.9.17
feature:
- cf424d6c docker: io: command-not-found update
2026.03.29, v19.9.16
feature:
- 19347a2b docker: io: add command-not-found
2026.03.29, v19.9.15
feature:
- ee170552 docker: io: ubuntu
- e04c4594 docker: io: net-tools: add
2026.03.29, v19.9.14
fix:
- 6c7709be docker: io: PS1
2026.03.29, v19.9.13
fix:
- 439b3710 bin: currify
- e4182841 docker: io: xterm-256color
2026.03.29, v19.9.12
feature:
- 811a47fd cloudcmd: bin: get rid of require
2026.03.29, v19.9.11
feature:
- 1f95f188 cloudcmd: get rid of simport
2026.03.29, v19.9.10
feature:
- f0deb323 docker: io: add ja_JP.UTF-8
2026.03.29, v19.9.9
fix:
- f671f798 docker: io: PS1 environment variable in Dockerfile
feature:
- cc7f9dc7 docker: io: use ubuntu
2026.03.28, v19.9.8
feature:
- 4d1cd8cd docker: io: ffmpeg
- 54b56fdc cloudcmd: vim: ESC: use only to enable, do not use to disable
2026.03.27, v19.9.7
feature:
- 110908e2 docker: io: apt-get
2026.03.26, v19.9.6
feature:
- 6450a2f8 docker: io: add UTF-8
2026.03.26, v19.9.5
fix:
- e761cacb columns: name-size-date-time: 20% -> 19%
2026.03.26, v19.9.4
feature:
- 69498ed6 docker: io: pull.rebase by default
2026.03.24, v19.9.3
feature:
- 8763788b docker: io: healthcheck
2026.03.23, v19.9.2
feature:
- 09a02074 docker: io: git config: add
- c448eaa4 docker: io: buni
2026.03.23, v19.9.1
fix:
- 6e5318fa client: modules: config: input: quote
2026.03.23, v19.9.0
feature:
- a1216cdd cloudcmd: add ability to hide port configuration
2026.03.23, v19.8.15
feature:
- 68b2aa78 docker: io: cloudcmd_vim
2026.03.23, v19.8.14
fix:
- 665ed9c2 docker: io: get back port
2026.03.23, v19.8.13
feature:
- 618a5615 docker: io: PS1
2026.03.23, v19.8.12
feature:
- 556b0150 docker: io: PS1
2026.03.23, v19.8.11
fix:
- 97672ef5 docker: io: apt-get install -> apt-get
feature:
- 67f27d84 docker: io: bun, deno
2026.03.23, v19.8.10
feature:
- 025b005e docker: io: add PS1
2026.03.23, v19.8.9
fix:
- b052cf22 cloudcmd: no time available: --.--.---- -> --:--:-- (#461)
2026.03.23, v19.8.8
feature:
- 02dbe56d server: user-menu: when error send it
2026.03.23, v19.8.7
feature:
- ecc76e8b docker: io: renamify-cli, runny, redfork
2026.03.23, v19.8.6
fix:
- 53c072ab @putout/plugin-cloudcmd: devDependencies -> dependnecies
feature:
- 4b9922bf docker: /usr/src: app -> cloudcmd
2026.03.23, v19.8.5
fix:
- 56fc8b83 docker: gritty
2026.03.22, v19.8.4
feature:
- 5000227e docker: vim nvim
2026.03.22, v19.8.3
fix:
- 01677e6a docker: io: slim -> io
feature:
- 7e35c606 docker: io: curl wget
2026.03.22, v19.8.2
feature:
- e5b221f7 docker: io: add
2026.03.22, v19.8.1
feature:
- 708a4c6b docker: slim: add
- 80613f46 docker: slim: add
2026.03.20, v19.8.0
fix:
- 59037f2c cloudcmd: bin: --show-config
feature:
- 10934b3a cloudcmd: add ability to show modification time (#230)
2026.03.18, v19.7.1
feature:
- b0c1d36c cloudcmd: @cloudcmd/fileop v9.0.7 (#460)
2026.03.17, v19.7.0
feature:
- daf83875 cloudfunc: override date format (#459)
2026.03.17, v19.6.9
feature:
- b28a070a cloudcmd: redzip v4.6.1
- 43c5a011 cloudcmd: css-minimizer-webpack-plugin v8.0.0
- 15dcae5c cloudcmd: webpack-cli v7.0.2
- 5976da81 cloudcmd: @cloudcmd/fileop v9.0.5
- 37cb83f2 cloudcmd: redzip v4.6.0
2026.02.27, v19.6.8
feature:
- 15fab514 cloudcmd: copymitter v10.3.0 (#458)
2026.02.26, v19.6.7
feature:
- 68c7d0be cloudcmd: onezip v7.0.0
2026.02.26, v19.6.6
feature:
- 3987cc82 cloudcmd: redzip v4.5.1 (#457)
2026.02.26, v19.6.5
feature:
- 964ae989 cloudcmd: redzip v4.5.0 (#457)
2026.02.25, v19.6.4
feature:
- a66eeda3 cloudcmd: copymitter v10.2.0 (coderaiser/cloudcmd#457)
- 4340533a cloudcmd: c8 v11.0.0
- 0857711f cloudcmd: redzip v4.2.0 (#457)
2026.02.24, v19.6.3
feature:
- 2234f1b4 cloudcmd: redzip v4.2.0 (#475)
2026.02.24, v19.6.2
feature:
- 321a54dd cloudcmd: @cloudcmd/fileop v9.0.2 (#457)
2026.02.24, v19.6.1
feature:
- 7c5ac408 cloudcmd: @cloudcmd/fileop v9.0.1 (#457)
2026.02.21, v19.6.0
feature:
- 6d19bf2e common: object.omit -> omit
2026.02.18, v19.5.1
feature:
- 6e1cf4ef cloudcmd: supermenu v5.0.0
2026.02.18, v19.5.0
feature:
- b20539ef common: entity: encode {,}
- 7ef134f4 cloudcmd: rendy v5.0.0
2026.02.18, v19.4.1
feature:
- 1e18d513 cloudcmd: @cloudcmd/fileop v9.0.0
2026.02.18, v19.4.0
fix:
- 45cf9baf menu: prefix (#456)
feature:
- 3e647290 cloudcmd: redlint v6.0.0
- 800ed012 cloudcmd: putout v42.0.5
- 525c17d4 cloudcmd: madrun v13.0.0
- 44247499 cloudcmd: eslint-plugin-putout v31.0.0
2026.02.15, v19.3.9
feature:
- 9ffe3ef1 cloudcmd: copymitter v10.0.0 (#457)
2026.02.15, v19.3.8
fix:
- d274a2b3 spinner (#456)
2026.02.13, v19.3.7
feature:
- 8fd79a27 cloudcmd: win32 v8.0.0
2026.02.12, v19.3.6
feature:
- ac94eccd cloudcmd: konsole: named
- 144e4a34 cloudcmd: gritty v10.0.0
- 938f9e76 cloudcmd: console-io v15.0.1
2026.02.08, v19.3.5
feature:
- fb40bd9c rm cssnano: has no sense for spinner, option disabled by default (https://svgo.dev/docs/plugins/convertPathData/)
- 76125be9 cloudcmd: eslint v10.0.0
2026.02.06, v19.3.4
feature:
- 66a08c7f cloudcmd: deepword v11.0.0
2026.02.05, v19.3.3
feature:

158
HELP.md
View file

@ -1,6 +1,6 @@
# Cloud Commander v19.3.3
# Cloud Commander v19.19.1
### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL]
### [Main][MainURL] [Blog][BlogURL] [Demo][DemoURL] [Deploy](#deploy)
[MainURL]: https://cloudcmd.io "Main"
[BlogURL]: https://blog.cloudcmd.io "Blog"
@ -9,13 +9,17 @@
[DWORD]: https://github.com/cloudcmd/dword "Editor based on CodeMirror"
[EDWARD]: https://github.com/cloudcmd/edward "Editor based on Ace"
[DEEPWORD]: https://github.com/cloudcmd/deepword "Editor based on Monaco"
[QWORD]: https://github.com/cloudcmd/qword "Editor based on CodeMirror 6"
[EDWARD_KEYS]: https://github.com/cloudcmd/edward/#hot-keys "Edward Hot keys"
[TERMUX]: https://termux.com "Termux"
[INLY]: https://github.com/coderaiser/node-inly "Extract archive"
[DeployInstaPodsIMG]: https://img.shields.io/badge/deploy%20on-InstaPods-blue
[DeployInstaPodsURL]: https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd
**Cloud Commander** is a file manager for the web. It includes a command-line console and a text editor. Cloud Commander helps you manage your server and work with files, directories and programs in a web browser from any computer, mobile or tablet.
![Cloud Commander](/img/logo/cloudcmd.png "Cloud Commander")
![Cloud Commander](https://cloudcmd.io/img/logo/cloudcmd.png "Cloud Commander")
## Benefits
@ -26,7 +30,7 @@
- Server works on **Windows**, **Linux**, **Mac OS** and **Android** (with help of [Termux][TERMUX]).
- Can be used local or remotely.
- Adapts to screen size.
- **3 built-in editors** with support of **syntax highlighting**: [Dword][DWORD], [Edward][EDWARD] and [Deepword][DEEPWORD].
- **3 built-in editors** with support of **syntax highlighting**: [Dword][DWORD], [Edward][EDWARD] [Deepword][DEEPWORD] and [Qword][QWORD].
- [Console](https://github.com/cloudcmd/console "Console") with support of the default OS command line.
- Written in **JavaScript/Node.js**.
- Built-in archives pack: **zip** and **tar.gz**.
@ -88,6 +92,7 @@ Cloud Commander supports the following command-line parameters:
| `--contact` | enable contact
| `--config-dialog` | enable config dialog
| `--config-auth` | enable auth change in config dialog
| `--config-port` | enable port change in config dialog
| `--console` | enable console
| `--sync-console-path` | sync console path
| `--terminal` | enable terminal
@ -118,6 +123,7 @@ Cloud Commander supports the following command-line parameters:
| `--no-confirm-move` | do not confirm move
| `--no-config-dialog` | disable config dialog
| `--no-config-auth` | disable auth change in config dialog
| `--no-config-port` | disable port change in config dialog
| `--no-console` | disable console
| `--no-sync-console-path` | do not sync console path
| `--no-contact` | disable contact
@ -189,6 +195,7 @@ Then, start the server again with `cloudcmd` and reload the page.
| `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
@ -211,24 +218,27 @@ Then, start the server again with `cloudcmd` and reload the page.
When the `--vim` option is provided, or the configuration parameter `vim` is set, the following hotkeys become available:
|Key |Operation
|:----------------------|:--------------------------------------------
| `j` | navigate to next file
| `k` | navigate to previous file
| `dd` | remove current file
| `G` or `$` | navigate to bottom file
| `gg` or `^` | navigate to top file
| `v` | visual mode
| `y` | copy (selected in visual mode files)
| `p` | paste files
| `Esc` | unselect all
| `/` | find file in current directory
| `n` | navigate to next found file
| `N` | navigate to previous found file
| `md` | make directory
| `mf` | make file
| `tt` | show terminal
| `e` | edit file
| Key |Operation
|:------------|:--------------------------------------------
| `j` | navigate to next file
| `k` | navigate to previous file
| `dd` | remove current file
| `G` or `$` | navigate to bottom file
| `gg` or `^` | navigate to top file
| `v` | visual mode
| `y` | copy (selected in visual mode files)
| `p` | paste files
| `Esc` | unselect all
| `/` | find file in current directory
| `n` | navigate to next found file
| `N` | navigate to previous found file
| `md` | make directory
| `mf` | make file
| `tt` | show terminal
| `e` | edit file
| `cc` | copy
| `mm` | move
| `rr` | rename file
Commands can be joined, for example:
@ -440,6 +450,7 @@ Some config options can be overridden with environment variables, such as:
- `CLOUDCMD_CONTACT` - enable contact
- `CLOUDCMD_CONFIG_DIALOG` - enable config dialog
- `CLOUDCMD_CONFIG_AUTH` - enable auth change in config dialog
- `CLOUDCMD_CONFIG_PORT` - enable port change in config dialog
- `CLOUDCMD_CONSOLE` - enable console
- `CLOUDCMD_SYNC_CONSOLE_PATH` - sync console path
- `CLOUDCMD_TERMINAL` - enable terminal
@ -688,7 +699,7 @@ And create `index.js`:
```js
import http from 'node:http';
import cloudcmd from 'cloudcmd';
import {cloudcmd} from 'cloudcmd';
import {Server} from 'socket.io';
import express from 'express';
@ -922,9 +933,9 @@ ln -s ./sites-available/io.cloudcmd.io ./sites-enabled
## Deploy
`Cloud Commander` can be easily deployed to [Heroku](https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd "Deploy to Heroku").
`Cloud Commander` can be easily deployed to [InstaPods][DeployInstaPodsURL]. After deploy you receive email with credentials.
[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.png "Deploy to Heroku")](https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd)
[![Deploy on InstaPods](https://instapods.com/deploy-button.svg)](https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd)
## Docker
@ -1111,6 +1122,103 @@ 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)**
- *2026.04.28*, **[v19.14.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.14.0)**
- *2026.04.21*, **[v19.13.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.13.1)**
- *2026.04.15*, **[v19.13.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.13.0)**
- *2026.04.12*, **[v19.12.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.5)**
- *2026.04.12*, **[v19.12.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.4)**
- *2026.04.12*, **[v19.12.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.3)**
- *2026.04.11*, **[v19.12.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.2)**
- *2026.04.09*, **[v19.12.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.1)**
- *2026.04.09*, **[v19.12.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.0)**
- *2026.04.07*, **[v19.11.14](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.14)**
- *2026.04.07*, **[v19.11.13](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.13)**
- *2026.04.06*, **[v19.11.12](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.12)**
- *2026.04.06*, **[v19.11.11](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.11)**
- *2026.04.06*, **[v19.11.10](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.10)**
- *2026.04.06*, **[v19.11.9](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.9)**
- *2026.04.06*, **[v19.11.8](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.8)**
- *2026.04.05*, **[v19.11.7](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.7)**
- *2026.04.05*, **[v19.11.6](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.6)**
- *2026.04.05*, **[v19.11.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.5)**
- *2026.04.04*, **[v19.11.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.4)**
- *2026.04.04*, **[v19.11.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.3)**
- *2026.04.04*, **[v19.11.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.2)**
- *2026.04.04*, **[v19.11.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.1)**
- *2026.04.04*, **[v19.11.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.0)**
- *2026.04.04*, **[v19.10.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.10.2)**
- *2026.04.03*, **[v19.10.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.10.1)**
- *2026.04.02*, **[v19.10.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.10.0)**
- *2026.04.02*, **[v19.9.24](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.24)**
- *2026.04.01*, **[v19.9.23](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.23)**
- *2026.03.31*, **[v19.9.22](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.22)**
- *2026.03.31*, **[v19.9.21](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.21)**
- *2026.03.30*, **[v19.9.20](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.20)**
- *2026.03.30*, **[v19.9.19](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.19)**
- *2026.03.30*, **[v19.9.18](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.18)**
- *2026.03.30*, **[v19.9.17](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.17)**
- *2026.03.29*, **[v19.9.16](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.16)**
- *2026.03.29*, **[v19.9.15](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.15)**
- *2026.03.29*, **[v19.9.14](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.14)**
- *2026.03.29*, **[v19.9.13](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.13)**
- *2026.03.29*, **[v19.9.12](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.12)**
- *2026.03.29*, **[v19.9.11](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.11)**
- *2026.03.29*, **[v19.9.10](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.10)**
- *2026.03.29*, **[v19.9.9](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.9)**
- *2026.03.28*, **[v19.9.8](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.8)**
- *2026.03.27*, **[v19.9.7](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.7)**
- *2026.03.26*, **[v19.9.6](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.6)**
- *2026.03.26*, **[v19.9.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.5)**
- *2026.03.26*, **[v19.9.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.4)**
- *2026.03.24*, **[v19.9.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.3)**
- *2026.03.23*, **[v19.9.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.2)**
- *2026.03.23*, **[v19.9.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.1)**
- *2026.03.23*, **[v19.9.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.0)**
- *2026.03.23*, **[v19.8.15](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.15)**
- *2026.03.23*, **[v19.8.14](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.14)**
- *2026.03.23*, **[v19.8.13](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.13)**
- *2026.03.23*, **[v19.8.12](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.12)**
- *2026.03.23*, **[v19.8.11](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.11)**
- *2026.03.23*, **[v19.8.10](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.10)**
- *2026.03.23*, **[v19.8.9](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.9)**
- *2026.03.23*, **[v19.8.8](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.8)**
- *2026.03.23*, **[v19.8.7](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.7)**
- *2026.03.23*, **[v19.8.6](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.6)**
- *2026.03.23*, **[v19.8.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.5)**
- *2026.03.22*, **[v19.8.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.4)**
- *2026.03.22*, **[v19.8.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.3)**
- *2026.03.22*, **[v19.8.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.2)**
- *2026.03.22*, **[v19.8.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.1)**
- *2026.03.20*, **[v19.8.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.0)**
- *2026.03.18*, **[v19.7.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.7.1)**
- *2026.03.17*, **[v19.7.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.7.0)**
- *2026.03.17*, **[v19.6.9](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.9)**
- *2026.02.27*, **[v19.6.8](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.8)**
- *2026.02.26*, **[v19.6.7](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.7)**
- *2026.02.26*, **[v19.6.6](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.6)**
- *2026.02.26*, **[v19.6.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.5)**
- *2026.02.25*, **[v19.6.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.4)**
- *2026.02.24*, **[v19.6.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.3)**
- *2026.02.24*, **[v19.6.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.2)**
- *2026.02.24*, **[v19.6.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.1)**
- *2026.02.21*, **[v19.6.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.0)**
- *2026.02.18*, **[v19.5.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.5.1)**
- *2026.02.18*, **[v19.5.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.5.0)**
- *2026.02.18*, **[v19.4.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.4.1)**
- *2026.02.18*, **[v19.4.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.4.0)**
- *2026.02.15*, **[v19.3.9](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.9)**
- *2026.02.15*, **[v19.3.8](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.8)**
- *2026.02.13*, **[v19.3.7](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.7)**
- *2026.02.12*, **[v19.3.6](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.6)**
- *2026.02.08*, **[v19.3.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.5)**
- *2026.02.06*, **[v19.3.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.4)**
- *2026.02.05*, **[v19.3.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.3)**
- *2026.02.04*, **[v19.3.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.2)**
- *2026.02.03*, **[v19.3.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.1)**

View file

@ -1,4 +1,4 @@
# Cloud Commander v19.3.3 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL]
# Cloud Commander v19.19.1 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deploy on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL]
### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL]
@ -15,6 +15,8 @@
[GitterIMGURL]: https://img.shields.io/gitter/room/coderaiser/cloudcmd.js.svg
[DeployURL]: https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd "Deploy"
[DeployIMG]: https://www.herokucdn.com/deploy/button.png
[DeployInstaPodsIMG]: https://img.shields.io/badge/deploy%20on-InstaPods-blue
[DeployInstaPodsURL]: https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd
**Cloud Commander** a file manager for the web with console and editor.

View file

@ -1,24 +1,21 @@
#!/usr/bin/env node
import process from 'node:process';
import {createRequire} from 'node:module';
import {promisify} from 'node:util';
import {tryToCatch} from 'try-to-catch';
import {createSimport} from 'simport';
import parse from 'yargs-parser';
import exit from '../server/exit.js';
import {createConfig, configPath} from '../server/config.js';
import * as env from '../server/env.js';
import prefixer from '../server/prefixer.js';
import * as validate from '../server/validate.js';
import Info from '../package.json' with {
type: 'json',
};
process.on('unhandledRejection', exit);
const require = createRequire(import.meta.url);
const Info = require('../package.json');
const isUndefined = (a) => typeof a === 'undefined';
const simport = createSimport(import.meta.url);
const choose = (a, b) => {
if (isUndefined(a))
@ -31,8 +28,6 @@ const config = createConfig({
configPath,
});
const DIR_SERVER = '../server/';
const maybeRoot = (a) => {
if (a === '.')
return process.cwd();
@ -78,6 +73,7 @@ const yargsOptions = {
'open',
'config-dialog',
'config-auth',
'config-port',
'console',
'sync-console-path',
'contact',
@ -119,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': config('log'),
'log': choose(env.bool('log'), config('log')),
'import-url': env.parse('import_url') || config('importUrl'),
'import-listen': choose(env.bool('import_listen'), config('importListen')),
@ -132,6 +128,7 @@ const yargsOptions = {
'sync-console-path': choose(env.bool('sync_console_path'), config('syncConsolePath')),
'config-dialog': choose(env.bool('config_dialog'), config('configDialog')),
'config-auth': choose(env.bool('config_auth'), config('configAuth')),
'config-port': choose(env.bool('config_port'), config('configPort')),
'terminal-path': env.parse('terminal_path') || config('terminalPath'),
'terminal-command': env.parse('terminal_command') || config('terminalCommand'),
'terminal-auto-restart': choose(env.bool('terminal_auto_restart'), config('terminalAutoRestart')),
@ -168,7 +165,7 @@ else
main();
async function main() {
const {validateArgs} = await simport('@putout/cli-validate-args');
const {validateArgs} = await import('@putout/cli-validate-args');
const error = await validateArgs(args, [
...yargsOptions.boolean,
@ -179,7 +176,7 @@ async function main() {
return exit(error);
if (args.repl)
repl();
await repl();
validate.columns(args.columns);
validate.theme(args.theme);
@ -214,6 +211,7 @@ async function main() {
config('oneFilePanel', args.oneFilePanel);
config('configDialog', args.configDialog);
config('configAuth', args.configAuth);
config('configPort', args.configPort);
config('keysPanel', args.keysPanel);
config('export', args.export);
config('exportToken', args.exportToken);
@ -248,7 +246,7 @@ async function main() {
if (args.showConfig)
await showConfig();
const {distributeImport} = await simport('../server/distribute/import.js');
const {distributeImport} = await import('../server/distribute/import.js');
const importConfig = promisify(distributeImport);
await start(options, config);
@ -266,11 +264,12 @@ function validateRoot(root, config) {
if (root === '/')
return;
console.log(`root: ${root}`);
if (config('log'))
console.log(`root: ${root}`);
}
async function getPassword(password) {
const criton = await simport('criton');
const {default: criton} = await import('criton');
return criton(password, config('algo'));
}
@ -279,12 +278,10 @@ function version() {
}
async function start(options, config) {
const SERVER = `../server/server.js`;
if (!args.server)
return;
const server = await simport(SERVER);
const {default: server} = await import('../server/server.js');
server(options, config);
}
@ -298,8 +295,8 @@ function port(arg) {
}
async function showConfig() {
const show = await simport('../server/show-config');
const data = show(config('*'));
const {showConfig} = await import('../server/show-config.js');
const data = showConfig(config('*'));
console.log(data);
}
@ -308,13 +305,13 @@ async function readConfig(name) {
if (!name)
return;
const tryToCatch = await simport('try-to-catch');
const forEachKey = await simport('for-each-key');
const {default: forEachKey} = await import('for-each-key');
const [error, data] = await tryToCatch(simport, name);
if (error)
return exit(error.message);
const data = await import(name, {
with: {
type: 'json',
},
});
forEachKey(config, data);
}
@ -326,8 +323,8 @@ async function help() {
},
});
const forEachKey = await simport('for-each-key');
const currify = await simport('currify');
const {default: forEachKey} = await import('for-each-key');
const {default: currify} = await import('currify');
const usage = 'Usage: cloudcmd [options]';
const url = Info.homepage;
@ -339,15 +336,15 @@ async function help() {
console.log('\nGeneral help using Cloud Commander: <%s>', url);
}
function repl() {
async function repl() {
console.log('REPL mode enabled (telnet localhost 1337)');
require(`${DIR_SERVER}repl`);
await import('../server/repl.js');
}
async function checkUpdate() {
const load = await simport('package-json');
const {default: load} = await import('package-json');
const {version} = await load(Info.name, 'latest');
await showUpdateInfo(version);
}
@ -355,7 +352,7 @@ async function showUpdateInfo(version) {
if (version === Info.version)
return;
const chalk = await simport('chalk');
const {default: chalk} = await import('chalk');
const latestVersion = chalk.green.bold(`v${version}`);
const latest = `update available: ${latestVersion}`;

26
bin/cloudcmd.spec.js Normal file
View file

@ -0,0 +1,26 @@
import {spawnSync} from 'node:child_process';
import {test} from 'supertape';
import info from '../package.json' with {
type: 'json',
};
const cliPath = new URL('cloudcmd.js', import.meta.url).pathname;
test('cloudcmd: bin: cli: -h', (t) => {
const {stdout} = spawnSync(cliPath, ['-h'], {
encoding: 'utf8',
});
t.match(stdout, `Options`);
t.end();
});
test('cloudcmd: bin: cli: -v', (t) => {
const {version} = info;
const {stdout} = spawnSync(cliPath, ['-v'], {
encoding: 'utf8',
});
t.match(stdout, `v${version}`);
t.end();
});

View file

@ -3,26 +3,27 @@
import {promisify} from 'node:util';
import process from 'node:process';
import {tryToCatch} from 'try-to-catch';
import {createSimport} from 'simport';
import minor from 'minor';
import _place from 'place';
import rendy from 'rendy';
import {rendy} from 'rendy';
import shortdate from 'shortdate';
import Info from '../package.json' with {
type: 'json',
};
const simport = createSimport(import.meta.url);
const place = promisify(_place);
const Info = await simport('../package.json');
await main();
async function main() {
const history = '## Version history\n\n';
const link = '//github.com/coderaiser/cloudcmd/releases/tag/';
const template = '- ' +
'*{{ date }}*, ' +
'**[v{{ version }}]' +
'(' + link +
'(' +
link +
'v{{ version }})**\n';
const {version} = Info;
@ -35,10 +36,11 @@ async function main() {
await replaceVersion('README.md', version, versionNew);
await replaceVersion('HELP.md', version, versionNew);
const historyNew = history + rendy(template, {
date: shortdate(),
version: versionNew,
});
const historyNew = history +
rendy(template, {
date: shortdate(),
version: versionNew,
});
await replaceVersion('HELP.md', history, historyNew);
}

View file

@ -1,7 +1,7 @@
import process from 'node:process';
import Emitify from 'emitify';
import inherits from 'inherits';
import rendy from 'rendy';
import {rendy} from 'rendy';
import load from 'load.js';
import {tryToCatch} from 'try-to-catch';
import {addSlashToEnd} from 'format-io';
@ -348,7 +348,12 @@ function CloudCmdProto({DOM, Listeners}) {
'pathLink',
];
const [error, [file, path, link, pathLink]] = await tryToCatch(Files.get, names);
const [error, [
file,
path,
link,
pathLink,
]] = await tryToCatch(Files.get, names);
if (error)
return DOM.Dialog.alert(error.responseText);

View file

@ -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');
import('https://esm.sh/@putout/bundle@5.5');
}, 100);
}

View file

@ -302,6 +302,7 @@ export const isCurrentIsDir = (currentFile) => {
export const getCurrentType = (currentFile) => {
const current = currentFile || DOM.getCurrentFile();
const el = DOM.getByDataName('js-type', current);
const type = el.className
.split(' ')
.pop();

View file

@ -19,11 +19,13 @@ test('current-file: setCurrentName: setAttribute', (t) => {
currentFile.setCurrentName('hello', current);
t.calledWith(setAttribute, ['data-name', 'js-file-aGVsbG8='], 'should call setAttribute');
globalThis.DOM = DOM;
globalThis.CloudCmd = CloudCmd;
t.calledWith(setAttribute, [
'data-name',
'js-file-aGVsbG8=',
], 'should call setAttribute');
t.end();
});
@ -38,11 +40,13 @@ test('current-file: setCurrentName: setAttribute: cyrillic', (t) => {
currentFile.setCurrentName('ай', current);
t.calledWith(setAttribute, ['data-name', 'js-file-JUQwJUIwJUQwJUI5'], 'should call setAttribute');
globalThis.DOM = DOM;
globalThis.CloudCmd = CloudCmd;
t.calledWith(setAttribute, [
'data-name',
'js-file-JUQwJUIwJUQwJUI5',
], 'should call setAttribute');
t.end();
});
@ -70,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();
});
@ -90,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();
});

View file

@ -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,106 @@ 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();
});

View file

@ -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);
}
/**

View file

@ -1,6 +1,6 @@
/* global CloudCmd */
import {callbackify} from 'node:util';
import rendy from 'rendy';
import {rendy} from 'rendy';
import itype from 'itype';
import * as load from 'load.js';
import {tryToCatch} from 'try-to-catch';
@ -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);

28
client/key/binder.spec.js Normal file
View file

@ -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();
});

View file

@ -13,8 +13,8 @@ const Chars = fullstore();
const toggleVim = (keyCode, overrides = {}) => {
const {_config, config} = overrides;
if (keyCode === KEY.ESC)
_config('vim', !config('vim'));
if (!config('vim') && keyCode === KEY.ESC)
_config('vim', true);
};
const isUndefined = (a) => typeof a === 'undefined';
@ -442,6 +442,14 @@ async function _switchKey(event) {
break;
case KEY.L:
if (ctrlMeta && shift) {
CloudCmd.logOut();
event.preventDefault();
}
break;
case KEY.M:
if (ctrlMeta) {
if (config('vim'))

View file

@ -37,6 +37,7 @@ test('cloudcmd: client: key: enable vim', async (t) => {
test('cloudcmd: client: key: disable vim', async (t) => {
const _config = stub();
const config = stub();
const event = {
keyCode: ESC,
key: 'Escape',

View file

@ -22,6 +22,7 @@ export const D = 68;
export const G = 71;
export const J = 74;
export const K = 75;
export const L = 76;
export const M = 77;
export const O = 79;
export const P = 80;

View file

@ -1,6 +1,12 @@
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 +17,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();
});

View file

@ -1,5 +1,3 @@
/* global CloudCmd */
/* global DOM */
import vim from './vim.js';
import * as finder from './find.js';
import {
@ -35,13 +33,13 @@ const getOperations = (event, deps) => {
prompt = globalThis.DOM.Dialog.prompt,
preventDefault = event?.preventDefault?.bind(event),
stopImmediatePropagation = event?.preventDefault?.bind(event),
promptNewFile = DOM.promptNewFile,
promptNewFile = globalThis.DOM.promptNewFile,
toggleSelectedFile,
Buffer = {},
createFindNext = _createFindNext,
createFindPrevious = _createFindPrevious,
createMakeFile = _createMakeFile,
renameCurrent,
} = deps;
return {
@ -57,15 +55,26 @@ const getOperations = (event, deps) => {
setCurrentByName,
}),
escape: unselectFiles,
rename: () => {
event.preventDefault();
renameCurrent();
},
remove: () => {
Operation.show('delete');
},
operationCopy: () => {
event.preventDefault();
Operation.show('copy');
},
operationMove: () => {
event.preventDefault();
Operation.show('move');
},
makeDirectory: () => {
event.stopImmediatePropagation();
event.preventDefault();
DOM.promptNewDir();
globalThis.DOM.promptNewDir();
},
terminal: () => {

View file

@ -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();
@ -447,6 +448,7 @@ test('cloudcmd: client: key: set first file current: ^', async (t) => {
test('cloudcmd: client: key: visual', (t) => {
const element = {};
const toggleSelectedFile = stub();
const Info = {
element,
};
@ -463,6 +465,7 @@ test('cloudcmd: client: key: visual', (t) => {
test('cloudcmd: client: key: ESC', (t) => {
const element = {};
const unselectFiles = stub();
const Info = {
element,
};
@ -508,6 +511,7 @@ test('cloudcmd: client: key: Enter', async (t) => {
test('cloudcmd: client: key: /', (t) => {
const preventDefault = stub();
const element = {};
const Info = {
element,
files: [],
@ -654,3 +658,99 @@ test('cloudcmd: client: vim: edit', async (t) => {
t.calledWithNoArgs(CloudCmd.EditFileVim.show);
t.end();
});
test('cloudcmd: client: vim: rename', async (t) => {
const DOM = getDOM();
const renameCurrent = stub();
assign(DOM, {
renameCurrent,
});
const event = {
preventDefault: stub(),
};
await vim('rr', event, DOM);
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();
});

View file

@ -40,6 +40,9 @@ export default (key, operations = {}) => {
makeDirectory = noop,
terminal = noop,
edit = noop,
operationCopy = noop,
operationMove = noop,
rename = noop,
} = operations;
if (key === 'Enter')
@ -111,11 +114,26 @@ export default (key, operations = {}) => {
return end();
}
if (value === 'cc') {
operationCopy();
return end();
}
if (value === 'mm') {
operationMove();
return end();
}
if (value === 'mf') {
makeFile();
return end();
}
if (value === 'rr') {
rename();
return end();
}
if (key === 'd' && (visual() || prevStore === 'd')) {
stopVisual();
remove();

View file

@ -43,6 +43,28 @@ test('vim: ^', (t) => {
t.end();
});
test('vim: cc', (t) => {
const operationCopy = stub();
vim('cc', {
operationCopy,
});
t.calledWithNoArgs(operationCopy);
t.end();
});
test('vim: mm', (t) => {
const operationMove = stub();
vim('mm', {
operationMove,
});
t.calledWithNoArgs(operationMove);
t.end();
});
test('vim: w', (t) => {
const moveNext = stub();

View file

@ -55,6 +55,7 @@ let EXT;
function header() {
const fm = DOM.getFM();
const isDataset = (el) => el.dataset;
const isPanel = (el) => {
return /^js-(left|right)$/.test(el.dataset.name);
};
@ -309,8 +310,12 @@ function onDragStart(event) {
link.href = prefixURL + '/pack' + Info.path + EXT;
}
event.dataTransfer.setData('DownloadURL', 'application/octet-stream' + ':' + name +
':' + link);
event.dataTransfer.setData(
'DownloadURL',
'application/octet-stream' + ':' + name +
':' +
link,
);
}
function getLIElement(element) {

View file

@ -1,6 +1,5 @@
/* global CloudCmd, DOM, io */
import '../../../css/config.css';
import rendy from 'rendy';
import {rendy} from 'rendy';
import currify from 'currify';
import wraptile from 'wraptile';
import squad from 'squad';
@ -14,6 +13,8 @@ import {getTitle} from '#common/cloudfunc';
import * as Images from '#dom/images';
import * as input from './input.js';
const {CloudCmd, DOM} = globalThis;
const {Dialog, setTitle} = DOM;
const Name = 'Config';
@ -78,7 +79,7 @@ function initSocket() {
const ONE_MINUTE = 60 * 1000;
const socket = io.connect(href + prefixSocket + '/config', {
const socket = globalThis.io.connect(href + prefixSocket + '/config', {
reconnectionAttempts: Infinity,
reconnectionDelay: ONE_MINUTE,
path: `${prefix}/socket.io`,
@ -132,6 +133,7 @@ async function fillTemplate() {
columns,
theme,
configAuth,
configPort,
...obj
} = input.convert(config);
@ -141,6 +143,7 @@ async function fillTemplate() {
obj[`${columns}-selected`] = 'selected';
obj[`${theme}-selected`] = 'selected';
obj.configAuth = configAuth ? '' : 'hidden';
obj.configPort = configPort ? '' : 'hidden';
const innerHTML = rendy(Template, obj);

View file

@ -1,7 +1,9 @@
import currify from 'currify';
import {encode} from '#common/entity';
const isType = currify((type, object, name) => type === typeof object[name]);
const isBool = isType('boolean');
const isBool = (a) => typeof a === 'boolean';
const isString = (a) => typeof a === 'string';
const {keys} = Object;
export function getElementByName(selector, element) {
const str = `[data-name="js-${selector}"]`;
@ -19,13 +21,19 @@ export const getName = (element) => {
export const convert = (config) => {
const result = config;
const array = Object.keys(config);
const filtered = array.filter(isBool(config));
for (const name of filtered) {
for (const name of keys(config)) {
const item = config[name];
result[name] = setState(item);
if (isBool(item)) {
result[name] = setState(item);
continue;
}
if (isString(item)) {
result[name] = encode(item);
continue;
}
}
return result;

View file

@ -0,0 +1,140 @@
import {test, stub} from 'supertape';
import {
convert,
getName,
getValue,
setValue,
} from './input.js';
test('cloudcmd: client: config: input: convert', (t) => {
const result = convert({
name: 'hello <world>',
});
const expected = {
name: 'hello &lt;world&gt;',
};
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();
});

View file

@ -2,7 +2,7 @@
import Format from 'format-io';
import {fullstore} from 'fullstore';
import exec from 'execon';
import supermenu from 'supermenu';
import {supermenu} from 'supermenu';
CloudCmd.EditFile = {
init,

View file

@ -1,6 +1,6 @@
import {tryToCatch} from 'try-to-catch';
import exec from 'execon';
import supermenu from 'supermenu';
import {supermenu} from 'supermenu';
import {multiRename} from 'multi-rename';
const {CloudCmd, DOM} = globalThis;
@ -63,7 +63,10 @@ async function keyListener(event) {
if (ctrlMeta && event.keyCode === Key.S) {
hide();
} else if (ctrlMeta && event.keyCode === Key.P) {
return;
}
if (ctrlMeta && event.keyCode === Key.P) {
const [, pattern] = await Dialog.prompt('Apply pattern:', '[n][e]');
pattern && applyPattern(pattern);
}

View file

@ -1,5 +1,5 @@
/* global CloudCmd */
import montag from 'montag';
import {montag} from 'montag';
import {promisify} from 'es6-promisify';
import {tryToCatch} from 'try-to-catch';
import createElement from '@cloudcmd/create-element';

View file

@ -1,4 +1,4 @@
import supermenu from 'supermenu';
import {supermenu} from 'supermenu';
const noop = () => {};
const {CloudCmd} = globalThis;
@ -14,8 +14,9 @@ export const createCloudMenu = async (fm, options, menuData) => {
async function loadMenu() {
if (CloudCmd.config('menu') === 'aleman') {
const {prefix} = CloudCmd;
const {host, protocol} = globalThis.location;
const url = `${protocol}//${host}/node_modules/aleman/menu/menu.js`;
const url = `${protocol}//${host}${prefix}/node_modules/aleman/menu/menu.js`;
const {createMenu} = await import(/* webpackIgnore: true */url);
return createMenu;

View file

@ -1,4 +1,3 @@
/* global CloudCmd, DOM */
import exec from 'execon';
import wrap from 'wraptile';
import createElement from '@cloudcmd/create-element';
@ -6,6 +5,7 @@ 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 {

View file

@ -15,15 +15,12 @@ export const setListeners = (options) => (emitter) => {
} = options;
let done;
let lastError;
const onAbort = wraptile(({emitter, operation}) => {
emitter.abort();
const msg = `${operation} aborted`;
lastError = true;
Dialog.alert(msg, {
cancel: false,
});
@ -40,13 +37,10 @@ export const setListeners = (options) => (emitter) => {
operation,
}));
let noProgress = true;
const listeners = {
progress: (value) => {
done = value === 100;
progress.setProgress(value);
noProgress = false;
},
end: () => {
@ -54,13 +48,10 @@ export const setListeners = (options) => (emitter) => {
forEachKey(removeListener, listeners);
progress.remove();
if (lastError || done || noProgress)
callback();
callback();
},
error: async (error) => {
lastError = error;
if (noContinue) {
listeners.end(error);
Dialog.alert(error);

View file

@ -51,7 +51,7 @@ export async function init() {
}
export async function show(options = {}) {
await runTerminal(options);
return await runTerminal(options);
}
const runTerminal = promisify((options, fn) => {

View file

@ -1,4 +1,3 @@
/* global CloudCmd, DOM */
import '../../../css/user-menu.css';
import currify from 'currify';
import wraptile from 'wraptile';
@ -19,6 +18,12 @@ import {runSelected} from './run.js';
const loadCSS = load.css;
const sourceStore = fullstore();
const {
CloudCmd,
DOM,
CloudFunc,
} = globalThis;
const Name = 'UserMenu';
CloudCmd[Name] = {
@ -146,6 +151,7 @@ const runUserMenu = async (fn) => {
const [error] = await tryToCatch(fn, {
DOM,
CloudCmd,
CloudFunc,
tryToCatch,
});

View file

@ -4,6 +4,7 @@ import {parseUserMenu} from './parse-user-menu.js';
test('cloudcmd: user menu: parse', (t) => {
const fn = stub();
const __settings = {};
const result = parseUserMenu({
__settings,
'F2 - Rename file': fn,

View file

@ -1,5 +1,5 @@
import '#css/view.css';
import rendy from 'rendy';
import {rendy} from 'rendy';
import currify from 'currify';
import wraptile from 'wraptile';
import {tryToCatch} from 'try-to-catch';

View file

@ -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();
});

View file

@ -1,7 +1,7 @@
import {extname} from 'node:path';
import currify from 'currify';
export const isAudio = (name) => /\.(mp3|ogg|m4a)$/i.test(name);
export const isAudio = (name) => /\.(mp3|ogg|m4a|flac)$/i.test(name);
const testRegExp = currify((name, reg) => reg.test(name));
const getRegExp = (ext) => RegExp(`\\.${ext}$`, 'i');

View file

@ -8,6 +8,13 @@ test('cloudcmd: client: view: types: isAudio', (t) => {
t.end();
});
test('cloudcmd: client: view: types: isAudio: flac', (t) => {
const result = isAudio('hello.flac');
t.ok(result);
t.end();
});
test('cloudcmd: client: view: types: isAudio: no', (t) => {
const result = isAudio('hello');

View file

@ -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',
});

View file

@ -1,17 +1,16 @@
import rendy from 'rendy';
import {rendy} from 'rendy';
import currify from 'currify';
import store from 'fullstore';
import {fullstore} from 'fullstore';
import {encode} from '#common/entity';
export const getHeaderField = currify(_getHeaderField);
/* КОНСТАНТЫ (общие для клиента и сервера)*/
/* название программы */
const id = (a) => a;
const NAME = 'Cloud Commander';
export const dateFormatter = fullstore(id);
export const getHeaderField = currify(_getHeaderField);
export const FS = '/fs';
const Path = store();
const Path = fullstore();
Path('/');
@ -123,12 +122,13 @@ export const buildFromJSON = (params) => {
showDotFiles,
} = params;
const formatDate = dateFormatter();
const templateFile = template.file;
const templateLink = template.link;
const json = params.data;
const path = encode(json.path);
const {files} = json;
/*
@ -151,6 +151,7 @@ export const buildFromJSON = (params) => {
const name = getFieldName('name');
const size = getFieldName('size');
const date = getFieldName('date');
const time = getFieldName('time');
const header = rendy(templateFile, {
tag: 'div',
@ -160,6 +161,7 @@ export const buildFromJSON = (params) => {
name,
size,
date,
time,
owner,
mode,
});
@ -192,6 +194,7 @@ export const buildFromJSON = (params) => {
name: linkResult,
size: '&lt;dir&gt;',
date: '--.--.----',
time: '--:--:--',
owner: '.',
mode: '--- --- ---',
});
@ -210,6 +213,7 @@ export const buildFromJSON = (params) => {
type,
mode,
date,
time,
owner,
size,
} = file;
@ -231,7 +235,8 @@ export const buildFromJSON = (params) => {
type,
name: linkResult,
size,
date,
date: formatDate(date),
time,
owner,
mode,
});
@ -246,6 +251,7 @@ export const buildFromJSON = (params) => {
const updateField = (file) => ({
...file,
date: file.date || '--.--.----',
time: file.time || '--:--:--',
owner: file.owner || 'root',
size: getSize(file),
});

View file

@ -1,12 +1,18 @@
import {readFileSync} from 'node:fs';
import test from 'supertape';
import montag from 'montag';
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;
@ -53,6 +59,7 @@ test('cloudfunc: buildFromJSON: ..', (t) => {
test('cloudfunc: getPathLink: /', (t) => {
const {pathLink} = template;
const result = getPathLink('/', '', pathLink);
const expected = montag`
<a data-name="js-path-link" href="/fs/" title="/">/</a>
`;
@ -64,6 +71,7 @@ test('cloudfunc: getPathLink: /', (t) => {
test('cloudfunc: getPathLink: /hello/world', (t) => {
const {pathLink} = template;
const result = getPathLink('/hello/world', '', pathLink);
const expected = montag`
<a data-name="js-path-link" href="/fs/" title="/">/</a>hello/
`;
@ -75,6 +83,7 @@ test('cloudfunc: getPathLink: /hello/world', (t) => {
test('cloudfunc: getPathLink: prefix', (t) => {
const {pathLink} = template;
const result = getPathLink('/hello/world', '/cloudcmd', pathLink);
const expected = montag`
<a data-name="js-path-link" href="/cloudcmd/fs/" title="/">/</a>hello/
`;
@ -86,6 +95,7 @@ test('cloudfunc: getPathLink: prefix', (t) => {
test('cloudfunc: getSize: dir', (t) => {
const type = 'directory';
const size = 0;
const result = _getSize({
type,
size,
@ -100,6 +110,7 @@ test('cloudfunc: getSize: dir', (t) => {
test('cloudfunc: getSize: link: dir', (t) => {
const type = 'directory-link';
const size = 0;
const result = _getSize({
type,
size,
@ -114,6 +125,7 @@ test('cloudfunc: getSize: link: dir', (t) => {
test('cloudfunc: getSize: link: file', (t) => {
const type = 'file-link';
const size = 0;
const result = _getSize({
type,
size,
@ -128,6 +140,7 @@ test('cloudfunc: getSize: link: file', (t) => {
test('cloudfunc: getSize: file', (t) => {
const type = 'file';
const size = '100.00kb';
const result = _getSize({
type,
size,
@ -172,6 +185,39 @@ test('cloudfunc: buildFromJSON: showDotFiles: false', (t) => {
t.end();
});
test('cloudfunc: buildFromJSON: name: {{ }}', (t) => {
const data = {
path: '/media/',
files: [{
date: '30.08.2016',
mode: 'rwx rwx rwx',
name: '{{}}',
owner: 'root',
size: '7b',
type: 'file',
}],
};
const html = buildFromJSON({
prefix: '',
template,
data,
showDotFiles: false,
});
const $ = cheerio.load(html);
const el = $('[data-name="js-file-JTdCJTdCJTdEJTdE"]');
const result = el
.find('[data-name="js-name"]')
.text();
const expected = '{{}}';
t.equal(result, expected);
t.end();
});
test('cloudfunc: _getDataName', (t) => {
const result = _getDataName('s');
const expected = 'data-name="js-file-cw==" ';
@ -179,3 +225,140 @@ test('cloudfunc: _getDataName', (t) => {
t.equal(result, expected);
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/',
files: [{
date: '30.08.2016',
mode: 'rwx rwx rwx',
name: '{{}}',
owner: 'root',
size: '7b',
type: 'file',
}],
};
const oldFormatter = dateFormatter();
const formatDate = (str) => {
const [day, month, year] = str.split('.');
const date = new Date(year, month - 1, day);
return date.toLocaleDateString('en-US');
};
dateFormatter(formatDate);
const html = buildFromJSON({
prefix: '',
template,
data,
showDotFiles: false,
});
dateFormatter(oldFormatter);
const $ = cheerio.load(html);
const el = $('[data-name="js-file-JTdCJTdCJTdEJTdE"]');
const result = el
.find('[data-name="js-date"]')
.text();
const expected = '8/30/2016';
t.equal(result, expected);
t.end();
});

View file

@ -2,6 +2,8 @@ const Entities = {
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&lcub;': '{',
'&rcub;': '}',
};
const keys = Object.keys(Entities);

View file

@ -9,6 +9,14 @@ test('cloudcmd: entity: encode', (t) => {
t.end();
});
test('cloudcmd: entity: {{}}', (t) => {
const result = entity.encode('{{}}');
const expected = '&lcub;&lcub;&rcub;&rcub;';
t.equal(result, expected, 'should encode entity');
t.end();
});
test('cloudcmd: entity: decode', (t) => {
const result = entity.decode('&lt;hello&gt; ');
const expected = '<hello> ';

12
common/omit.js Normal file
View file

@ -0,0 +1,12 @@
const difference = (a, b) => new Set(a).difference(new Set(b));
const {keys} = Object;
export const omit = (a, list) => {
const result = {};
for (const key of difference(keys(a), list)) {
result[key] = a[key];
}
return result;
};

18
common/omit.spec.js Normal file
View file

@ -0,0 +1,18 @@
import {test} from 'supertape';
import {omit} from '#common/omit';
test('cloudcmd: common: omit', (t) => {
const a = {
hello: 1,
world: 2,
};
const result = omit(a, ['world']);
const expected = {
hello: 1,
};
t.deepEqual(result, expected);
t.end();
});

View file

@ -5,6 +5,8 @@ import {
getRegExp,
escapeRegExp,
getExt,
time,
timeEnd,
} from '#common/util';
test('getExt: no extension', (t) => {
@ -63,6 +65,7 @@ test('util: findObjByNameInArr: object', (t) => {
test('util: findObjByNameInArr: array', (t) => {
const name = 'hello';
const data = 'abc';
const item = {
name,
data,
@ -105,11 +108,31 @@ 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();
});
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();
});

View file

@ -0,0 +1,26 @@
.name {
width: 35%;
}
.size {
float: none;
}
.owner {
display: none;
}
.mode {
display: none;
}
.date {
float: right;
width: 19%;
}
.time {
display: inline;
float: right;
width: 20%;
}

View file

@ -18,3 +18,8 @@
float: right;
width: 19%;
}
.time {
display: none;
}

View file

@ -0,0 +1,24 @@
.name {
width: 55%;
}
.size {
float: none;
}
.owner {
display: none;
}
.mode {
display: none;
}
.date {
display: none;
}
.time {
float: right;
width: 19%;
}

View file

@ -18,3 +18,8 @@
.date {
display: none;
}
.time {
display: none;
}

View file

@ -161,8 +161,9 @@
.size,
.date,
.owner,
.time,
.mode {
display: none;
display: none !important;
}
.name {

View file

@ -228,9 +228,15 @@ a:active {
color: var(--column-color);
}
.time {
color: var(--column-color);
display: none;
}
.owner {
display: inline-block;
width: 13%;
width: 12%;
/* when inline-block
* vertical align should be
* set top to prevent additional
@ -242,7 +248,7 @@ a:active {
.mode {
float: right;
width: 18%;
width: 22%;
color: var(--column-color);
}

View file

@ -1,14 +0,0 @@
'use strict';
// used by OptimizeCssAssetsPlugin
const defaultPreset = require('cssnano-preset-default');
module.exports = defaultPreset({
svgo: {
plugins: [{
convertPathData: false,
}, {
convertShapeToPath: false,
}],
},
});

View file

@ -1,18 +1,22 @@
FROM node:lts-bookworm
FROM node
LABEL maintainer="Coderaiser"
LABEL org.opencontainers.image.source="https://github.com/coderaiser/cloudcmd"
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN mkdir -p /usr/src/cloudcmd
COPY package.json /usr/src/app/
WORKDIR /usr/src/cloudcmd
COPY package.json /usr/src/cloudcmd/
RUN curl -fsSL https://bun.com/install | bash && \
~/.bun/bin/bun i --production --no-save && \
PATH=$PATH:~/.bun/bin bun i gritty --no-save && \
~/.bun/bin/bun pm cache rm
ln -s /root/.bun/bin/bun /usr/local/bin/bun && \
chmod +x /usr/local/bin/bun && \
bun r gritty --omit dev && \
bun i gritty --omit dev && \
bun pm cache rm
COPY . /usr/src/app
COPY . /usr/src/cloudcmd
WORKDIR /
@ -22,4 +26,4 @@ ENV cloudcmd_open=false
EXPOSE 8000
ENTRYPOINT ["/usr/src/app/bin/cloudcmd.js"]
ENTRYPOINT ["/usr/src/cloudcmd/bin/cloudcmd.js"]

View file

@ -1,30 +1,34 @@
FROM node:alpine
LABEL maintainer="Coderaiser"
LABEL org.opencontainers.image.source="https://github.com/coderaiser/cloudcmd"
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN mkdir -p /usr/src/cloudcmd
COPY package.json /usr/src/app/
WORKDIR /usr/src/cloudcmd
COPY package.json /usr/src/cloudcmd/
RUN apk update && \
apk add --no-cache curl bash make g++ python3 && \
curl -fsSL https://bun.com/install | bash && \
~/.bun/bin/bun i --production --no-save && \
PATH=$PATH:~/.bun/bin bun i gritty --no-save && \
~/.bun/bin/bun pm cache rm && \
curl -fsSL https://bun.sh/install | bash && \
ln -s ~/.bun/bin/bun /usr/local/bin/bun && \
chmod +x /usr/local/bin/bun && \
bun r gritty --omit dev && \
bun i gritty --omit dev && \
bun pm cache rm && \
apk del make g++ python3 && \
rm -rf /usr/include /tmp/* /var/cache/apk/*
COPY . /usr/src/app
COPY . /usr/src/cloudcmd
WORKDIR /
ENV cloudcmd_terminal true
ENV cloudcmd_terminal_path gritty
ENV cloudcmd_open false
ENV cloudcmd_terminal=true
ENV cloudcmd_terminal_path=gritty
ENV cloudcmd_open=false
ENV cloudcmd_vim=true
EXPOSE 8000
ENTRYPOINT ["/usr/src/app/bin/cloudcmd.js"]
ENTRYPOINT ["/usr/src/cloudcmd/bin/cloudcmd.js"]

113
docker/Dockerfile.io Normal file
View file

@ -0,0 +1,113 @@
FROM ubuntu:resolute
LABEL maintainer="Coderaiser"
LABEL org.opencontainers.image.source="https://github.com/coderaiser/cloudcmd"
RUN mkdir -p /usr/local/share/cloudcmd
WORKDIR /usr/local/share/cloudcmd
COPY package.json /usr/local/share/cloudcmd/
ENV DEBIAN_FRONTEND=noninteractive \
NVM_DIR=/usr/local/share/nvm \
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 BUN_DEPS="palabra wisdom nupdate version-io redrun superc8 supertape madrun redlint putout renamify-cli runny redfork cline"
RUN apt-get update && \
apt-get upgrade -y && \
apt-get autoremove && \
apt-get install -y ${UBUNTU_DEPS} ${RUST_DEPES} ${DEPS} && \
echo "> Install git" && \
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 && \
apt-get autoremove && \
apt-get clean && \
echo "> create user" && \
useradd -m -s /bin/bash -u 1337 instalador && \
chown -R instalador /usr/local && \
chown -R instalador /tmp
USER instalador
RUN echo "> install bun" && \
curl https://bun.sh/install | bash && \
echo "> install npm globals" && \
bun i ${BUN_DEPS} -g && \
echo "> install rust go deno bun fasm nvim" && \
bun ${BUN_INSTALL}/bin/palabra i ${PALABRA_DEPS} && \
echo "> install node" && \
. $NVM_DIR/nvm.sh
USER root
RUN echo "> remove user" && \
userdel -r instalador && \
echo "> install gritty" && \
bun r gritty --omit dev && \
bun i gritty --omit dev && \
bun pm cache rm && \
echo "> setup cloudcmd" && \
ln -s /usr/local/share/cloudcmd/bin/cloudcmd.js /usr/local/bin/cloudcmd && \
echo "> setup git" && \
git config --global core.whitespace -trailing-space && \
git config --global pull.rebase true && \
git config --global init.defaultBranch master && \
echo "> configure bash" && \
echo "alias ls='ls --color=auto'" >> /etc/bash.bashrc && \
echo "alias buni='bun i --no-save'" >> /etc/bash.bashrc && \
echo "alias bat='batcat'" >> /etc/bash.bashrc && \
echo ". /usr/local/share/nvm/nvm.sh" >> /etc/bash.bashrc && \
echo ". /usr/share/bash-completion/completions/git" >> /etc/bash.bashrc && \
echo 'PS1="\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "' >> /etc/bash.bashrc && \
echo "> setup inputrc" && \
echo "set editing-mode vi" >> /etc/inputrc && \
echo "TAB: menu-complete" >> /etc/inputrc && \
echo "set UTF-8" && \
echo " > configure languages" && \
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \
echo "ru_RU.UTF-8 UTF-8" >> /etc/locale.gen && \
echo "uk_UA.UTF-8 UTF-8" >> /etc/locale.gen && \
echo "es_ES.UTF-8 UTF-8" >> /etc/locale.gen && \
echo "ja_JP.UTF-8 UTF-8" >> /etc/locale.gen && \
echo "el_GR.UTF-8 UTF-8" >> /etc/locale.gen && \
locale-gen
COPY . /usr/local/share/cloudcmd
WORKDIR /
ENV cloudcmd_terminal=true \
cloudcmd_terminal_path=gritty \
cloudcmd_vim=true \
cloudcmd_open=false \
PATH=node_modules/.bin:$PATH \
PATH=~/.local/bin:$PATH \
BUN_INSTALL_CACHE_DIR=/tmp/bun-cache \
DENO_DIR=/tmp/deno-cache \
LANG=en_US.UTF-8 \
LANGUAGE=en_US:en \
LC_ALL=en_US.UTF-8 \
TERM=xterm-256color \
XDG_CACHE_HOME=/tmp \
XDG_DATA_HOME=/usr/local/share \
XDG_CONFIG_HOME=~/.config
EXPOSE 8000
ENTRYPOINT ["/usr/local/share/cloudcmd/bin/cloudcmd.js"]

39
docker/Dockerfile.slim Normal file
View file

@ -0,0 +1,39 @@
FROM node:slim AS build
RUN mkdir -p /usr/src/cloudcmd/
WORKDIR /usr/src/cloudcmd
COPY package.json /usr/src/cloudcmd/
RUN apt-get update && \
apt-get install -y build-essential python3 libncurses5-dev pkg-config && \
apt-get install -y --no-install-recommends curl ca-certificates unzip && \
curl -fsSL https://bun.sh/install | bash && \
ln -s ~/.bun/bin/bun /usr/local/bin/bun && \
chmod +x /usr/local/bin/bun && \
bun r gritty --omit dev && \
bun i gritty --omit dev && \
~/.bun/bin/bun pm cache rm && \
rm -rf /var/lib/apt/lists/*
COPY . /usr/src/cloudcmd
FROM node:slim AS runtime
COPY --from=build /usr/src/cloudcmd /usr/src/cloudcmd
COPY --from=build /root/.bun /root/.bun
LABEL maintainer="Coderaiser"
LABEL org.opencontainers.image.source="https://github.com/coderaiser/cloudcmd"
WORKDIR /
ENV cloudcmd_terminal=true
ENV cloudcmd_terminal_path=gritty
ENV cloudcmd_open=false
ENV PATH="/root/.bun/bin:$PATH"
EXPOSE 8000
ENTRYPOINT ["/usr/src/cloudcmd/bin/cloudcmd.js"]

View file

@ -1,6 +1,5 @@
import {safeAlign} from 'eslint-plugin-putout';
import {defineConfig} from 'eslint/config';
import n from 'eslint-plugin-n';
import globals from 'globals';
import {matchToFlat} from '@putout/eslint-flat';
@ -12,23 +11,15 @@ export const match = {
'client/dom/index.*': {
'no-multi-spaces': 'off',
},
'{client,static}/**/*.{js,mjs}': {
'n/no-extraneous-require': 'off',
'client/**': {
'n/no-unsupported-features/node-builtins': 'off',
},
'bin/cloudcmd.js': {
'no-console': 'off',
},
};
export default defineConfig([
safeAlign, {
ignores: ['**/fixture'],
rules: {
'key-spacing': 'off',
'n/prefer-node-protocol': 'error',
},
plugins: {
n,
},
}, {
files: ['{client,common,static}/**/*.js'],

View file

@ -9,7 +9,6 @@
<link rel="icon" href="{{ prefix }}/favicon.ico">
<link rel=stylesheet href="{{ prefix }}/dist/cloudcmd.common.css">
<link rel="manifest" href="{{ prefix }}/public/manifest.json">
<noscript>
<link rel=stylesheet href="{{ prefix }}/dist/nojs.css">
</noscript>
@ -45,7 +44,7 @@
<script data-name="aleman-importmap" type="importmap">
{
"imports": {
"putout": "https://esm.sh/@putout/bundle@4.7.6",
"putout": "https://esm.sh/@putout/bundle@5.5",
"@putout/processor-html": "https://esm.sh/@putout/processor-html",
"fullstore": "https://esm.sh/fullstore",
"jessy": "https://esm.sh/jessy"

View file

@ -24,6 +24,7 @@
"confirmMove": true,
"configDialog": true,
"configAuth": true,
"configPort": true,
"oneFilePanel": false,
"console": true,
"syncConsolePath": false,

View file

@ -25,6 +25,7 @@
"--keys-panel ": "show keys panel",
"--config-dialog ": "enable config dialog",
"--config-auth ": "enable auth change in config dialog",
"--config-port ": "enable port change in config dialog",
"--console ": "enable console",
"--sync-console-path ": "sync console path",
"--contact ": "enable contact",
@ -56,6 +57,7 @@
"--no-confirm-move ": "do not confirm move",
"--no-config-dialog ": "disable config dialog",
"--no-config-auth ": "disable auth change in config dialog",
"--no-config-port ": "disable port change in config dialog",
"--no-console ": "disable console",
"--no-sync-console-path ": "do not sync console path",
"--no-contact ": "disable contact",

View file

@ -33,7 +33,7 @@ programs in browser from any computer, mobile or tablet device.
--show-config show config values
--show-dot-files show dot files
--show-file-name show file name in view and edit modes
--editor set editor: "dword", "edward" or "deepword"
--editor set editor: "dword", "edward", "deepword" or "qword"
--menu set menu: "supermenu" or "aleman"
--packer set packer: "tar" or "zip"
--root set root directory
@ -49,6 +49,7 @@ programs in browser from any computer, mobile or tablet device.
--contact enable contact
--config-dialog enable config dialog
--config-auth enable auth change in config dialog
--config-port enable port change in config dialog
--console enable console
--sync-console-path sync console path
--terminal enable terminal
@ -81,6 +82,7 @@ programs in browser from any computer, mobile or tablet device.
--no-contact disable contact
--no-config-dialog disable config dialog
--no-config-auth disable auth change in config dialog
--no-config-port disable port change in config dialog
--no-console disable console
--no-sync-console-path do not sync console path
--no-terminal disable terminal

View file

@ -1,6 +1,6 @@
{
"name": "cloudcmd",
"version": "19.3.3",
"version": "19.19.1",
"type": "module",
"author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
"description": "File manager for the web with console and editor",
@ -55,6 +55,7 @@
"fix:lint": "madrun fix:lint",
"lint:stream": "madrun lint:stream",
"test": "madrun test",
"test:e2e": "madrun test:e2e",
"test:client": "madrun test:client",
"test:server": "madrun test:server",
"wisdom": "madrun wisdom",
@ -84,27 +85,28 @@
},
"subdomain": "cloudcmd",
"dependencies": {
"@babel/plugin-transform-optional-chaining": "^7.21.0",
"@cloudcmd/dropbox": "^5.0.1",
"@cloudcmd/fileop": "^8.0.0",
"@cloudcmd/fileop": "^9.0.7",
"@cloudcmd/move-files": "^8.0.0",
"@cloudcmd/read-files-sync": "^2.0.0",
"@putout/cli-validate-args": "^2.0.0",
"aleman": "^2.0.0",
"@putout/plugin-cloudcmd": "^5.2.0",
"aleman": "^2.0.1",
"apart": "^2.0.0",
"chalk": "^5.3.0",
"compression": "^1.7.4",
"console-io": "^14.0.0",
"copymitter": "^9.0.0",
"console-io": "^15.0.1",
"copymitter": "^10.3.0",
"criton": "^2.0.0",
"currify": "^4.0.0",
"deepmerge": "^4.0.0",
"deepword": "^10.0.0",
"deepword": "^11.0.0",
"dword": "^16.0.0",
"edward": "^16.0.0",
"es6-promisify": "^7.0.0",
"execon": "^1.2.0",
"express": "^5.1.0",
"express-rate-limit": "^8.5.2",
"files-io": "^4.0.0",
"find-up": "^8.0.0",
"for-each-key": "^2.0.0",
@ -119,24 +121,23 @@
"markdown-it": "^14.0.0",
"mellow": "^3.0.0",
"mime-types": "^3.0.1",
"montag": "^1.2.1",
"montag": "^2.0.1",
"nano-memoize": "^3.0.16",
"nomine": "^4.0.0",
"object.omit": "^3.0.0",
"once": "^1.4.0",
"onezip": "^6.0.1",
"onezip": "^7.0.0",
"open": "^11.0.0",
"package-json": "^10.0.0",
"pipe-io": "^4.0.1",
"ponse": "^7.0.0",
"ponse": "^8.0.0",
"pullout": "^5.0.0",
"putout": "^41.0.0",
"redzip": "^4.0.0",
"rendy": "^4.1.3",
"putout": "^42.0.5",
"qword": "^1.0.0",
"redzip": "^4.6.1",
"rendy": "^5.0.0",
"restafary": "^13.0.1",
"restbox": "^4.0.0",
"shortdate": "^2.0.0",
"simport": "^1.0.1",
"socket.io": "^4.0.0",
"socket.io-client": "^4.0.1",
"squad": "^3.0.0",
@ -144,15 +145,16 @@
"try-catch": "^4.0.4",
"try-to-catch": "^4.0.0",
"tryrequire": "^3.0.0",
"win32": "^7.0.0",
"win32": "^8.0.0",
"wraptile": "^3.0.0",
"writejson": "^3.0.0",
"yargs-parser": "^22.0.0"
},
"devDependencies": {
"@babel/code-frame": "^7.22.5",
"@babel/core": "^7.22.5",
"@babel/preset-env": "^7.0.0",
"@babel/code-frame": "^8.0.0",
"@babel/core": "^8.0.1",
"@babel/plugin-transform-optional-chaining": "^8.0.1",
"@babel/preset-env": "^8.0.2",
"@cloudcmd/clipboard": "^2.0.0",
"@cloudcmd/create-element": "^2.0.0",
"@cloudcmd/modal": "^4.0.0",
@ -160,25 +162,25 @@
"@cloudcmd/stub": "^5.0.0",
"@iocmd/wait": "^2.1.0",
"@putout/eslint-flat": "^4.0.0",
"@putout/plugin-cloudcmd": "^5.2.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",
"babel-loader": "^10.0.0",
"babel-plugin-macros": "^3.0.0",
"c8": "^10.1.2",
"cheerio": "^1.0.0-rc.5",
"clean-css-loader": "^4.2.1",
"codegen.macro": "^4.0.0",
"css-loader": "^7.1.2",
"css-modules-require-hook": "^4.2.3",
"cssnano-preset-default": "^7.0.10",
"cssnano-preset-default": "^8.0.1",
"domtokenlist-shim": "^1.2.0",
"emitify": "^4.0.1",
"eslint": "^9.23.0",
"eslint-plugin-n": "^17.0.0-4",
"eslint-plugin-putout": "^30.0.0",
"eslint": "^10.0.0",
"eslint-plugin-putout": "^31.0.0",
"globals": "^17.0.0",
"gritty": "^9.0.0",
"gritty": "^10.2.0",
"gunzip-maybe": "^1.3.1",
"html-webpack-plugin": "^5.6.3",
"inherits": "^2.0.3",
@ -187,37 +189,33 @@
"just-pascal-case": "^3.2.0",
"limier": "^3.0.0",
"load.js": "^3.0.0",
"madrun": "^12.1.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",
"nodemon": "^3.0.1",
"optimize-css-assets-webpack-plugin": "^6.0.1",
"path-browserify": "^1.0.1",
"philip": "^3.0.0",
"place": "^1.1.4",
"postcss": "^8.5.3",
"process": "^0.11.10",
"readjson": "^2.0.1",
"redlint": "^5.0.0",
"redlint": "^6.0.0",
"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",
"supermenu": "^4.0.1",
"supertape": "^12.0.0",
"superc8": "^12.6.0",
"supermenu": "^5.0.0",
"supertape": "^13.0.0",
"tar-stream": "^3.0.0",
"unionfs": "^4.0.0",
"url-loader": "^4.0.0",
"util": "^0.12.5",
"webpack": "^5.99.9",
"webpack-cli": "^6.0.1",
"webpack-merge": "^6.0.1",
"webpackbar": "^7.0.0"
"webpack-merge": "^6.0.1"
},
"imports": {
"#css/": "./css/",
@ -231,6 +229,7 @@
"#dom/storage": "./client/dom/storage.js",
"#dom/rest": "./client/dom/rest.js",
"#common/util": "./common/util.js",
"#common/omit": "./common/omit.js",
"#common/cloudfunc": "./common/cloudfunc.js",
"#common/entity": "./common/entity.js",
"#server/cloudcmd": "./server/cloudcmd.js"

View file

@ -1,11 +0,0 @@
{
"short_name": "CloudCMD",
"name": "Cloud Commander",
"display": "standalone",
"start_url": "..",
"icons": [{
"src": "../img/favicon/favicon-256.png",
"type": "image/png",
"sizes": "256x256"
}]
}

10
rspack.config.js Normal file
View file

@ -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,
]);

View file

@ -5,15 +5,16 @@ import fs from 'node:fs';
import {fullstore} from 'fullstore';
import currify from 'currify';
import apart from 'apart';
import ponse from 'ponse';
import * as ponse from 'ponse';
import {restafary} from 'restafary';
import restbox from 'restbox';
import konsole from 'console-io';
import {konsole} from 'console-io';
import {edward} from 'edward';
import {dword} from 'dword';
import deepword from 'deepword';
import {deepword} from 'deepword';
import {qword} from 'qword';
import nomine from 'nomine';
import fileop from '@cloudcmd/fileop';
import {fileop} from '@cloudcmd/fileop';
import * as cloudfunc from '#common/cloudfunc';
import authentication from './auth.js';
import {createConfig, configPath} from './config.js';
@ -51,6 +52,7 @@ export default cloudcmd;
export function cloudcmd(params) {
const p = params || {};
const options = p.config || {};
const config = p.configManager || createConfig({
configPath,
});
@ -151,6 +153,12 @@ function listen({prefixSocket, socket, config}) {
prefixSocket: `${prefixSocket}/dword`,
});
qword.listen(socket, {
root,
auth,
prefixSocket: `${prefixSocket}/qword`,
});
deepword.listen(socket, {
root,
auth,
@ -185,7 +193,7 @@ function cloudcmdMiddle({modules, config}) {
const zip = apart(config, 'zip');
const root = apart(config, 'root');
const ponseStatic = ponse.static({
const ponseStatic = ponse.createStatic({
cache,
root: DIR_ROOT,
});
@ -214,6 +222,14 @@ function cloudcmdMiddle({modules, config}) {
dropbox,
dropboxToken,
}),
qword({
root,
online,
diff,
zip,
dropbox,
dropboxToken,
}),
deepword({
root,
online,
@ -241,6 +257,7 @@ function cloudcmdMiddle({modules, config}) {
}),
userMenu({
menuName: '.cloudcmd.menu.js',
config,
}),
rest({
config,

View file

@ -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,
@ -10,6 +10,9 @@ import cloudcmd, {
_initAuth,
_getIndexPath,
} from '#server/cloudcmd';
import {connect} from '../test/before.js';
const noop = () => {};
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@ -158,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();
});
@ -179,19 +185,63 @@ test('cloudcmd: sw', async (t) => {
t.end();
});
test('cloudcmd: manifest.json', async (t) => {
const config = {
auth: true,
};
test('cloudcmd: no params', (t) => {
const middle = cloudcmd();
const options = {
config,
};
const {status} = await request.get('/public/manifest.json', {
options,
});
t.equal(status, 200, 'should return manifest.json even when authentication is enabled');
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: noop,
},
});
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();
});

View file

@ -35,6 +35,7 @@ export const getColumns = ({isDev = _isDev()} = {}) => {
const readFilesSyncMemo = nanomemoize((isDev) => {
const dist = getDist(isDev);
const columnsDir = path.join(__dirname, '..', dist, 'columns');
const names = fs
.readdirSync(columnsDir)
.filter(not(isMap));

View file

@ -1,13 +1,12 @@
import path, {dirname} from 'node:path';
import path from 'node:path';
import fs from 'node:fs';
import Emitter from 'node:events';
import {homedir} from 'node:os';
import {fileURLToPath} from 'node:url';
import currify from 'currify';
import wraptile from 'wraptile';
import {tryToCatch} from 'try-to-catch';
import pullout from 'pullout';
import ponse from 'ponse';
import * as ponse from 'ponse';
import jonny from 'jonny';
import jju from 'jju';
import writejson from 'writejson';
@ -15,15 +14,16 @@ import {tryCatch} from 'try-catch';
import criton from 'criton';
import * as CloudFunc from '#common/cloudfunc';
import exit from './exit.js';
import rootConfig from '../json/config.json' with {
type: 'json',
};
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const DIR_SERVER = `${__dirname}/`;
const isUndefined = (a) => typeof a === 'undefined';
const DIR = `${DIR_SERVER}../`;
const HOME = homedir();
const resolve = Promise.resolve.bind(Promise);
const formatMsg = currify((a, b) => CloudFunc.formatMsg(a, b));
const {apiURL} = CloudFunc;
@ -32,10 +32,10 @@ const key = (a) => Object
.keys(a)
.pop();
const ConfigPath = path.join(DIR, 'json/config.json');
const connection = currify(_connection);
const connectionWrapped = wraptile(_connection);
const middle = currify(_middle);
const readjsonSync = (name) => {
@ -44,8 +44,6 @@ const readjsonSync = (name) => {
});
};
const rootConfig = readjsonSync(ConfigPath);
function read(filename) {
if (!filename)
return rootConfig;
@ -197,6 +195,7 @@ function get(manage, request, response) {
async function patch(manage, request, response) {
const name = 'config.json';
const cache = false;
const options = {
name,
request,

View file

@ -1,7 +1,7 @@
import currify from 'currify';
import wraptile from 'wraptile';
import squad from 'squad';
import omit from 'object.omit';
import {omit} from '#common/omit';
import log, {
exportStr,
connectedStr,

View file

@ -7,6 +7,8 @@ import _forEachKey from 'for-each-key';
import log from './log.js';
import * as env from '../env.js';
const isUndefined = (a) => typeof a === 'undefined';
const noop = () => {};
const forEachKey = currify(_forEachKey);
@ -36,9 +38,14 @@ const addUrl = currify((url, a) => `${url}: ${a}`);
const rmListeners = wraptile((socket, listeners) => {
socket.removeListener('connect', listeners.onConnect);
socket.removeListener('accept', listeners.onAccept);
socket.removeListener('config', listeners.onConfig);
socket.removeListener('error', listeners.onError);
socket.removeListener('connection_error', listeners.onError);
socket.removeListener('connect_error', listeners.onConnectError);
socket.removeListener('reject', listeners.onReject);
if (listeners.onChange)
socket.removeListener('change', listeners.onChange);
});
const canceled = (f) => f(null, {
@ -58,7 +65,7 @@ const emitAuth = wraptile((importUrl, config, socket) => {
const updateConfig = currify((config, data) => {
for (const [key, value] of entries(data)) {
if (typeof env.parse(key) !== 'undefined')
if (!isUndefined(env.parse(key)))
continue;
config(key, value);
@ -104,17 +111,6 @@ export const distributeImport = (config, options, fn) => {
const onConnect = emitAuth(importUrl, config, socket);
const onAccept = logWrapped(isLog, importStr, `${connectedStr} to ${colorUrl}`);
const onDisconnect = squad(...[
done(fn, statusStore),
logWrapped(isLog, importStr, `${disconnectedStr} from ${colorUrl}`),
rmListeners(socket, {
onError,
onConnect,
onConfig,
}),
]);
const onChange = squad(logWrapped(isLog, importStr), config);
const onReject = squad(superFn('reject'), logWrapped(
@ -123,6 +119,20 @@ export const distributeImport = (config, options, fn) => {
tokenRejectedStr,
));
const onDisconnect = squad(...[
done(fn, statusStore),
logWrapped(isLog, importStr, `${disconnectedStr} from ${colorUrl}`),
rmListeners(socket, {
onConnect,
onAccept,
onConfig,
onError,
onConnectError,
onReject,
onChange,
}),
]);
socket.on('connect', onConnect);
socket.on('accept', onAccept);
socket.on('disconnect', onDisconnect);

View file

@ -32,3 +32,32 @@ 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.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({
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();
});

View file

@ -54,3 +54,16 @@ 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();
});

View file

@ -4,44 +4,46 @@ 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();
});

View file

@ -2,7 +2,7 @@ import {callbackify} from 'node:util';
import {fileURLToPath} from 'node:url';
import {dirname} from 'node:path';
import pullout from 'pullout';
import ponse from 'ponse';
import {getQuery} from 'ponse';
import {read} from 'redzip';
import root from '../root.js';
import parse from './worker.js';
@ -40,7 +40,7 @@ function parseName(query, name, rootDir) {
}
async function onGET(request, name, root) {
const query = ponse.getQuery(request);
const query = getQuery(request);
const fileName = parseName(query, name, root);
const stream = await read(fileName);
const data = await pullout(stream);

View file

@ -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';
@ -90,6 +90,7 @@ test('cloudcmd: markdown: no request', async (t) => {
test('cloudcmd: markdown', async (t) => {
const configManager = cloudcmd.createConfigManager();
const fixtureDir = new URL('fixture', import.meta.url).pathname;
const config = {
auth: false,
root: fixtureDir,
@ -109,6 +110,7 @@ test('cloudcmd: markdown', async (t) => {
test('cloudcmd: markdown: zip', async (t) => {
const configManager = cloudcmd.createConfigManager();
const fixtureDir = new URL('fixture', import.meta.url).pathname;
const config = {
auth: false,
root: fixtureDir,

View file

@ -2,14 +2,14 @@ import path from 'node:path';
import _fs from 'node:fs';
import process from 'node:process';
import jaguar from 'jaguar';
import onezip from 'onezip';
import {onezip} from 'onezip';
import inly from 'inly';
import wraptile from 'wraptile';
import currify from 'currify';
import pullout from 'pullout';
import json from 'jonny';
import ponse from 'ponse';
import copymitter from 'copymitter';
import * as ponse from 'ponse';
import {copymitter} from 'copymitter';
import _moveFiles from '@cloudcmd/move-files';
import * as CloudFunc from '#common/cloudfunc';
import root from '../root.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}`);
@ -72,7 +74,7 @@ function rest({fs, config, moveFiles}, request, response) {
return ponse.sendError(error, params);
if (error)
return ponse.sendError(error.stack, params);
return ponse.sendError(error, params);
ponse.send(data, params);
});
@ -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)

View file

@ -1,4 +1,4 @@
import {test} from 'supertape';
import {test, stub} from 'supertape';
import {tryToCatch} from 'try-to-catch';
import {
_formatMsg,
@ -6,6 +6,11 @@ import {
_isRootWin32,
_isRootAll,
_onPUT,
_UserError,
_getPackReg,
_getCMD,
_rename,
_getPacker,
} from './index.js';
test('rest: formatMsg', (t) => {
@ -70,3 +75,104 @@ 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.match('file.zip', result, 'should match .zip');
t.end();
});
test('rest: getPackReg: tar', (t) => {
const result = _getPackReg('tar');
t.match('file.tar.gz', result, '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', (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', (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();
});

View file

@ -1,8 +1,8 @@
import {createRequire} from 'node:module';
import {extname} from 'node:path';
import _win32 from 'win32';
import ponse from 'ponse';
import rendy from 'rendy';
import * as _win32 from 'win32';
import * as ponse from 'ponse';
import {rendy} from 'rendy';
import format from 'format-io';
import currify from 'currify';
import wraptile from 'wraptile';
@ -65,6 +65,7 @@ export const _getReadDir = getReadDir;
async function route({config, options, request, response}) {
const name = ponse.getPathName(request);
const gzip = true;
const p = {
request,
response,

View file

@ -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';
@ -243,6 +243,7 @@ test('cloudcmd: route: sendIndex: encode', async (t) => {
test('cloudcmd: route: sendIndex: encode: not encoded', async (t) => {
const name = '"><svg onload=alert(3);>';
const path = '/';
const files = [{
name,
}];
@ -276,6 +277,7 @@ test('cloudcmd: route: sendIndex: encode: not encoded', async (t) => {
test('cloudcmd: route: sendIndex: ddos: render', async (t) => {
const name = `$$$'&quot;`;
const path = '/';
const files = [{
name,
}];
@ -366,7 +368,7 @@ test('cloudcmd: route: buttons: terminal: can not load', async (t) => {
test('cloudcmd: route: buttons: terminal', async (t) => {
const config = {
terminal: true,
terminalPath: 'console-io',
terminalPath: 'gritty',
};
const options = {

View file

@ -1,23 +1,29 @@
import http from 'node:http';
import {promisify} from 'node:util';
import process from 'node:process';
import {rateLimit} from 'express-rate-limit';
import currify from 'currify';
import squad from 'squad';
import {tryToCatch} from 'try-to-catch';
import opn from 'open';
import express from 'express';
import {Server} from 'socket.io';
import tryRequire from 'tryrequire';
import wraptile from 'wraptile';
import compression from 'compression';
import tryRequire from 'tryrequire';
import {cloudcmd} from '#server/cloudcmd';
import exit from './exit.js';
const RATE_LIMIT = 1000;
const RATE_WINDOW = 15 * 60 * 1000;
const bind = (f, self) => f.bind(self);
const two = currify((f, a, b) => f(a, b));
const shutdown = wraptile(async (promises) => {
console.log('closing cloudcmd...');
const shutdown = wraptile(async (config, promises) => {
if (config('log'))
console.log('closing cloudcmd...');
await Promise.all(promises);
process.exit(0);
});
@ -25,6 +31,7 @@ const shutdown = wraptile(async (promises) => {
const promisifySelf = squad(promisify, bind);
const exitPort = two(exit, 'cloudcmd --port: %s');
const logger = tryRequire('morgan');
export default async (options, config) => {
@ -36,7 +43,7 @@ export default async (options, config) => {
const app = express();
const server = http.createServer(app);
if (logger)
if (config('log') && logger)
app.use(logger('dev'));
if (prefix)
@ -46,8 +53,15 @@ export default async (options, config) => {
path: `${prefix}/socket.io`,
});
app.use(compression());
const limiter = rateLimit({
windowMs: RATE_WINDOW,
limit: RATE_LIMIT,
});
app.set('trust proxy', 1);
app.use(compression());
app.use(limiter);
app.use(prefix, cloudcmd({
config: options,
socket: socketServer,
@ -64,14 +78,17 @@ export default async (options, config) => {
server.on('error', exitPort);
await listen(port, ip);
const close = shutdown([closeServer, closeSocket]);
const close = shutdown(config, [closeServer, closeSocket]);
process.on('SIGINT', close);
process.on('SIGUSR1', close);
const host = config('ip') || 'localhost';
const port0 = port || server.address().port;
const url = `http://${host}:${port0}${prefix}/`;
console.log(`url: ${url}`);
if (config('log'))
console.log(`url: ${url}`);
if (!config('open'))
return;

View file

@ -9,7 +9,8 @@ const noop = (req, res, next) => {
noop.listen = noop;
const _getModule = (a) => require(a);
const parseDefault = (a) => a.default || a;
const _getModule = (a) => parseDefault(require(a));
export default (config, arg, overrides = {}) => {
const {

View file

@ -24,6 +24,7 @@ export const getThemes = ({isDev = _isDev()} = {}) => {
const readFilesSyncMemo = nanomemoize((isDev) => {
const dist = getDist(isDev);
const themesDir = path.join(__dirname, '..', dist, 'themes');
const names = fs
.readdirSync(themesDir)
.filter(not(isMap));

View file

@ -30,3 +30,21 @@ 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();
});

View file

@ -1,9 +1,8 @@
import {homedir} from 'node:os';
import {readFile as _readFile} from 'node:fs/promises';
import {join} from 'node:path';
import {readFileSync} from 'node:fs';
// warm up worker cache
import montag from 'montag';
import {montag} from 'montag';
import {tryToCatch} from 'try-to-catch';
import currify from 'currify';
import {putout, codeframe} from 'putout';
@ -13,7 +12,7 @@ transpile('');
const PREFIX = '/api/v1/user-menu';
export const userMenu = currify(async ({menuName, readFile = _readFile}, req, res, next) => {
export const userMenu = currify(async ({menuName, readFile = _readFile, config}, req, res, next) => {
if (!req.url.startsWith(PREFIX))
return next();
@ -25,22 +24,26 @@ export const userMenu = currify(async ({menuName, readFile = _readFile}, req, re
res,
menuName,
readFile,
config,
});
next();
});
async function onGET({req, res, menuName, readFile}) {
async function onGET({req, res, menuName, readFile, config}) {
const {dir} = req.query;
const url = req.url.replace(PREFIX, '');
if (url === '/default')
return await sendDefaultMenu(res);
return await sendDefaultMenu(res, {
readFile,
});
const {findUp} = await import('find-up');
const cwd = join(config('root'), dir);
const [errorFind, currentMenuPath] = await tryToCatch(findUp, [menuName], {
cwd: dir,
cwd,
});
if (errorFind && errorFind.code !== 'ENOENT')
@ -58,7 +61,9 @@ async function onGET({req, res, menuName, readFile}) {
.send(e.message);
if (e)
return await sendDefaultMenu(res);
return await sendDefaultMenu(res, {
readFile,
});
const [parseError, result] = await transpile(source);
@ -86,8 +91,10 @@ function getError(error, source) {
`;
}
async function sendDefaultMenu(res) {
const menu = await getDefaultMenu();
async function sendDefaultMenu(res, {readFile}) {
const menu = await getDefaultMenu({
readFile,
});
res
.type('js')
@ -106,10 +113,13 @@ async function transpile(source) {
});
}
async function getDefaultMenu() {
async function getDefaultMenu({readFile}) {
const DEFAULT_MENU_PATH = new URL('../static/user-menu.js', import.meta.url).pathname;
const menu = readFileSync(DEFAULT_MENU_PATH, 'utf8');
const [, result] = await transpile(menu);
const menu = await readFile(DEFAULT_MENU_PATH, 'utf8');
const [error, result] = await transpile(menu);
if (error)
return String(error);
return result.code;
}

View file

@ -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';
@ -24,8 +24,11 @@ const fixtureCopy = readFileSync(fixtureCopyName, 'utf8');
const fixtureCopyFix = readFileSync(fixtureCopyFixName, 'utf8');
test('cloudcmd: user menu', async (t) => {
const config = stub().returns('');
const userMenuFile = getUserMenuFile();
const options = {
config,
menuName: '.cloudcmd.menu.js',
};
@ -38,10 +41,13 @@ test('cloudcmd: user menu', async (t) => {
});
test('cloudcmd: user menu: io.mv', async (t) => {
const config = stub().returns('');
const readFile = stub().returns(fixtureMove);
const options = {
menuName: '.cloudcmd.menu.js',
readFile,
config,
};
const {request} = serveOnce(userMenu);
@ -55,8 +61,10 @@ test('cloudcmd: user menu: io.mv', async (t) => {
});
test('cloudcmd: user menu: default menu', async (t) => {
const config = stub().returns('');
const options = {
menuName: '111.cloudcmd.menu.js',
config,
};
const {request} = serveOnce(userMenu);
@ -70,10 +78,13 @@ test('cloudcmd: user menu: default menu', async (t) => {
});
test('cloudcmd: user menu: io.cp', async (t) => {
const config = stub().returns('');
const readFile = stub().returns(fixtureCopy);
const options = {
menuName: '.cloudcmd.menu.js',
readFile,
config,
};
const {request} = serveOnce(userMenu);
@ -86,6 +97,25 @@ test('cloudcmd: user menu: io.cp', async (t) => {
t.end();
});
test('cloudcmd: user menu: broken file', async (t) => {
const readFile = stub().returns('sdfsdf /; s');
const options = {
menuName: '.cloudcmd.menu.js',
readFile,
};
const {request} = serveOnce(userMenu);
const {body} = await request.get(`/api/v1/user-menu/default`, {
options,
});
const expected = 'SyntaxError: Unexpected token (1:8)';
t.equal(body, expected);
t.end();
});
function getUserMenuFile() {
const userMenuFile = readFileSync(userMenuPath, 'utf8');
const {code} = putout(userMenuFile, {

Some files were not shown because too many files have changed in this diff Show more