mirror of
https://github.com/coderaiser/cloudcmd.git
synced 2026-07-18 00:47:01 +00:00
Compare commits
253 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f28803a82 | ||
|
|
943bdecd3e | ||
|
|
e4ff4a78cc | ||
|
|
fec5538f01 | ||
|
|
b7bb363aa8 | ||
|
|
cd4a2cec76 | ||
|
|
0638ca32cb | ||
|
|
fe185cc93e | ||
|
|
7edea50296 | ||
|
|
8258cdb651 | ||
|
|
a9e29eee85 | ||
|
|
a52a6aae62 | ||
|
|
92c79830f5 | ||
|
|
8629f67972 | ||
|
|
c04d363c46 | ||
|
|
046a4ef22e | ||
|
|
579c9e41d7 | ||
|
|
6014898610 | ||
|
|
ad3bf95e5f | ||
|
|
4e22a4edde | ||
|
|
9c50fe53e7 | ||
|
|
a2a2d51ec1 | ||
|
|
c36c32cb08 | ||
|
|
78b206e06b | ||
|
|
ef83a61a0c | ||
|
|
f5c0aa5c4c | ||
|
|
e59167b1fa | ||
|
|
ea584aceea | ||
|
|
4d7116b467 | ||
|
|
b149288545 | ||
|
|
9d06279ba8 | ||
|
|
21e1dcf0e5 | ||
|
|
b3b0c1d159 | ||
|
|
76d7bf4d73 | ||
|
|
f1d80282eb | ||
|
|
220cc5557b | ||
|
|
d2176c93fb | ||
|
|
c558e44a1c | ||
|
|
45ec062bce | ||
|
|
f8edddd442 | ||
|
|
fc3d5ac700 | ||
|
|
4ee38db68c | ||
|
|
1dfa7ca355 | ||
|
|
40ecef5ec4 | ||
|
|
cad5e0f44e | ||
|
|
d139011cc3 | ||
|
|
5d9628ce70 | ||
|
|
161bede8db | ||
|
|
2be395e640 | ||
|
|
447d990f43 | ||
|
|
4f4725ac3c | ||
|
|
eff03299b6 | ||
|
|
a6adf39db2 | ||
|
|
70ff6fb29f | ||
|
|
eb32fff688 | ||
|
|
6071b66118 | ||
|
|
83c0b8d3f1 | ||
|
|
79da2319f8 | ||
|
|
68eacb916a | ||
|
|
ccf608a52e | ||
|
|
4acd294bf6 | ||
|
|
05c800431d | ||
|
|
6ff1db2710 | ||
|
|
800a6545a1 | ||
|
|
a985bb36ca | ||
|
|
e7c55e05b8 | ||
|
|
4315ec6179 | ||
|
|
628d12fa2f | ||
|
|
d82d0335b2 | ||
|
|
acfa27cf74 | ||
|
|
14f009f832 | ||
|
|
d1f6476604 | ||
|
|
c6c6014691 | ||
|
|
9a6b89356e | ||
|
|
882fe83623 | ||
|
|
a9bc22ce29 | ||
|
|
36bacfe915 | ||
|
|
bcbef1a3b0 | ||
|
|
591da25cfe | ||
|
|
1e1b073c8c | ||
|
|
ea7b828cbb | ||
|
|
cd6c11ace2 | ||
|
|
c4beeec63b | ||
|
|
87c13e7cce | ||
|
|
d5cd11e812 | ||
|
|
dc94d2db7a | ||
|
|
1637beee78 | ||
|
|
0541b7e351 | ||
|
|
b1bc4e731f | ||
|
|
7c0dca6038 | ||
|
|
24fd0a8aa7 | ||
|
|
2b3dc1573d | ||
|
|
48693d9e24 | ||
|
|
977a8aaaf4 | ||
|
|
15537ec518 | ||
|
|
7d0098fd5c | ||
|
|
da0c4c4704 | ||
|
|
b0687b54c7 | ||
|
|
a89e901b2b | ||
|
|
ef06a0c310 | ||
|
|
e7242900a4 | ||
|
|
a6b01dce49 | ||
|
|
1cfc1a6f5d | ||
|
|
9915757dce | ||
|
|
673fba2e21 | ||
|
|
d37f8cd36e | ||
|
|
5e509a4d7b | ||
|
|
cb6cabd4c9 | ||
|
|
f61cc46f41 | ||
|
|
32a8f169d4 | ||
|
|
aa74123228 | ||
|
|
27f20d6af8 | ||
|
|
4695480ead | ||
|
|
bec3f5a799 | ||
|
|
13b15b7bea | ||
|
|
969bc770e3 | ||
|
|
5ecc48e348 | ||
|
|
9efefc23e0 | ||
|
|
6a08479a1b | ||
|
|
c2563ed6b3 | ||
|
|
46c655546e | ||
|
|
4fa0786a39 | ||
|
|
ae2ce3882c | ||
|
|
3c9389c4f0 | ||
|
|
bb6be7abbb | ||
|
|
3aac4e68b4 | ||
|
|
ea96d13fab | ||
|
|
f9c5804fb8 | ||
|
|
84f96fa185 | ||
|
|
8238bd4011 | ||
|
|
36cdef3717 | ||
|
|
f8035d6c49 | ||
|
|
845f9bd127 | ||
|
|
b5ec9ce210 | ||
|
|
71a1d78c39 | ||
|
|
6bbfeb9024 | ||
|
|
a038470816 | ||
|
|
45e0ff41f4 | ||
|
|
9970ff7629 | ||
|
|
4395a47178 | ||
|
|
77daf5028e | ||
|
|
c40ae8e405 | ||
|
|
f463c5c5a7 | ||
|
|
d1032f09ab | ||
|
|
b3e682c0a5 | ||
|
|
ffabdc5276 | ||
|
|
7787bfc22e | ||
|
|
406429aec8 | ||
|
|
32f89d3874 | ||
|
|
1c0021dbb6 | ||
|
|
df4fb51716 | ||
|
|
314cceb462 | ||
|
|
ceb7ef4ff7 | ||
|
|
34fad6ea73 | ||
|
|
d12e7bd0c5 | ||
|
|
b8a621ca1b | ||
|
|
1fc1e3734c | ||
|
|
582b7423dc | ||
|
|
e4d7d441fc | ||
|
|
e53aa06cdb | ||
|
|
b4345ed40d | ||
|
|
ddbf4d69a4 | ||
|
|
e2855641ca | ||
|
|
5c6a9a958e | ||
|
|
d2a7f6343a | ||
|
|
2b29448062 | ||
|
|
09a8b2bd1c | ||
|
|
6a52b11e46 | ||
|
|
9a053ec374 | ||
|
|
6aa496c995 | ||
|
|
738059f27f | ||
|
|
3fc8932f4c | ||
|
|
2db458bc7b | ||
|
|
cf424d6c05 | ||
|
|
8b9004b4dd | ||
|
|
efb24e229f | ||
|
|
19347a2b4c | ||
|
|
a80f109d19 | ||
|
|
74e008198f | ||
|
|
d25a08d2bd | ||
|
|
e04c459489 | ||
|
|
3d2944bd13 | ||
|
|
52a9076f14 | ||
|
|
7e2d2ede98 | ||
|
|
6c7709be9c | ||
|
|
340a639f1b | ||
|
|
aa5a772f00 | ||
|
|
439b3710cc | ||
|
|
e41828410c | ||
|
|
799d86bda4 | ||
|
|
a423739cb8 | ||
|
|
47acb8fdf7 | ||
|
|
1f95f18898 | ||
|
|
2aaf0a5385 | ||
|
|
1a44ab0eac | ||
|
|
fe3e201143 | ||
|
|
f0deb32362 | ||
|
|
ba6f66e73e | ||
|
|
cc7f9dc75b | ||
|
|
f671f79814 | ||
|
|
fa8875d53c | ||
|
|
2dd62241f7 | ||
|
|
4d1cd8cdc6 | ||
|
|
54b56fdc38 | ||
|
|
785c32e2e3 | ||
|
|
c3f2b7c32f | ||
|
|
110908e221 | ||
|
|
8be917da4a | ||
|
|
8db81f30dc | ||
|
|
56439d5ec2 | ||
|
|
e761cacbd0 | ||
|
|
0bb706995a | ||
|
|
69498ed6ab | ||
|
|
82f8d846a9 | ||
|
|
1ea4aa67ca | ||
|
|
8763788bad | ||
|
|
c1c67c18af | ||
|
|
09a02074e1 | ||
|
|
c448eaa48b | ||
|
|
05ad2fd635 | ||
|
|
b949a149ec | ||
|
|
6e5318faa7 | ||
|
|
ec66ba78a7 | ||
|
|
a1216cddeb | ||
|
|
2f0affd3a3 | ||
|
|
68b2aa780b | ||
|
|
56e21b4011 | ||
|
|
665ed9c2e9 | ||
|
|
a44a28f496 | ||
|
|
f51229fc6f | ||
|
|
0c9f3922f8 | ||
|
|
4410cd2665 | ||
|
|
618a561514 | ||
|
|
682729ac3e | ||
|
|
67f27d8425 | ||
|
|
97672ef5d5 | ||
|
|
e6684df59e | ||
|
|
9493e4de7e | ||
|
|
025b005e12 | ||
|
|
2d524a4974 | ||
|
|
5e0b681d8b | ||
|
|
b052cf220d | ||
|
|
4fa92f9394 | ||
|
|
02dbe56d79 | ||
|
|
e616cc0bc9 | ||
|
|
ecc76e8b1e | ||
|
|
0fbfa0f0cf | ||
|
|
53c072abe4 | ||
|
|
4b9922bf92 | ||
|
|
1bb77992b0 | ||
|
|
bec991d941 | ||
|
|
56fc8b838a | ||
|
|
df21e81d5a |
93 changed files with 2093 additions and 466 deletions
|
|
@ -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
60
.github/workflows/docker-io.yml
vendored
Normal 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
|
||||
28
.github/workflows/docker.yml
vendored
28
.github/workflows/docker.yml
vendored
|
|
@ -1,4 +1,6 @@
|
|||
name: Docker CI
|
||||
permissions:
|
||||
contents: write
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
|
|
@ -32,22 +34,22 @@ jobs:
|
|||
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
|
||||
|
|
@ -59,7 +61,7 @@ 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
|
||||
|
|
@ -72,7 +74,7 @@ jobs:
|
|||
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@v5
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile.slim
|
||||
|
|
@ -84,15 +86,3 @@ jobs:
|
|||
coderaiser/cloudcmd:${{ steps.build.outputs.version }}-slim
|
||||
ghcr.io/${{ github.repository }}-slim
|
||||
ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}-slim
|
||||
- name: Build and push io-image
|
||||
uses: docker/build-push-action@v5
|
||||
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
|
||||
|
|
|
|||
25
.github/workflows/nodejs.yml
vendored
25
.github/workflows/nodejs.yml
vendored
|
|
@ -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@v5
|
||||
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 ☘️"
|
||||
|
|
|
|||
|
|
@ -21,14 +21,15 @@ export default {
|
|||
'start:dev',
|
||||
]),
|
||||
'lint:all': () => run('lint:progress'),
|
||||
'lint': () => 'redlint fix; putout . --rulesdir rules',
|
||||
'lint': () => 'redlint fix; putout .',
|
||||
'lint:progress': () => run('lint', '-f progress'),
|
||||
'watch:lint': () => 'nodemon -w client -w server -w test -w common -w .webpack -x "putout -s"',
|
||||
'fresh:lint': () => run('lint', '--fresh'),
|
||||
'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, {
|
||||
|
|
@ -38,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',
|
||||
|
|
|
|||
|
|
@ -4,26 +4,23 @@
|
|||
*.ai
|
||||
*.cdr
|
||||
*.eps
|
||||
*.config.*
|
||||
*.log
|
||||
*.loc
|
||||
*.lock
|
||||
|
||||
.*
|
||||
|
||||
manifest.yml
|
||||
docker-compose.yml
|
||||
yarn.lock
|
||||
now.json
|
||||
app.json
|
||||
bower.json
|
||||
manifest.yml
|
||||
deno.json
|
||||
bin/release.*
|
||||
img/logo/cloudcmd-hq.png
|
||||
webpack.config.js
|
||||
|
||||
docker
|
||||
test
|
||||
test*
|
||||
fixture
|
||||
fixture-*
|
||||
coverage
|
||||
|
|
|
|||
1
.nvmrc
1
.nvmrc
|
|
@ -1 +0,0 @@
|
|||
v20.15.1
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"check-coverage": false,
|
||||
"checkCoverage": false,
|
||||
"all": false,
|
||||
"exclude": [
|
||||
"**/*.spec.js",
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
import {env} from 'node:process';
|
||||
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import {rspack} from '@rspack/core';
|
||||
|
||||
const {CssExtractRspackPlugin} = rspack;
|
||||
const isDev = env.NODE_ENV === 'development';
|
||||
const clean = (a) => a.filter(Boolean);
|
||||
|
||||
const plugins = clean([
|
||||
new MiniCssExtractPlugin({
|
||||
const plugins = [
|
||||
new CssExtractRspackPlugin({
|
||||
filename: '[name].css',
|
||||
}),
|
||||
]);
|
||||
];
|
||||
|
||||
const rules = [{
|
||||
test: /\.css$/i,
|
||||
use: [MiniCssExtractPlugin.loader, {
|
||||
use: [CssExtractRspackPlugin.loader, {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
url: true,
|
||||
|
|
@ -33,13 +32,7 @@ export default {
|
|||
optimization: {
|
||||
minimize: !isDev,
|
||||
minimizer: [
|
||||
new CssMinimizerPlugin({
|
||||
minimizerOptions: {
|
||||
preset: ['default', {
|
||||
svgo: false,
|
||||
}],
|
||||
},
|
||||
}),
|
||||
new rspack.LightningCssMinimizerRspackPlugin(),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -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',
|
||||
}),
|
||||
];
|
||||
|
|
@ -99,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,
|
||||
|
|
@ -156,7 +162,7 @@ export default {
|
|||
},
|
||||
plugins,
|
||||
performance: {
|
||||
maxEntrypointSize: 600_000,
|
||||
maxEntrypointSize: 800_000,
|
||||
maxAssetSize: 600_000,
|
||||
},
|
||||
};
|
||||
414
ChangeLog
414
ChangeLog
|
|
@ -1,3 +1,417 @@
|
|||
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:
|
||||
|
|
|
|||
129
HELP.md
129
HELP.md
|
|
@ -1,6 +1,6 @@
|
|||
# Cloud Commander v19.8.4
|
||||
# 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.
|
||||
|
||||

|
||||

|
||||
|
||||
## 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
|
||||
|
|
@ -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.
|
||||
|
||||
[](https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd)
|
||||
[](https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd)
|
||||
|
||||
## Docker
|
||||
|
||||
|
|
@ -1111,6 +1122,76 @@ 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)**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Cloud Commander v19.8.4 [![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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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
26
bin/cloudcmd.spec.js
Normal 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();
|
||||
});
|
||||
|
|
@ -3,17 +3,16 @@
|
|||
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 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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,13 @@ test('current-file: setCurrentName: setAttribute', (t) => {
|
|||
|
||||
currentFile.setCurrentName('hello', current);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.calledWith(setAttribute, [
|
||||
'data-name',
|
||||
'js-file-aGVsbG8=',
|
||||
], 'should call setAttribute');
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
@ -41,14 +40,13 @@ test('current-file: setCurrentName: setAttribute: cyrillic', (t) => {
|
|||
|
||||
currentFile.setCurrentName('ай', current);
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.calledWith(setAttribute, [
|
||||
'data-name',
|
||||
'js-file-JUQwJUIwJUQwJUI5',
|
||||
], 'should call setAttribute');
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
@ -76,11 +74,10 @@ test('current-file: emit', (t) => {
|
|||
|
||||
currentFile.setCurrentName('hello', current);
|
||||
|
||||
t.calledWith(emit, ['current-file', current], 'should call emit');
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.calledWith(emit, ['current-file', current], 'should call emit');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
@ -96,14 +93,12 @@ test('current-file: setCurrentName: return', (t) => {
|
|||
globalThis.CloudCmd = getCloudCmd();
|
||||
|
||||
const current = create();
|
||||
|
||||
const result = currentFile.setCurrentName('hello', current);
|
||||
|
||||
t.equal(result, link, 'should return link');
|
||||
|
||||
globalThis.DOM = DOM;
|
||||
globalThis.CloudCmd = CloudCmd;
|
||||
|
||||
t.equal(result, link, 'should return link');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
28
client/key/binder.spec.js
Normal 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();
|
||||
});
|
||||
|
|
@ -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'))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
/* global CloudCmd */
|
||||
/* global DOM */
|
||||
import vim from './vim.js';
|
||||
import * as finder from './find.js';
|
||||
import {
|
||||
|
|
@ -35,12 +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 {
|
||||
|
|
@ -56,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: () => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -657,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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ async function fillTemplate() {
|
|||
columns,
|
||||
theme,
|
||||
configAuth,
|
||||
configPort,
|
||||
...obj
|
||||
} = input.convert(config);
|
||||
|
||||
|
|
@ -142,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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
140
client/modules/config/input.spec.js
Normal file
140
client/modules/config/input.spec.js
Normal 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 <world>',
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ export const buildFromJSON = (params) => {
|
|||
name: linkResult,
|
||||
size: '<dir>',
|
||||
date: '--.--.----',
|
||||
time: '--.--.----',
|
||||
time: '--:--:--',
|
||||
owner: '.',
|
||||
mode: '--- --- ---',
|
||||
});
|
||||
|
|
@ -251,7 +251,7 @@ export const buildFromJSON = (params) => {
|
|||
const updateField = (file) => ({
|
||||
...file,
|
||||
date: file.date || '--.--.----',
|
||||
time: file.time || '--.--.----',
|
||||
time: file.time || '--:--:--',
|
||||
owner: file.owner || 'root',
|
||||
size: getSize(file),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +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;
|
||||
|
|
@ -221,6 +226,97 @@ test('cloudfunc: _getDataName', (t) => {
|
|||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: formatMsg: name', (t) => {
|
||||
const result = formatMsg('hello', 'world');
|
||||
const expected = 'hello: ok("world")';
|
||||
|
||||
t.equal(result, expected, 'should format message with name');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: formatMsg: no name', (t) => {
|
||||
const result = formatMsg('hello');
|
||||
const expected = 'hello: ok';
|
||||
|
||||
t.equal(result, expected, 'should format message without name');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: getTitle: no options', (t) => {
|
||||
const result = getTitle();
|
||||
|
||||
t.ok(result, 'should return a title string even without options');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: getTitle: with name', (t) => {
|
||||
const result = getTitle({
|
||||
name: 'MyName',
|
||||
});
|
||||
|
||||
t.match(result, 'MyName', 'should return title with name');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: getHeaderField: sort not name', (t) => {
|
||||
const result = getHeaderField('size', 'asc', 'name');
|
||||
|
||||
t.equal(result, 'name', 'should return plain name when sort does not match');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: getHeaderField: sort name asc', (t) => {
|
||||
const result = getHeaderField('name', 'asc', 'name');
|
||||
|
||||
t.equal(result, 'name', 'should return plain name when name asc');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: getDotDot: root', (t) => {
|
||||
const result = getDotDot('/');
|
||||
|
||||
t.equal(result, '/', 'should return / for root path');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: getPathLink: /a/b/c', (t) => {
|
||||
const {pathLink} = template;
|
||||
const result = getPathLink('/a/b/c/', '', pathLink);
|
||||
|
||||
t.ok(result, 'should build path link for 3-segment path');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: getPathLink: no url', (t) => {
|
||||
const [error] = tryCatch(getPathLink);
|
||||
|
||||
t.equal(error.message, 'url could not be empty!', 'should throw when url is empty');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: getPathLink: no template', (t) => {
|
||||
const [error] = tryCatch(getPathLink, '/');
|
||||
|
||||
t.equal(error.message, 'template could not be empty!', 'should throw when template is empty');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: getHeaderField: sort name desc', (t) => {
|
||||
const result = getHeaderField('name', 'desc', 'name');
|
||||
const expected = 'name↓';
|
||||
|
||||
t.equal(result, expected, 'should return name with down arrow');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: getDotDot: normal path', (t) => {
|
||||
const result = getDotDot('/hello/world/');
|
||||
const expected = '/hello';
|
||||
|
||||
t.equal(result, expected, 'should return parent directory');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudfunc: buildFromJSON: formatDate', (t) => {
|
||||
const data = {
|
||||
path: '/media/',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import {
|
|||
getRegExp,
|
||||
escapeRegExp,
|
||||
getExt,
|
||||
time,
|
||||
timeEnd,
|
||||
} from '#common/util';
|
||||
|
||||
test('getExt: no extension', (t) => {
|
||||
|
|
@ -106,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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.name {
|
||||
width: 40%;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.size {
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
.date {
|
||||
float: right;
|
||||
width: 20%;
|
||||
width: 19%;
|
||||
}
|
||||
|
||||
.time {
|
||||
|
|
|
|||
|
|
@ -161,8 +161,9 @@
|
|||
.size,
|
||||
.date,
|
||||
.owner,
|
||||
.time,
|
||||
.mode {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.name {
|
||||
|
|
|
|||
|
|
@ -235,7 +235,8 @@ a:active {
|
|||
|
||||
.owner {
|
||||
display: inline-block;
|
||||
width: 13%;
|
||||
width: 12%;
|
||||
|
||||
/* when inline-block
|
||||
* vertical align should be
|
||||
* set top to prevent additional
|
||||
|
|
@ -247,7 +248,7 @@ a:active {
|
|||
|
||||
.mode {
|
||||
float: right;
|
||||
width: 18%;
|
||||
width: 22%;
|
||||
color: var(--column-color);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
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 && \
|
||||
ln -s /root/.bun/bin/bun /usr/local/bin/bun && \
|
||||
chmod +x /usr/local/bin/bun && \
|
||||
bun i --production --no-save && \
|
||||
bun i gritty --production --no-save && \
|
||||
bun r gritty --omit dev && \
|
||||
bun i gritty --omit dev && \
|
||||
bun pm cache rm
|
||||
|
||||
COPY . /usr/src/app
|
||||
COPY . /usr/src/cloudcmd
|
||||
|
||||
WORKDIR /
|
||||
|
||||
|
|
@ -24,4 +26,4 @@ ENV cloudcmd_open=false
|
|||
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/usr/src/app/bin/cloudcmd.js"]
|
||||
ENTRYPOINT ["/usr/src/cloudcmd/bin/cloudcmd.js"]
|
||||
|
|
|
|||
|
|
@ -1,31 +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.sh/install | bash && \
|
||||
ln -s ~/.bun/bin/bun /usr/local/bin/bun && \
|
||||
chmod +x /usr/local/bin/bun && \
|
||||
bun i --production --no-save && \
|
||||
bun i gritty --production --no-save && \
|
||||
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"]
|
||||
|
|
|
|||
|
|
@ -1,38 +1,113 @@
|
|||
FROM node
|
||||
FROM ubuntu:resolute
|
||||
|
||||
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/local/share/cloudcmd
|
||||
|
||||
COPY package.json /usr/src/app/
|
||||
WORKDIR /usr/local/share/cloudcmd
|
||||
|
||||
RUN apt-get update && apt-get upgrade && apt-get autoremove && \
|
||||
apt-get install -y netcat-openbsd mc iputils-ping vim neovim sudo && \
|
||||
npm i wisdom nupdate version-io redrun madrun redlint putout -g && \
|
||||
curl -fsSL https://bun.sh/install | bash && \
|
||||
ln -s ~/.bun/bin/bun /usr/local/bin/bun && \
|
||||
chmod +x /usr/local/bin/bun && \
|
||||
curl -fsSL https://deno.land/install.sh | sh && \
|
||||
ln -s ~/.deno/bin/deno /usr/local/bin/deno && \
|
||||
chmod +x /usr/local/bin/deno && \
|
||||
bun i --production && \
|
||||
bun i gritty --production && \
|
||||
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 "ALL ALL=(ALL) NOPASSWD: /usr/bin/apt-get install *" > /etc/sudoers.d/apt-install && \
|
||||
chmod 0440 /etc/sudoers.d/apt-install && \
|
||||
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 "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/src/app
|
||||
COPY . /usr/local/share/cloudcmd
|
||||
|
||||
WORKDIR /
|
||||
|
||||
ENV cloudcmd_terminal=true
|
||||
ENV cloudcmd_terminal_path=gritty
|
||||
ENV cloudcmd_open=false
|
||||
ENV PATH=node_modules/.bin:$PATH
|
||||
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/src/app/bin/cloudcmd.js"]
|
||||
ENTRYPOINT ["/usr/local/share/cloudcmd/bin/cloudcmd.js"]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
FROM node:slim AS build
|
||||
|
||||
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 apt-get update && \
|
||||
apt-get install -y build-essential python3 libncurses5-dev pkg-config && \
|
||||
|
|
@ -11,18 +12,16 @@ RUN apt-get update && \
|
|||
curl -fsSL https://bun.sh/install | bash && \
|
||||
ln -s ~/.bun/bin/bun /usr/local/bin/bun && \
|
||||
chmod +x /usr/local/bin/bun && \
|
||||
bun i --production --no-save && \
|
||||
bun i gritty --production --no-save && \
|
||||
~/.bun/bin/bun i --production --no-save && \
|
||||
~/.bun/bin/bun add --no-save gritty && \
|
||||
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/app
|
||||
COPY . /usr/src/cloudcmd
|
||||
|
||||
FROM node:slim AS runtime
|
||||
|
||||
COPY --from=build /usr/src/app /usr/src/app
|
||||
COPY --from=build /usr/src/cloudcmd /usr/src/cloudcmd
|
||||
COPY --from=build /root/.bun /root/.bun
|
||||
|
||||
LABEL maintainer="Coderaiser"
|
||||
|
|
@ -37,4 +36,4 @@ ENV PATH="/root/.bun/bin:$PATH"
|
|||
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/usr/src/app/bin/cloudcmd.js"]
|
||||
ENTRYPOINT ["/usr/src/cloudcmd/bin/cloudcmd.js"]
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"confirmMove": true,
|
||||
"configDialog": true,
|
||||
"configAuth": true,
|
||||
"configPort": true,
|
||||
"oneFilePanel": false,
|
||||
"console": true,
|
||||
"syncConsolePath": false,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
44
package.json
44
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cloudcmd",
|
||||
"version": "19.8.4",
|
||||
"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",
|
||||
|
|
@ -89,7 +90,8 @@
|
|||
"@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",
|
||||
|
|
@ -104,6 +106,7 @@
|
|||
"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",
|
||||
|
|
@ -118,7 +121,7 @@
|
|||
"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",
|
||||
"once": "^1.4.0",
|
||||
|
|
@ -126,15 +129,15 @@
|
|||
"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": "^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",
|
||||
|
|
@ -148,10 +151,10 @@
|
|||
"yargs-parser": "^22.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-transform-optional-chaining": "^7.21.0",
|
||||
"@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",
|
||||
|
|
@ -159,26 +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": "^11.0.0",
|
||||
"cheerio": "^1.0.0-rc.5",
|
||||
"clean-css-loader": "^4.2.1",
|
||||
"codegen.macro": "^4.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^8.0.0",
|
||||
"css-modules-require-hook": "^4.2.3",
|
||||
"cssnano-preset-default": "^7.0.10",
|
||||
"cssnano-preset-default": "^8.0.1",
|
||||
"domtokenlist-shim": "^1.2.0",
|
||||
"emitify": "^4.0.1",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-plugin-n": "^17.0.0-4",
|
||||
"eslint-plugin-putout": "^31.0.0",
|
||||
"globals": "^17.0.0",
|
||||
"gritty": "^10.0.2",
|
||||
"gritty": "^10.2.0",
|
||||
"gunzip-maybe": "^1.3.1",
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"inherits": "^2.0.3",
|
||||
|
|
@ -189,12 +191,10 @@
|
|||
"load.js": "^3.0.0",
|
||||
"madrun": "^13.0.0",
|
||||
"memfs": "^4.2.0",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"minor": "^1.2.2",
|
||||
"morgan": "^1.6.1",
|
||||
"multi-rename": "^3.0.0",
|
||||
"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",
|
||||
|
|
@ -205,19 +205,17 @@
|
|||
"request": "^2.76.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"scroll-into-view-if-needed": "^3.0.4",
|
||||
"serve-once": "^3.0.1",
|
||||
"serve-once": "^4.0.1",
|
||||
"smalltalk": "^5.0.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"superc8": "^12.6.0",
|
||||
"supermenu": "^5.0.0",
|
||||
"supertape": "^12.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": "^7.0.2",
|
||||
"webpack-merge": "^6.0.1",
|
||||
"webpackbar": "^7.0.0"
|
||||
"webpack-merge": "^6.0.1"
|
||||
},
|
||||
"imports": {
|
||||
"#css/": "./css/",
|
||||
|
|
|
|||
|
|
@ -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
10
rspack.config.js
Normal 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,
|
||||
]);
|
||||
|
|
@ -5,13 +5,14 @@ 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 {edward} from 'edward';
|
||||
import {dword} from 'dword';
|
||||
import {deepword} from 'deepword';
|
||||
import {qword} from 'qword';
|
||||
import nomine from 'nomine';
|
||||
import {fileop} from '@cloudcmd/fileop';
|
||||
import * as cloudfunc from '#common/cloudfunc';
|
||||
|
|
@ -152,6 +153,12 @@ function listen({prefixSocket, socket, config}) {
|
|||
prefixSocket: `${prefixSocket}/dword`,
|
||||
});
|
||||
|
||||
qword.listen(socket, {
|
||||
root,
|
||||
auth,
|
||||
prefixSocket: `${prefixSocket}/qword`,
|
||||
});
|
||||
|
||||
deepword.listen(socket, {
|
||||
root,
|
||||
auth,
|
||||
|
|
@ -186,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,
|
||||
});
|
||||
|
|
@ -215,6 +222,14 @@ function cloudcmdMiddle({modules, config}) {
|
|||
dropbox,
|
||||
dropboxToken,
|
||||
}),
|
||||
qword({
|
||||
root,
|
||||
online,
|
||||
diff,
|
||||
zip,
|
||||
dropbox,
|
||||
dropboxToken,
|
||||
}),
|
||||
deepword({
|
||||
root,
|
||||
online,
|
||||
|
|
@ -242,6 +257,7 @@ function cloudcmdMiddle({modules, config}) {
|
|||
}),
|
||||
userMenu({
|
||||
menuName: '.cloudcmd.menu.js',
|
||||
config,
|
||||
}),
|
||||
rest({
|
||||
config,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import wraptile from 'wraptile';
|
|||
import currify from 'currify';
|
||||
import pullout from 'pullout';
|
||||
import json from 'jonny';
|
||||
import ponse from 'ponse';
|
||||
import * as ponse from 'ponse';
|
||||
import {copymitter} from 'copymitter';
|
||||
import _moveFiles from '@cloudcmd/move-files';
|
||||
import * as CloudFunc from '#common/cloudfunc';
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {createRequire} from 'node:module';
|
||||
import {extname} from 'node:path';
|
||||
import * as _win32 from 'win32';
|
||||
import ponse from 'ponse';
|
||||
import * as ponse from 'ponse';
|
||||
import {rendy} from 'rendy';
|
||||
import format from 'format-io';
|
||||
import currify from 'currify';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ export const root = (dir, config, overrides = {}) => {
|
|||
};
|
||||
|
||||
export const editor = (name, {exit = _exit} = {}) => {
|
||||
const reg = /^(dword|edward|deepword)$/;
|
||||
const reg = /^(dword|edward|deepword|qword)$/;
|
||||
|
||||
if (!reg.test(name))
|
||||
exit('cloudcmd --editor: could be "dword", "edward" or "deepword" only');
|
||||
exit(`cloudcmd --editor: could be "dword", "edward", "deepword" or "qword" only, received: "${name}"`);
|
||||
};
|
||||
|
||||
export const menu = (name, {exit = _exit} = {}) => {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ test('validate: packer: not valid', (t) => {
|
|||
|
||||
test('validate: editor: not valid', (t) => {
|
||||
const exit = stub();
|
||||
const msg = 'cloudcmd --editor: could be "dword", "edward" or "deepword" only';
|
||||
const msg = 'cloudcmd --editor: could be "dword", "edward", "deepword" or "qword" only, received: "hello"';
|
||||
|
||||
validate.editor('hello', {
|
||||
exit,
|
||||
|
|
@ -131,3 +131,26 @@ test('validate: theme: wrong', (t) => {
|
|||
t.calledWith(exit, [msg], 'should call exit');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('validate: menu: not valid', (t) => {
|
||||
const exit = stub();
|
||||
const msg = 'cloudcmd --menu: could be "supermenu" or "aleman" only';
|
||||
|
||||
validate.menu('hello', {
|
||||
exit,
|
||||
});
|
||||
|
||||
t.calledWith(exit, [msg], 'should call fn');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('validate: menu: valid', (t) => {
|
||||
const exit = stub();
|
||||
|
||||
validate.menu('supermenu', {
|
||||
exit,
|
||||
});
|
||||
|
||||
t.notCalled(exit, 'should not call fn');
|
||||
t.end();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const RENAME_FILE = 'Rename file';
|
||||
const CDN = 'https://cdn.jsdelivr.net/gh/cloudcmd/user-menu@1.2.4';
|
||||
const CDN = 'https://cdn.jsdelivr.net';
|
||||
const CDN_GH = `${CDN}/gh/cloudcmd/user-menu@1.2.4`;
|
||||
|
||||
export default {
|
||||
'__settings': {
|
||||
|
|
@ -10,13 +11,7 @@ export default {
|
|||
await DOM.renameCurrent();
|
||||
},
|
||||
|
||||
'F6 - Copy URL to current file': async ({DOM}) => {
|
||||
const {copyURLToCurrentFile} = await import(`${CDN}/menu/copy-url-to-current-file.js`);
|
||||
|
||||
await copyURLToCurrentFile({
|
||||
DOM,
|
||||
});
|
||||
},
|
||||
'F6 - Copy URL to current file': runFromCDN('copy-url-to-current-file'),
|
||||
|
||||
'R - cd /': async ({CloudCmd}) => {
|
||||
await CloudCmd.changeDir('/');
|
||||
|
|
@ -156,3 +151,18 @@ function compare(a, b) {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
const MAP = {
|
||||
'copy-url-to-current-file': 'copyURLToCurrentFile',
|
||||
};
|
||||
|
||||
function runFromCDN(name) {
|
||||
return async (...a) => {
|
||||
const fnName = MAP[name];
|
||||
const {
|
||||
[fnName]: fn,
|
||||
} = await import(`${CDN_GH}/menu/${name}.js`);
|
||||
|
||||
await fn(...a);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,7 +168,9 @@ test('cloudcmd: static: user menu: C: exists: cancel', async (t) => {
|
|||
const {write} = DOM.IO;
|
||||
|
||||
getCurrentByName.returns({});
|
||||
confirm.resolves([Error('cancel')]);
|
||||
confirm.resolves([
|
||||
Error('cancel'),
|
||||
]);
|
||||
|
||||
await defaultMenu[name]({
|
||||
DOM,
|
||||
|
|
|
|||
55
test-e2e/ratelimit.js
Normal file
55
test-e2e/ratelimit.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import process from 'node:process';
|
||||
import {spawn} from 'node:child_process';
|
||||
import {test} from 'supertape';
|
||||
|
||||
test('cloudcmd: server: ratelimit: x-forwarded-for', async (t) => {
|
||||
const PORT = 3000;
|
||||
const child = await run(PORT);
|
||||
|
||||
const {status} = await fetch(`http://localhost:${PORT}`, {
|
||||
headers: {
|
||||
'X-Forwarded-For': '127.0.0.1',
|
||||
},
|
||||
});
|
||||
|
||||
child.kill();
|
||||
|
||||
t.notEqual(status, 500);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('cloudcmd: server: ratelimit', async (t) => {
|
||||
const PORT = 3001;
|
||||
const STATUS = 429;
|
||||
|
||||
const child = await run(PORT);
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
await fetch(`http://localhost:${PORT}`);
|
||||
}
|
||||
|
||||
const {status} = await fetch(`http://localhost:${PORT}`);
|
||||
child.kill();
|
||||
|
||||
t.equal(status, STATUS);
|
||||
t.end();
|
||||
});
|
||||
|
||||
function run(port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(new URL('../bin/cloudcmd.js', import.meta.url).pathname, [], {
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: port,
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
child.stdout.on('data', (a) => {
|
||||
if (a.toString().includes('url'))
|
||||
resolve(child);
|
||||
});
|
||||
|
||||
child.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
|
@ -50,8 +50,7 @@ function before(options, fn = options) {
|
|||
}));
|
||||
|
||||
server.listen(() => {
|
||||
fn(server
|
||||
.address().port, promisify(after));
|
||||
fn(server.address().port, promisify(after));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
<span data-name="js-type" class="mini-icon directory"></span>
|
||||
<span data-name="js-name" class="name reduce-text"><a href="/fs/etc" title=".." draggable="true">..</a></span>
|
||||
<span data-name="js-size" class="size reduce-text"><dir></span>
|
||||
<span data-name="js-time" class="time reduce-text">--.--.----</span>
|
||||
<span data-name="js-time" class="time reduce-text">--:--:--</span>
|
||||
<span data-name="js-date" class="date reduce-text">--.--.----</span>
|
||||
<span data-name="js-owner" class="owner reduce-text">.</span>
|
||||
<span data-name="js-mode" class="mode reduce-text">--- --- ---</span>
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
<span data-name="js-type" class="mini-icon directory"></span>
|
||||
<span data-name="js-name" class="name reduce-text"><a href="/fs/etc/X11/applnk" title="applnk" draggable="true">applnk</a></span>
|
||||
<span data-name="js-size" class="size reduce-text"><dir></span>
|
||||
<span data-name="js-time" class="time reduce-text">--.--.----</span>
|
||||
<span data-name="js-time" class="time reduce-text">--:--:--</span>
|
||||
<span data-name="js-date" class="date reduce-text">21.02.2016</span>
|
||||
<span data-name="js-owner" class="owner reduce-text">root</span>
|
||||
<span data-name="js-mode" class="mode reduce-text">rwx r-x r-x</span>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
<span data-name="js-type" class="mini-icon file"></span>
|
||||
<span data-name="js-name" class="name reduce-text"><a href="/fs/etc/X11/ай" title="ай" target="_blank" draggable="true">ай</a></span>
|
||||
<span data-name="js-size" class="size reduce-text">1.30kb</span>
|
||||
<span data-name="js-time" class="time reduce-text">--.--.----</span>
|
||||
<span data-name="js-time" class="time reduce-text">--:--:--</span>
|
||||
<span data-name="js-date" class="date reduce-text">--.--.----</span>
|
||||
<span data-name="js-owner" class="owner reduce-text">root</span>
|
||||
<span data-name="js-mode" class="mode reduce-text">rwx r-x r-x</span>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import serveOnce from 'serve-once';
|
||||
import {serveOnce} from 'serve-once';
|
||||
import test from 'supertape';
|
||||
import {cloudcmd} from '#server/cloudcmd';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {dirname, join} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {mkdirSync} from 'node:fs';
|
||||
import serveOnce from 'serve-once';
|
||||
import {serveOnce} from 'serve-once';
|
||||
import test from 'supertape';
|
||||
import {rimraf} from 'rimraf';
|
||||
import {cloudcmd} from '#server/cloudcmd';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import serveOnce from 'serve-once';
|
||||
import {serveOnce} from 'serve-once';
|
||||
import test from 'supertape';
|
||||
import {cloudcmd} from '#server/cloudcmd';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {EventEmitter} from 'node:events';
|
||||
import wait from '@iocmd/wait';
|
||||
import {test, stub} from 'supertape';
|
||||
import serveOnce from 'serve-once';
|
||||
import {serveOnce} from 'serve-once';
|
||||
import {cloudcmd} from '#server/cloudcmd';
|
||||
|
||||
test('cloudcmd: rest: move', async (t) => {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import test from 'supertape';
|
|||
import tar from 'tar-stream';
|
||||
import gunzip from 'gunzip-maybe';
|
||||
import pullout from 'pullout';
|
||||
import serveOnce from 'serve-once';
|
||||
import {serveOnce} from 'serve-once';
|
||||
import {cloudcmd} from '#server/cloudcmd';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|||
import test from 'supertape';
|
||||
import {Volume} from 'memfs';
|
||||
import {ufs} from 'unionfs';
|
||||
import serveOnce from 'serve-once';
|
||||
import {serveOnce} from 'serve-once';
|
||||
import {cloudcmd} from '#server/cloudcmd';
|
||||
|
||||
test('cloudcmd: rest: rename', async (t) => {
|
||||
|
|
|
|||
|
|
@ -4,34 +4,37 @@ import * as env from '../../server/env.js';
|
|||
|
||||
test('env: small', (t) => {
|
||||
process.env.cloudcmd_hello = 'world';
|
||||
t.equal(env.parse('hello'), 'world', 'should parse string from env');
|
||||
|
||||
delete process.env.cloudcmd_hello;
|
||||
|
||||
t.equal(env.parse('hello'), 'world', 'should parse string from env');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('env: big', (t) => {
|
||||
process.env.CLOUDCMD_HELLO = 'world';
|
||||
t.equal(env.parse('hello'), 'world', 'should parse string from env');
|
||||
|
||||
delete process.env.CLOUDCMD_HELLO;
|
||||
|
||||
t.equal(env.parse('hello'), 'world', 'should parse string from env');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('env: bool: false', (t) => {
|
||||
process.env.cloudcmd_terminal = 'false';
|
||||
t.notOk(env.bool('terminal'), 'should return false');
|
||||
|
||||
delete process.env.cloudcmd_terminal;
|
||||
|
||||
t.notOk(env.bool('terminal'), 'should return false');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('env: bool: true', (t) => {
|
||||
process.env.cloudcmd_terminal = 'true';
|
||||
|
||||
t.ok(env.bool('terminal'), 'should be true');
|
||||
|
||||
delete process.env.cloudcmd_terminal;
|
||||
|
||||
t.ok(env.bool('terminal'), 'should be true');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
@ -39,9 +42,10 @@ test('env: bool: undefined', (t) => {
|
|||
const {cloudcmd_terminal} = process.env;
|
||||
|
||||
process.env.cloudcmd_terminal = undefined;
|
||||
|
||||
t.notOk(env.bool('terminal'), 'should be undefined');
|
||||
|
||||
process.env.cloudcmd_terminal = cloudcmd_terminal;
|
||||
|
||||
const result = env.bool('terminal');
|
||||
|
||||
t.notOk(result);
|
||||
t.end();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {createRequire} from 'node:module';
|
||||
import {dirname, join} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import serveOnce from 'serve-once';
|
||||
import {serveOnce} from 'serve-once';
|
||||
import {test, stub} from 'supertape';
|
||||
import {cloudcmd} from '#server/cloudcmd';
|
||||
import modulas from '../../server/modulas.js';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {Buffer} from 'node:buffer';
|
||||
import serveOnce from 'serve-once';
|
||||
import {serveOnce} from 'serve-once';
|
||||
import test from 'supertape';
|
||||
import criton from 'criton';
|
||||
import {cloudcmd} from '#server/cloudcmd';
|
||||
|
|
|
|||
|
|
@ -57,11 +57,12 @@
|
|||
<option {{ name-size-selected }}>name-size</option>
|
||||
</select>
|
||||
</li>
|
||||
<li title="Editor: Edward(Ace-based), Dword(Codemirror-based), Deepword(Monaco-based)">
|
||||
<li title="Editor: Edward(Ace-based), Dword(Codemirror-based), Deepword(Monaco-based), Qword(Codemirror6-based)">
|
||||
<select data-name="js-editor" class="form-control full-width" title="Editor">
|
||||
<option {{ edward-selected }}>edward</option>
|
||||
<option {{ dword-selected }}>dword</option>
|
||||
<option {{ deepword-selected }}>deepword</option>
|
||||
<option {{ qword-selected }}>qword</option>
|
||||
</select>
|
||||
</li>
|
||||
<li title="Navigate and edit files using hotkeys of legendary editor">
|
||||
|
|
@ -131,7 +132,7 @@
|
|||
placeholder="root"
|
||||
class="form-control">
|
||||
</li>
|
||||
<li title="Port used by server">
|
||||
<li {{ configPort }} title="Port used by server">
|
||||
<input
|
||||
data-name="js-port"
|
||||
min="0"
|
||||
|
|
@ -172,4 +173,4 @@
|
|||
Sync Console Path
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
Loading…
Add table
Add a link
Reference in a new issue