mirror of https://github.com/keeweb/keeweb.git
Compare commits
42 Commits
Author | SHA1 | Date |
---|---|---|
Aetherinox | fe77e5399a | |
Aetherinox | 90ab4075af | |
Aetherinox | daab8c9693 | |
Aetherinox | cbae9af18b | |
Aetherinox | 1be7a55eab | |
Kevin McCormack | c1fda05c77 | |
Aetherinox | fb6aaff519 | |
Aetherinox | ab9b5a9d68 | |
Aetherinox | fe23aa9097 | |
Aetherinox | 0f8332b07a | |
Aetherinox | bd87c6f2de | |
Aetherinox | 6690d01643 | |
Aetherinox | 331ec8c738 | |
Aetherinox | 570b365f0a | |
Aetherinox | c45d7b1eb5 | |
Aetherinox | 80370d128c | |
Aetherinox | 6e63a3db52 | |
Aetherinox | d0d163bf5e | |
Aetherinox | 738d9c6a00 | |
Aetherinox | fcdce278a7 | |
Aetherinox | e4fd64d1cf | |
Aetherinox | 0fd3690ec2 | |
Aetherinox | 08c4f844ec | |
Kevin McCormack | a96a4df3a4 | |
Kevin McCormack | c5f8b28b33 | |
Kevin McCormack | 5ea7328bb8 | |
Kevin McCormack | b09e1344cc | |
Kevin McCormack | 790b32e5d0 | |
Kevin McCormack | 2fb3357ed3 | |
Kevin McCormack | 72b25a2c20 | |
Kevin McCormack | 9d346490f7 | |
Kevin McCormack | 8bb2719b21 | |
Kevin McCormack | 3dda7f171b | |
Aetherinox | a33f1e0ba1 | |
Aetherinox | ce7838d935 | |
Aetherinox | 7e06762b25 | |
Aetherinox | 1499fd789b | |
Aetherinox | ae4bce3a11 | |
Aetherinox | 2aa6e05b53 | |
Kevin McCormack | 62b3458187 | |
Dimitri Witkowski | e6df216291 | |
Anuj Koli | 3fff58353c |
|
@ -1,34 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve KeeWeb
|
|
||||||
title: ''
|
|
||||||
labels: 'bug'
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Environment**
|
|
||||||
Please copy all version information from Settings/Help
|
|
||||||
|
|
||||||
**Kdbx File**
|
|
||||||
Does it happen on Demo or New database?
|
|
||||||
If you have a test db without your personal data, please attach it.
|
|
||||||
|
|
||||||
**Logs**
|
|
||||||
Please open dev tools in your browser and attach output log from Console tab. If you are using a desktop app, devtools can be opened from Settings/General/Advanced.
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
name: "🐛 Bug Report"
|
||||||
|
description: Create a report to help us improve KeeWeb!
|
||||||
|
title: "🐛 Bug: <title>"
|
||||||
|
labels: [
|
||||||
|
"Type ◦ Bug"
|
||||||
|
]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
1. Please speak `English`.
|
||||||
|
2. Make sure you are using the latest version and take a moment to check that your issue hasn't been reported before.
|
||||||
|
3. It's really important to provide pertinent details and logs,
|
||||||
|
incomplete details will be handled as an invalid report.
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: priority-type
|
||||||
|
attributes:
|
||||||
|
label: Priority
|
||||||
|
description: |
|
||||||
|
How critical is the issue?
|
||||||
|
Do not abuse this. Issues that completely break KeeWeb would be classified as critical.
|
||||||
|
If you are requesting a new feature, then it would be low or normal.
|
||||||
|
options:
|
||||||
|
- "Low"
|
||||||
|
- "Normal"
|
||||||
|
- "High"
|
||||||
|
- "Urgent"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: ver-keeweb
|
||||||
|
attributes:
|
||||||
|
label: "Version: KeeWeb"
|
||||||
|
description: |
|
||||||
|
Version of KeeWeb you are running
|
||||||
|
Version is located in Settings › Help › App Information
|
||||||
|
placeholder: "Ex: v1.18.7"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: build-env
|
||||||
|
attributes:
|
||||||
|
label: Environment
|
||||||
|
description: |
|
||||||
|
What environment are you running KeeWeb as?
|
||||||
|
options:
|
||||||
|
- "Web"
|
||||||
|
- "Desktop"
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: build-os
|
||||||
|
attributes:
|
||||||
|
label: Operating System
|
||||||
|
description: |
|
||||||
|
Select which OS you are running:
|
||||||
|
options:
|
||||||
|
- "Windows"
|
||||||
|
- "Mac"
|
||||||
|
- "Linux"
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: issue-target
|
||||||
|
attributes:
|
||||||
|
label: Range of issue
|
||||||
|
description: |
|
||||||
|
Where does this issue occur?
|
||||||
|
Before submitting your bug report, try to see if the issue also occurs on the official demo site at https://app.keeweb.info/
|
||||||
|
options:
|
||||||
|
- "The issue occurs on my own setup AND demo site"
|
||||||
|
- "The issue only occurs on my own setup, demo site works correctly"
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: issue-db-type
|
||||||
|
attributes:
|
||||||
|
label: Database Type
|
||||||
|
description: |
|
||||||
|
What type of database are you having this issue with?
|
||||||
|
options:
|
||||||
|
- "Issue occurs only on new databases"
|
||||||
|
- "Issue occurs only on existing databases"
|
||||||
|
- "Issue occurs on both new and existing databases"
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs-env
|
||||||
|
attributes:
|
||||||
|
label: Environment Copy/Paste
|
||||||
|
description: |
|
||||||
|
Copy all information from Settings › Help inside KeeWeb
|
||||||
|
render: shell
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: |
|
||||||
|
Please provide a description of your issue here.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: steps-reproduce
|
||||||
|
attributes:
|
||||||
|
label: Steps To Reproduce
|
||||||
|
description: |
|
||||||
|
Describe the steps that need taken by the developer(s) to get the error / issue you're experiencing.
|
||||||
|
value: |
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected Behavior
|
||||||
|
description: |
|
||||||
|
Describe what you expect KeeWeb to actually do if it were working properly
|
||||||
|
value: |
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs-console
|
||||||
|
attributes:
|
||||||
|
label: Logs
|
||||||
|
description: |
|
||||||
|
List any error messages you received in the developer console.
|
||||||
|
Developer console for **web** can be opened with **SHIFT + CTRL + I**
|
||||||
|
Open **dev tools** in your browser and attach output log from **CONSOLE** tab. If you are using a desktop app, devtools can be opened from Settings › General › Advanced.
|
||||||
|
render: shell
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: references
|
||||||
|
attributes:
|
||||||
|
label: Referenced Issues
|
||||||
|
description: |
|
||||||
|
List any existing issues this ticket may be associated with.
|
||||||
|
Structure each issue as:
|
||||||
|
- #NUMBER
|
||||||
|
- #3
|
||||||
|
value: |
|
||||||
|
- #
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: attachment-db
|
||||||
|
attributes:
|
||||||
|
label: "Attachments: Database kdbx File"
|
||||||
|
description: |
|
||||||
|
Attach a test database below that does give you the described issue.
|
||||||
|
Ensure it has no real security information within it as it will be publicly visible to all.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: attachment-screenshots
|
||||||
|
attributes:
|
||||||
|
label: "Attachments: Screenshots"
|
||||||
|
description: |
|
||||||
|
Please provide screenshots of any errors or the issue you're having.
|
||||||
|
Gifs are even better.
|
|
@ -1,20 +0,0 @@
|
||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: 'enhancement'
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
name: "💡 Feature Request"
|
||||||
|
description: Got a suggestion? Submit your request here.
|
||||||
|
title: "💡 Feature: <title>"
|
||||||
|
labels: [
|
||||||
|
"Type ◦ Feature"
|
||||||
|
]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
1. Please speak English.
|
||||||
|
2. Please take a moment to check that your feature hasn't already been suggested.
|
||||||
|
3. Be detailed but to the point.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: text-description
|
||||||
|
attributes:
|
||||||
|
label: Feature Description
|
||||||
|
description: |
|
||||||
|
Explain your feature. Be detailed.
|
||||||
|
If your feature addresses a problem; explain the problem.
|
||||||
|
placeholder: |
|
||||||
|
I would like to request ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: text-alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternatives Considered
|
||||||
|
description: |
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: attachment-screenshots
|
||||||
|
attributes:
|
||||||
|
label: "Attachments: Screenshots"
|
||||||
|
description: |
|
||||||
|
If possible, provide screenshots.
|
||||||
|
Want a feature placed in a specific location? Mark it in a screenshot.
|
||||||
|
Want something modified? Try creating a mockup.
|
||||||
|
The more details about how it should look, the better.
|
||||||
|
Not required, but appreciated.
|
|
@ -250,7 +250,7 @@ jobs:
|
||||||
tagRegex: "^v(\\d+\\.\\d+\\.\\d+)$"
|
tagRegex: "^v(\\d+\\.\\d+\\.\\d+)$"
|
||||||
tagRegexGroup: 1
|
tagRegexGroup: 1
|
||||||
- name: Setup GCloud
|
- name: Setup GCloud
|
||||||
uses: google-github-actions/setup-gcloud@master
|
uses: google-github-actions/setup-gcloud@v0.7.0
|
||||||
with:
|
with:
|
||||||
version: '285.0.0'
|
version: '285.0.0'
|
||||||
service_account_key: ${{ secrets.GCP_SA_KEY }}
|
service_account_key: ${{ secrets.GCP_SA_KEY }}
|
||||||
|
@ -418,9 +418,9 @@ jobs:
|
||||||
- name: Purge CloudFlare cache
|
- name: Purge CloudFlare cache
|
||||||
if: ${{ github.repository == 'keeweb/keeweb' }}
|
if: ${{ github.repository == 'keeweb/keeweb' }}
|
||||||
env:
|
env:
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CF_TOKEN: ${{ secrets.CF_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
curl -sS -X POST "https://api.cloudflare.com/client/v4/zones/dbda8e7e454d23edb08d4bc3b3f6f872/purge_cache" \
|
curl -sS -X POST "https://api.cloudflare.com/client/v4/zones/dbda8e7e454d23edb08d4bc3b3f6f872/purge_cache" \
|
||||||
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
-H "Authorization: Bearer $CF_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
--data '{"purge_everything":true}'
|
--data '{"purge_everything":true}'
|
||||||
|
|
|
@ -0,0 +1,371 @@
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Cloudflare Pages › Update Website
|
||||||
|
# Pushes a new version of the website to Cloudflare
|
||||||
|
#
|
||||||
|
# If editing this workflow, all you need to edit are the INPUT values and the global
|
||||||
|
# ENV variables. No need to go any deeper.
|
||||||
|
#
|
||||||
|
# CHANGE:
|
||||||
|
# - run-name && name
|
||||||
|
# - INPUTS:
|
||||||
|
# PROJECT_NAME
|
||||||
|
# - ENV:
|
||||||
|
# PROJECT_NAME
|
||||||
|
# DOMAIN
|
||||||
|
# DIR_BUILD_OUTPUT
|
||||||
|
# DIR_WORKING
|
||||||
|
# WRANGLER_VERSION
|
||||||
|
# BRANCH
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
run-name: "☁️ CF › Deploy › app.keeweb.info"
|
||||||
|
name: "☁️ CF › app.keeweb.info"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# TRIGGERS
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# The default values set for each input should not need to be changed as they are
|
||||||
|
# already set to the correct values.
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
PROJECT_NAME:
|
||||||
|
description: "Project Name"
|
||||||
|
required: true
|
||||||
|
default: 'app-keeweb'
|
||||||
|
type: string
|
||||||
|
|
||||||
|
DIRECTORY_BUILD_OUTPUT:
|
||||||
|
description: "Build Output Dir"
|
||||||
|
required: true
|
||||||
|
default: 'gh-pages'
|
||||||
|
type: string
|
||||||
|
|
||||||
|
DIRECTORY_WORKING:
|
||||||
|
description: "Root Dir"
|
||||||
|
required: true
|
||||||
|
default: './'
|
||||||
|
type: string
|
||||||
|
|
||||||
|
WRANGLER_VERSION:
|
||||||
|
description: "Wrangler Version"
|
||||||
|
required: true
|
||||||
|
default: '3'
|
||||||
|
type: string
|
||||||
|
|
||||||
|
BRANCH:
|
||||||
|
description: 'Website Branch'
|
||||||
|
required: true
|
||||||
|
default: 'gh-pages'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- gh-pages
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# ENV VARIABLES
|
||||||
|
#
|
||||||
|
# PROJECT_NAME : This is the project name used in Cloudflare.
|
||||||
|
# DOMAIN : purely cosmetic which displays as the label of some steps
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
env:
|
||||||
|
PROJECT_NAME: ${{ github.event.inputs.PROJECT_NAME || 'app-keeweb' }}
|
||||||
|
DOMAIN: 'app.keeweb.info'
|
||||||
|
DIR_BUILD_OUTPUT: ${{ github.event.inputs.DIRECTORY_BUILD_OUTPUT || 'gh-pages' }}
|
||||||
|
DIR_WORKING: ${{ github.event.inputs.DIRECTORY_WORKING || './' }}
|
||||||
|
WRANGLER_VERSION: ${{ github.event.inputs.WRANGLER_VERSION || '2' }}
|
||||||
|
BRANCH: ${{ github.event.inputs.BRANCH || 'gh-pages' }}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# JOBS
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# JOB > PUBLISH
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
job-cfpublish:
|
||||||
|
name: >-
|
||||||
|
☁️ Publish Website
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
upload_github: ${{ steps.task_cloudflare_project_create_github.outputs.SUCCESS }}
|
||||||
|
upload_assets: ${{ steps.task_cloudflare_project_create_assets.outputs.SUCCESS }}
|
||||||
|
existing_site: ${{ steps.task_cloudflare_project_check.outputs.EXISTING }}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
deployments: write
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: "✅ Start Publish"
|
||||||
|
id: task_cfpublish_start
|
||||||
|
run: |
|
||||||
|
echo "Publishing new version of ${{ env.DOMAIN }} to Cloudflare Pages service"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Job > Publish > Checkout
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: "☑️ Checkout gh-pages"
|
||||||
|
id: task_cfpublish_checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: gh-pages
|
||||||
|
path: gh-pages
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Job > Publish > Commit Dist
|
||||||
|
# used in original build script for keeweb
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#- name: "Commit dist to gh-pages"
|
||||||
|
# working-directory: gh-pages
|
||||||
|
# run: |
|
||||||
|
# git rm -r '*'
|
||||||
|
# cp -r ../html/* .
|
||||||
|
# mkdir -p .github/workflows
|
||||||
|
# echo $GITHUB_SHA > build.txt
|
||||||
|
# date >> build.txt
|
||||||
|
# git add .
|
||||||
|
# git config --local user.email "action@github.com"
|
||||||
|
# git config --local user.name "GitHub Action"
|
||||||
|
# git commit -am v${{ steps.get_tag.outputs.tag }}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Job > Publish > Push
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#- name: "Push gh-pages"
|
||||||
|
# uses: keeweb/github-push-action@master
|
||||||
|
# with:
|
||||||
|
# github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# branch: gh-pages
|
||||||
|
# directory: gh-pages
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Job > Publish > Restore mtime
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#- name: Restore git mtime
|
||||||
|
# working-directory: gh-pages
|
||||||
|
# run: python3 ../keeweb/.github/actions/scripts/git-restore-mtime.py
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# CLOUDFLARE > NODE > SETUP
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: "⚙️ Setup › Node"
|
||||||
|
id: task_cfpublish_node_setup
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20.x'
|
||||||
|
scope: '@aetherinox'
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# CLOUDFLARE > NODE > INSTALL
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: "📦 NPM › Install Wrangler"
|
||||||
|
id: task_cloudflare_npm_install
|
||||||
|
run: |
|
||||||
|
npm install -g npm@latest
|
||||||
|
npm install --global wrangler
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.SELF_TOKEN_CL }}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# CLOUDFLARE > CHECK PROJECT
|
||||||
|
#
|
||||||
|
# this step checks to see if your project already exists on cloudflare
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: "☁️ CF › Check Project"
|
||||||
|
id: task_cloudflare_project_check
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
check=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/pages/projects/${{ inputs.PROJECT_NAME || env.PROJECT_NAME }}" \
|
||||||
|
-H "Authorization: Bearer ${{ secrets.CF_TOKEN }}" \
|
||||||
|
-H "Content-Type:application/json" | jq -r '.success')
|
||||||
|
echo "EXISTING=$check" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# CLOUDFLARE > CREATE PROJECT (LINKED TO GITHUB)
|
||||||
|
#
|
||||||
|
# This step will only success if you've already linked your Github account to cloudflare.
|
||||||
|
# if you attempt to push this deployment to any github repo that is not linked to
|
||||||
|
# cloudflare; the step will fail, and then execute step
|
||||||
|
# task_cloudflare_project_create_assets
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: "☁️ CF › Create Project › Linked Github (if nonexistent)"
|
||||||
|
id: task_cloudflare_project_create_github
|
||||||
|
shell: bash
|
||||||
|
if: |
|
||||||
|
${{ steps.task_cloudflare_project_check.outputs.EXISTING != 'true' }}
|
||||||
|
run: |
|
||||||
|
check=$(curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/pages/projects" \
|
||||||
|
-H "Authorization: Bearer ${{ secrets.CF_TOKEN }}" \
|
||||||
|
-H "Content-Type:application/json" \
|
||||||
|
-d '{"name": "${{ inputs.PROJECT_NAME || env.PROJECT_NAME }}", "production_branch": "${{ inputs.BRANCH || env.BRANCH || 'master' }}", "source": {"type": "github", "config": {"owner": "${{ github.repository_owner }}", "repo_name": "${{ github.event.repository.name }}"}}}' | jq -r '.success')
|
||||||
|
echo "SUCCESS=$check" >> $GITHUB_OUTPUT
|
||||||
|
echo "Mode (Link Github): $check"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# CLOUDFLARE > CREATE PROJECT
|
||||||
|
#
|
||||||
|
# this step should execute only if the previous step failed.
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: "☁️ CF › Create Project › Upload Assets (if nonexistent)"
|
||||||
|
id: task_cloudflare_project_create_assets
|
||||||
|
shell: bash
|
||||||
|
if: |
|
||||||
|
steps.task_cloudflare_project_check.outputs.EXISTING != 'true' && ( ${{ steps.task_cloudflare_project_create_github.outputs.SUCCESS == 'false' || failure()}} )
|
||||||
|
run: |
|
||||||
|
check=$(curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/pages/projects" \
|
||||||
|
-H "Authorization: Bearer ${{ secrets.CF_TOKEN }}" \
|
||||||
|
-H "Content-Type:application/json" \
|
||||||
|
-d '{"name":"${{ inputs.PROJECT_NAME || env.PROJECT_NAME }}", "production_branch":"${{ inputs.BRANCH || env.BRANCH || 'master' }}"}' | jq -r '.success')
|
||||||
|
echo "SUCCESS=$check" >> $GITHUB_OUTPUT
|
||||||
|
echo "Mode (Upload Assets): $check"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# CLOUDFLARE > CREATE > SUMMARY
|
||||||
|
#
|
||||||
|
# outlines which method was used to create the project
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: "☁️ CF › Create Project › Summary"
|
||||||
|
id: task_cloudflare_project_summary
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ ${{ steps.task_cloudflare_project_create_github.outputs.SUCCESS }} == 'true' ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Project ${{ env.PROJECT_NAME }} linked to a Github account"
|
||||||
|
echo ""
|
||||||
|
elif [ ${{ steps.task_cloudflare_project_create_assets.outputs.SUCCESS }} == 'true' ]; then
|
||||||
|
echo "========================================================================="
|
||||||
|
echo ""
|
||||||
|
echo " Project ${{ env.PROJECT_NAME }} NOT linked to a Github account."
|
||||||
|
echo " Uploading assets to Cloudflare"
|
||||||
|
echo ""
|
||||||
|
echo "========================================================================="
|
||||||
|
echo "If you wish to sync the assets from Github to Cloudflare, you must ensure"
|
||||||
|
echo "you have connected your Github account to cloudflare and that you're"
|
||||||
|
echo "uploading from the same account that is linked."
|
||||||
|
elif [ ${{ steps.task_cloudflare_project_check.outputs.EXISTING }} == 'true' ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Site already exists on Cloudflare, updating ${{ env.PROJECT_NAME }}"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "Error occured creating ${{ env.PROJECT_NAME }}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Job > Publish > Cloudflare Wrangler
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#- name: "☁️ CF › Publish KeeWeb.info"
|
||||||
|
# uses: cloudflare/wrangler-action@v3
|
||||||
|
# with:
|
||||||
|
# apiToken: ${{ secrets.CF_TOKEN }} # Cloudflare API Token at https://dash.cloudflare.com/profile/api-tokens
|
||||||
|
# accountId: ${{ secrets.CF_ACCOUNT_ID }} # Cloudflare account ID available on right side of CF website
|
||||||
|
# workingDirectory: ${{ inputs.DIRECTORY_WORKING || env.DIR_WORKING || './' }} # Working directory
|
||||||
|
# command: pages deploy ${{ inputs.DIRECTORY_BUILD_OUTPUT || env.DIR_BUILD_OUTPUT || './' }} --project-name=${{ inputs.PROJECT_NAME || env.PROJECT_NAME }} --commit-dirty=true
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Cloudflare Pages Action
|
||||||
|
#
|
||||||
|
# a new version has been supplied above to switch from
|
||||||
|
# cloudflare/pages-action@v1 -> cloudflare/wrangler-action@v3
|
||||||
|
# the new action uses NodeJS 20, instead of 16.
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: "☁️ CF › Publish ${{ env.DOMAIN }}"
|
||||||
|
uses: aetherinox/cloudflare-publish-action@latest
|
||||||
|
with:
|
||||||
|
apiToken: ${{ secrets.CF_TOKEN }} # Cloudflare API Token at https://dash.cloudflare.com/profile/api-tokens
|
||||||
|
accountId: ${{ secrets.CF_ACCOUNT_ID }} # Cloudflare account ID available on right side of CF website
|
||||||
|
projectName: ${{ inputs.PROJECT_NAME || env.PROJECT_NAME }} # Project name assigned at creation. view on workers-and-pages section of CF website
|
||||||
|
directory: ${{ inputs.DIRECTORY_BUILD_OUTPUT || env.DIR_BUILD_OUTPUT || './' }} # Output directory for built website
|
||||||
|
gitHubToken: ${{ secrets.SELF_TOKEN_CL }} # Optional: Enable this if you want to have GitHub Deployments triggered
|
||||||
|
branch: ${{ inputs.BRANCH || env.BRANCH || 'master' }} # Branch website published to; by default this will be the branch which triggered this workflow
|
||||||
|
workingDirectory: ${{ inputs.DIRECTORY_WORKING || env.DIR_WORKING || './' }} # Working directory
|
||||||
|
wranglerVersion: ${{ inputs.WRANGLER_VERSION || env.WRANGLER_VERSION || '3' }} # Optional: Change the Wrangler version, allows you to point to a specific version or a tag such as `beta`
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Cloudflare > Purge Cache
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: "☁️ CF › Purge Cache"
|
||||||
|
id: task_cloudflare_purge_cache
|
||||||
|
run: |
|
||||||
|
check=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CF_ZONE_SITE_MAIN }}/purge_cache" \
|
||||||
|
-H "Authorization: Bearer ${{ secrets.CF_TOKEN }}" \
|
||||||
|
-H "Content-Type:application/json" \
|
||||||
|
-d '{"purge_everything":true}' | jq -r '.success')
|
||||||
|
echo "SUCCESS=$check" >> $GITHUB_OUTPUT
|
||||||
|
echo "Purge Cache: $check"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# JOB > COMPLETE
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
job-complete:
|
||||||
|
name: >-
|
||||||
|
🆗 Successful Deployment
|
||||||
|
needs: job-cfpublish
|
||||||
|
env:
|
||||||
|
UPLOAD_GITHUB: ${{ needs.job-cfpublish.outputs.upload_github }}
|
||||||
|
UPLOAD_ASSETS: ${{ needs.job-cfpublish.outputs.upload_assets }}
|
||||||
|
EXISTING_SITE: ${{ needs.job-cfpublish.outputs.existing_site }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Job > Complete > Get publish timestamp
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: "🕛 Get Timestamp"
|
||||||
|
id: task_complete_timestamp_get
|
||||||
|
run: |
|
||||||
|
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Job > Complete > Summary of publish
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: "🆗 CF Pages Deployment: ${{ env.DOMAIN }}"
|
||||||
|
id: task_complete_summary
|
||||||
|
run: |
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo "| Name | Result |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| ------------------------------- | ----------------------- |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **Cloudflare Project ID** | ${{ env.PROJECT_NAME }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **Domain** | ${{ env.DOMAIN }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **Deploy Time** | ${{ env.NOW }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ ${{ env.EXISTING_SITE }} == 'true' ]; then
|
||||||
|
echo "| **Update Type** | 🟩 Update Existing |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
if [ ${{ env.UPLOAD_GITHUB }} == 'true' ]; then
|
||||||
|
echo "| **Update Type** | 🇬 Create (Link Github) |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ ${{ env.UPLOAD_ASSETS }} == 'true' ]; then
|
||||||
|
echo "| **Update Type** | 📦 Create (Asset Upload) |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "| **Update Type** | ❌ Could not push to Cloudflare |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,31 @@
|
||||||
|
name: Verify
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'npm'
|
||||||
|
- name: Install npm modules
|
||||||
|
run: npm ci
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'npm'
|
||||||
|
- name: Install npm modules
|
||||||
|
run: npm ci
|
||||||
|
- name: Test
|
||||||
|
run: npm test
|
|
@ -16,7 +16,8 @@ Donate: [OpenCollective](https://opencollective.com/keeweb#support), [GitHub](ht
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
The app is quite stable now. Basic stuff, as well as more advanced operations, should be rather reliable.
|
The app is quite stable now. Basic stuff, as well as more advanced operations, should be rather reliable.
|
||||||
|
Looking for a new maintainer, see [#2022](https://github.com/keeweb/keeweb/issues/2022)
|
||||||
|
|
||||||
## Self-hosting
|
## Self-hosting
|
||||||
|
|
||||||
|
|
|
@ -467,6 +467,7 @@ class EntryModel extends Model {
|
||||||
if (otpUrl.isProtected) {
|
if (otpUrl.isProtected) {
|
||||||
otpUrl = otpUrl.getText();
|
otpUrl = otpUrl.getText();
|
||||||
}
|
}
|
||||||
|
// called only if secret provided, no formatted url
|
||||||
if (Otp.isSecret(otpUrl.replace(/\s/g, ''))) {
|
if (Otp.isSecret(otpUrl.replace(/\s/g, ''))) {
|
||||||
otpUrl = Otp.makeUrl(otpUrl.replace(/\s/g, '').toUpperCase());
|
otpUrl = Otp.makeUrl(otpUrl.replace(/\s/g, '').toUpperCase());
|
||||||
} else if (otpUrl.toLowerCase().lastIndexOf('otpauth:', 0) !== 0) {
|
} else if (otpUrl.toLowerCase().lastIndexOf('otpauth:', 0) !== 0) {
|
||||||
|
|
|
@ -536,7 +536,7 @@ class FileModel extends Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeKeyFile() {
|
removeKeyFile() {
|
||||||
this.db.credentials.keyFileHash = null;
|
this.db.credentials.setKeyFile(null);
|
||||||
const changed = !!this.oldKeyFileHash;
|
const changed = !!this.oldKeyFileHash;
|
||||||
if (!changed && this.db.credentials.passwordHash === this.oldPasswordHash) {
|
if (!changed && this.db.credentials.passwordHash === this.oldPasswordHash) {
|
||||||
this.db.meta.keyChanged = this.oldKeyChangeDate;
|
this.db.meta.keyChanged = this.oldKeyChangeDate;
|
||||||
|
|
|
@ -12,7 +12,7 @@ class CsvParser {
|
||||||
this.result = [];
|
this.result = [];
|
||||||
this.next = this.handleBeforeValue;
|
this.next = this.handleBeforeValue;
|
||||||
this.index = 0;
|
this.index = 0;
|
||||||
while (this.next && this.index < this.csv.length) {
|
while (this.next && this.index <= this.csv.length) {
|
||||||
this.next = this.next(this);
|
this.next = this.next(this);
|
||||||
}
|
}
|
||||||
if (this.lines.length <= 1) {
|
if (this.lines.length <= 1) {
|
||||||
|
|
|
@ -135,12 +135,12 @@ Otp.leftPad = function (str, len) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Otp.parseUrl = function (url) {
|
Otp.parseUrl = function (url) {
|
||||||
const match = /^otpauth:\/\/(\w+)\/([^\?]+)\?(.*)/i.exec(url);
|
const match = /^otpauth:\/\/(\w+)(?:\/([^\?]+)\?|\?)(.*)/i.exec(url);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
throw 'Not OTP url';
|
throw 'Not OTP url';
|
||||||
}
|
}
|
||||||
const params = {};
|
const params = {};
|
||||||
const label = decodeURIComponent(match[2]);
|
const label = decodeURIComponent(match[2] ?? 'default');
|
||||||
if (label) {
|
if (label) {
|
||||||
const parts = label.split(':');
|
const parts = label.split(':');
|
||||||
params.issuer = parts[0].trim();
|
params.issuer = parts[0].trim();
|
||||||
|
@ -148,7 +148,8 @@ Otp.parseUrl = function (url) {
|
||||||
params.account = parts[1].trim();
|
params.account = parts[1].trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
params.type = match[1].toLowerCase();
|
params.type = match[1].toLowerCase(); // returns "totp"
|
||||||
|
// match[3] = secret=XXXXXXXXXXXXX&period=30&digits=6&algorithm=SHA1
|
||||||
match[3].split('&').forEach((part) => {
|
match[3].split('&').forEach((part) => {
|
||||||
const parts = part.split('=', 2);
|
const parts = part.split('=', 2);
|
||||||
params[parts[0].toLowerCase()] = decodeURIComponent(parts[1]);
|
params[parts[0].toLowerCase()] = decodeURIComponent(parts[1]);
|
||||||
|
|
|
@ -8,6 +8,11 @@ class RandomNameGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
function charCodeToHtml(char) {
|
function charCodeToHtml(char) {
|
||||||
|
// convert certain special chars like space into to non-breaking space
|
||||||
|
// ' ' to &#nbsp;
|
||||||
|
if (char === 32 || char === 8193 || char === 8239) {
|
||||||
|
char = 160;
|
||||||
|
}
|
||||||
return Math.random() < 0.2 ? String.fromCharCode(char) : `&#x${char.toString(16)};`;
|
return Math.random() < 0.2 ? String.fromCharCode(char) : `&#x${char.toString(16)};`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,19 @@ module.exports = function (grunt) {
|
||||||
const done = this.async();
|
const done = this.async();
|
||||||
const opt = this.options();
|
const opt = this.options();
|
||||||
const file = this.files[0].src[0];
|
const file = this.files[0].src[0];
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const puppeteer = require('puppeteer');
|
const puppeteer = require('puppeteer');
|
||||||
|
|
||||||
(async function () {
|
(async function () {
|
||||||
grunt.log.writeln('Running tests...');
|
grunt.log.writeln('Running tests...');
|
||||||
|
|
||||||
const fullPath = 'file://' + path.resolve(file);
|
const fullPath = 'file://' + path.resolve(file);
|
||||||
|
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
headless: opt.headless
|
headless: opt.headless,
|
||||||
|
executablePath: process.env.CHROME_BIN || null,
|
||||||
|
args: ['--disable-dev-shm-usage']
|
||||||
});
|
});
|
||||||
|
grunt.log.writeln('puppeteer launched...');
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await page.goto(fullPath);
|
await page.goto(fullPath);
|
||||||
|
|
||||||
async function check() {
|
async function check() {
|
||||||
const result = await page.evaluate(() => {
|
const result = await page.evaluate(() => {
|
||||||
const { output, done } = window;
|
const { output, done } = window;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -32,7 +32,7 @@
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^5.2.4",
|
"css-loader": "^5.2.4",
|
||||||
"dompurify": "^2.2.8",
|
"dompurify": "^2.2.8",
|
||||||
"electron": "^12.0.7",
|
"electron": "^13.6.9",
|
||||||
"electron-builder": "^22.11.1",
|
"electron-builder": "^22.11.1",
|
||||||
"electron-evil-feature-patcher": "^1.2.1",
|
"electron-evil-feature-patcher": "^1.2.1",
|
||||||
"electron-notarize": "^1.0.0",
|
"electron-notarize": "^1.0.0",
|
||||||
|
@ -110,15 +110,18 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "grunt",
|
"start": "grunt",
|
||||||
|
"lint": "grunt eslint",
|
||||||
"test": "grunt test",
|
"test": "grunt test",
|
||||||
"build-beta": "grunt --beta && cp dist/index.html ../keeweb-beta/index.html && cd ../keeweb-beta && git add index.html && git commit -a -m 'beta' && git push origin master",
|
"build-beta": "grunt --beta && cp dist/index.html ../keeweb-beta/index.html && cd ../keeweb-beta && git add index.html && git commit -a -m 'beta' && git push origin master",
|
||||||
"electron": "cross-env KEEWEB_IS_PORTABLE=0 ELECTRON_DISABLE_SECURITY_WARNINGS=1 KEEWEB_EMULATE_HARDWARE_ENCRYPTION=persistent KEEWEB_HTML_PATH=http://localhost:8085 electron desktop --no-sandbox",
|
"electron": "cross-env KEEWEB_IS_PORTABLE=0 ELECTRON_DISABLE_SECURITY_WARNINGS=1 KEEWEB_EMULATE_HARDWARE_ENCRYPTION=persistent KEEWEB_HTML_PATH=http://localhost:8085 electron desktop --no-sandbox",
|
||||||
"dev": "grunt dev",
|
"dev": "grunt dev",
|
||||||
|
"dev-legacy": "cross-env NODE_OPTIONS='--openssl-legacy-provider' grunt dev",
|
||||||
"dev-desktop-macos": "grunt dev-desktop-darwin --skip-sign",
|
"dev-desktop-macos": "grunt dev-desktop-darwin --skip-sign",
|
||||||
"dev-desktop-macos-signed": "grunt dev-desktop-darwin-signed",
|
"dev-desktop-macos-signed": "grunt dev-desktop-darwin-signed",
|
||||||
"dev-desktop-windows": "grunt dev-desktop-win32 --skip-sign",
|
"dev-desktop-windows": "grunt dev-desktop-win32 --skip-sign",
|
||||||
"dev-desktop-linux": "grunt dev-desktop-linux --skip-sign",
|
"dev-desktop-linux": "grunt dev-desktop-linux --skip-sign",
|
||||||
"babel-helpers": "babel-external-helpers -l 'slicedToArray,toConsumableArray,defineProperty,typeof' -t global > app/lib/babel-helpers.js"
|
"babel-helpers": "babel-external-helpers -l 'slicedToArray,toConsumableArray,defineProperty,typeof' -t global > app/lib/babel-helpers.js",
|
||||||
|
"set-legacy": "export NODE_OPTIONS=--openssl-legacy-provider"
|
||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Antelle",
|
"name": "Antelle",
|
||||||
|
@ -140,4 +143,4 @@
|
||||||
"last 1 ChromeAndroid version",
|
"last 1 ChromeAndroid version",
|
||||||
"Electron > 6.0.1"
|
"Electron > 6.0.1"
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
Reference in New Issue